From 7ba6b81eee5f0c5381129e2d46dd41026c83f02f Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Fri, 25 Sep 2026 14:37:42 -0400 Subject: [PATCH 01/46] Fix Cargo hosted workspace redirects Resolve inherited dependency aliases through their workspace definitions and match local lockfile owners by both package name and version. This allows valid workspace patches while refusing consumers outside the editable project. Read quoted dependency values and annotated registry tables consistently so repeated application cannot leave an alias unpatched or create an invalid duplicate TOML table. Validated with 70 Cargo redirect unit tests and real service-backed hosted, vendored service, and vendored build installs using fresh Cargo caches, locked offline builds, integrity rejection, and revert. Assisted-by: Codex:gpt-6-astra --- .../src/patch/redirect/mod.rs | 563 +++++++++++++++--- 1 file changed, 471 insertions(+), 92 deletions(-) diff --git a/crates/socket-patch-core/src/patch/redirect/mod.rs b/crates/socket-patch-core/src/patch/redirect/mod.rs index ebcd2fab..b22fe03e 100644 --- a/crates/socket-patch-core/src/patch/redirect/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/mod.rs @@ -765,11 +765,13 @@ fn rewrite_cargo( for (path, text) in manifests.iter_mut() { *text = to_lf(path, std::mem::take(text)); } - // Each manifest's own `[package] name` — how Cargo.lock names the - // source-less (workspace / path) package it declares. - let manifest_packages: Vec> = manifests + let workspace_version = files + .get("Cargo.toml") + .and_then(|text| text.parse::().ok()) + .and_then(|doc| cargo_workspace_package_version(&doc).map(str::to_string)); + let manifest_packages: Vec> = manifests .iter() - .map(|(_, text)| cargo_manifest_package_name(text)) + .map(|(path, text)| cargo_manifest_package_id(text, path, workspace_version.as_deref())) .collect(); let edits_before = result.edits.len(); let mut changed_manifests: std::collections::BTreeSet = @@ -950,9 +952,10 @@ fn rewrite_cargo( // a symlink) — keeps resolving it from crates.io, so the repointed // lock is unsatisfiable and that consumer compiles the unpatched copy. if let Some(lock_text) = cargo_lock.as_deref() { - let pinned_packages: std::collections::BTreeSet<&str> = toml_plans + let pinned_packages: std::collections::BTreeSet<(&str, &str)> = toml_plans .iter() - .filter_map(|(i, _)| manifest_packages[*i].as_deref()) + .filter_map(|(i, _)| manifest_packages[*i].as_ref()) + .map(|(name, version)| (name.as_str(), version.as_str())) .collect(); let blocking = cargo_unpinnable_dependents(lock_text, &dep.name, &dep.version, &pinned_packages); @@ -1187,14 +1190,44 @@ fn cargo_requirement_excludes_detail( ) } -/// The `[package] name` a manifest declares (`None` for a virtual workspace -/// root or an unparseable file). -fn cargo_manifest_package_name(text: &str) -> Option { - let doc = text.parse::().ok()?; - doc.get("package")? - .get("name")? +fn cargo_workspace_package_version(doc: &toml_edit::DocumentMut) -> Option<&str> { + doc.get("workspace")? + .get("package")? + .get("version")? .as_str() - .map(str::to_string) +} + +fn cargo_manifest_package_id( + text: &str, + path: &str, + workspace_version: Option<&str>, +) -> Option<(String, String)> { + let doc = text.parse::().ok()?; + let package = doc.get("package")?; + let name = package.get("name")?.as_str()?; + let version = match package.get("version") { + None => "0.0.0", + Some(version) => match version.as_str() { + Some(version) => version, + None if version.get("workspace").and_then(toml_edit::Item::as_bool) == Some(true) => { + if doc.get("workspace").is_some() { + cargo_workspace_package_version(&doc)? + } else { + if let Some(workspace) = package.get("workspace") { + let dir = path.strip_suffix("/Cargo.toml").unwrap_or(""); + if crate::utils::cargo_workspace::normalize_rel(dir, workspace.as_str()?) + .is_none_or(|workspace| !workspace.is_empty()) + { + return None; + } + } + workspace_version? + } + } + None => return None, + }, + }; + Some((name.to_string(), version.to_string())) } /// The Cargo.lock packages that depend on `crate_name@version` and that a @@ -1208,7 +1241,7 @@ fn cargo_unpinnable_dependents( lock: &str, crate_name: &str, version: &str, - pinned_packages: &std::collections::BTreeSet<&str>, + pinned_packages: &std::collections::BTreeSet<(&str, &str)>, ) -> Vec { let Ok(doc) = lock.parse::() else { return vec!["Cargo.lock (it does not parse as TOML)".to_string()]; @@ -1246,7 +1279,7 @@ fn cargo_unpinnable_dependents( }; out.push(format!("{name} {pkg_version} ({kind})")); } - None if !pinned_packages.contains(name) => { + None if !pinned_packages.contains(&(name, pkg_version)) => { out.push(format!( "{name} {pkg_version} (a path package whose Cargo.toml is outside the \ project or not rewritable)" @@ -1477,12 +1510,6 @@ fn is_socket_patch_registry_name(value: &str) -> bool { /// the inline spelling and read the other three as "not redirected". pub(crate) fn cargo_socket_registry_pin(content: &str, crate_name: &str) -> Option { let lines: Vec<&str> = content.split('\n').collect(); - let socket_value = |text: &str| -> Option { - CARGO_TOML_REGISTRY_VAL_RE - .captures(text) - .map(|c| c[1].to_string()) - .filter(|v| is_socket_patch_registry_name(v)) - }; let mut section = CargoTomlSection::Other; for (idx, raw) in lines.iter().enumerate() { let trimmed = raw.trim_start(); @@ -1517,13 +1544,7 @@ pub(crate) fn cargo_socket_registry_pin(content: &str, crate_name: &str) -> Opti if k != name { return None; } - let v = rest.trim_start().strip_prefix('=')?.trim(); - Some( - v.strip_prefix('"') - .and_then(|s| s.split('"').next()) - .unwrap_or(v) - .to_string(), - ) + cargo_toml_string(rest.trim_start().strip_prefix('=')?) }) }; let is_ours = match value_of("package") { @@ -1548,11 +1569,14 @@ pub(crate) fn cargo_socket_registry_pin(content: &str, crate_name: &str) -> Opti if let Some(dotted) = rest_trim.strip_prefix('.') { // `.registry = "socket-patch-…"`: a spelling this rewriter // refuses to write, but a hand edit can leave one behind. - if key == crate_name - && parse_cargo_entry_key(dotted).is_some_and(|(k, _)| k == "registry") - { - if let Some(reg) = socket_value(trimmed) { - return Some(reg); + if key == crate_name { + let registry = parse_cargo_entry_key(dotted) + .filter(|(key, _)| key == "registry") + .and_then(|(_, rest)| rest.trim_start().strip_prefix('=')) + .and_then(cargo_toml_string) + .filter(|registry| is_socket_patch_registry_name(registry)); + if registry.is_some() { + return registry; } } continue; @@ -1567,12 +1591,14 @@ pub(crate) fn cargo_socket_registry_pin(content: &str, crate_name: &str) -> Opti continue; }; let inner = &value[1..close]; - let is_ours = match CARGO_TOML_PACKAGE_RE.captures(inner) { - Some(c) => c[1] == *crate_name, + let is_ours = match cargo_toml_inline_string(inner, "package") { + Some(package) => package == crate_name, None => key == crate_name, }; if is_ours { - if let Some(reg) = socket_value(inner) { + if let Some(reg) = cargo_toml_inline_string(inner, "registry") + .filter(|registry| is_socket_patch_registry_name(registry)) + { return Some(reg); } } @@ -1695,6 +1721,25 @@ fn parse_cargo_entry_key(line: &str) -> Option<(String, &str)> { } } +fn cargo_toml_string(value: &str) -> Option { + let document = format!("value = {value}") + .parse::() + .ok()?; + document.get("value")?.as_str().map(str::to_string) +} + +fn cargo_toml_inline_string(inner: &str, key: &str) -> Option { + let document = format!("dependency = {{{inner}}}") + .parse::() + .ok()?; + document + .get("dependency")? + .as_inline_table()? + .get(key)? + .as_str() + .map(str::to_string) +} + struct CargoTomlPlan { content: String, edits: Vec, @@ -1740,11 +1785,9 @@ enum CargoTomlAction { static CARGO_TOML_HEADER_RE: LazyLock = LazyLock::new(|| { Regex::new(r"^\[([^\]]+)\]\s*(?:#.*)?$").expect("static section-header regex is valid") }); -static CARGO_TOML_PACKAGE_RE: LazyLock = LazyLock::new(|| { - Regex::new(r#"\bpackage\s*=\s*"([^"]*)""#).expect("static package-key regex is valid") -}); static CARGO_TOML_REGISTRY_VAL_RE: LazyLock = LazyLock::new(|| { - Regex::new(r#"\bregistry\s*=\s*"([^"]*)""#).expect("static registry-value regex is valid") + Regex::new(r#"(?:\bregistry|"registry"|'registry')\s*=\s*(?:"[^"]*"|'[^']*')"#) + .expect("static registry-value regex is valid") }); static CARGO_TOML_REGISTRY_KEY_RE: LazyLock = LazyLock::new(|| { Regex::new(r"\bregistry\s*=").expect("static registry-key probe regex is valid") @@ -1759,10 +1802,6 @@ static CARGO_TOML_PATH_GIT_RE: LazyLock = LazyLock::new(|| { Regex::new(r"\b(?:path|git)\s*=").expect("static path/git probe regex is valid") }); -static CARGO_TOML_VERSION_VAL_RE: LazyLock = LazyLock::new(|| { - Regex::new(r#"\bversion\s*=\s*"([^"]*)""#).expect("static version-value regex is valid") -}); - /// Whether one declaration's version requirement selects the patched /// version. Cargo resolves a declaration to ONE version, so a project that /// locks several versions of a crate (`cfg-if = "1"` beside a renamed @@ -1817,6 +1856,7 @@ enum CargoWorkspaceEntry { Pinned, /// The entry names the crate at another version. OtherVersion, + OtherPackage, } /// Every version of `crate_name` a Cargo.lock holds other than `version`. @@ -1853,13 +1893,11 @@ fn plan_cargo_toml( ) -> Result { let lines: Vec<&str> = content.split('\n').collect(); let header_re: &Regex = &CARGO_TOML_HEADER_RE; - let package_re: &Regex = &CARGO_TOML_PACKAGE_RE; let registry_val_re: &Regex = &CARGO_TOML_REGISTRY_VAL_RE; let registry_key_re: &Regex = &CARGO_TOML_REGISTRY_KEY_RE; let registry_index_re: &Regex = &CARGO_TOML_REGISTRY_INDEX_RE; let workspace_key_re: &Regex = &CARGO_TOML_WORKSPACE_KEY_RE; let path_git_re: &Regex = &CARGO_TOML_PATH_GIT_RE; - let version_val_re: &Regex = &CARGO_TOML_VERSION_VAL_RE; let ambiguous = || format!("its version requirement also matches another locked version of {crate_name}"); @@ -1913,39 +1951,37 @@ fn plan_cargo_toml( if k == key_name { let rest = rest.trim_start(); if let Some(v) = rest.strip_prefix('=') { - let v = v.trim(); - let v = v - .strip_prefix('"') - .and_then(|s| s.split('"').next()) - .unwrap_or(v); - return Some((*j, v.to_string())); + return cargo_toml_string(v).map(|value| (*j, value)); } } } } None }; + let has = |name: &str| { + block.iter().any(|(_, t)| { + parse_cargo_entry_key(t).is_some_and(|(k, rest)| { + k == name && rest.trim_start().starts_with('=') + }) + }) + }; + if has("workspace") { + pending.push(Pending::NeedsWorkspacePin(key.clone())); + continue; + } let package_val = find_value("package").map(|(_, v)| v); let is_ours = match &package_val { Some(p) => p == crate_name, None => key == crate_name, }; if !is_ours { + if ws { + ws_entries.insert(key.clone(), CargoWorkspaceEntry::OtherPackage); + } continue; } - let has = |name: &str| { - block.iter().any(|(_, t)| { - parse_cargo_entry_key(t).is_some_and(|(k, rest)| { - k == name && rest.trim_start().starts_with('=') - }) - }) - }; let req = find_value("version").map(|(_, v)| v); - let selects = if has("workspace") { - CargoReqMatch::Ours - } else { - cargo_req_selects(req.as_deref(), version, other_versions) - }; + let selects = cargo_req_selects(req.as_deref(), version, other_versions); if selects == CargoReqMatch::NotOurs { excluded.extend(req); if ws { @@ -1953,9 +1989,7 @@ fn plan_cargo_toml( } continue; } - if has("workspace") { - pending.push(Pending::NeedsWorkspacePin(key.clone())); - } else if selects == CargoReqMatch::Ambiguous { + if selects == CargoReqMatch::Ambiguous { pending.push(Pending::Refuse(ambiguous())); } else if has("path") || has("git") { pending.push(Pending::Refuse( @@ -2012,19 +2046,19 @@ fn plan_cargo_toml( if let Some(dotted) = rest_trim.strip_prefix('.') { // Dotted entry (`serde.workspace = true`, `serde.version = "1"`, // `alias.package = "serde"`, …). - let sub = parse_cargo_entry_key(dotted).map(|(k, _)| k); - if key == crate_name { - if sub.as_deref() == Some("workspace") { - pending.push(Pending::NeedsWorkspacePin(key.clone())); - } else { - pending.push(Pending::Refuse( - "declared with dotted keys this rewriter does not edit".to_string(), - )); - } - } else if sub.as_deref() == Some("package") - && package_re - .captures(trimmed) - .is_some_and(|c| &c[1] == crate_name) + let sub = parse_cargo_entry_key(dotted); + if sub.as_ref().is_some_and(|(key, _)| key == "workspace") { + pending.push(Pending::NeedsWorkspacePin(key.clone())); + } else if key == crate_name { + pending.push(Pending::Refuse( + "declared with dotted keys this rewriter does not edit".to_string(), + )); + } else if sub + .filter(|(key, _)| key == "package") + .and_then(|(_, rest)| rest.trim_start().strip_prefix('=')) + .and_then(cargo_toml_string) + .as_deref() + == Some(crate_name) { pending.push(Pending::Refuse( "declared with dotted keys this rewriter does not edit".to_string(), @@ -2049,19 +2083,22 @@ fn plan_cargo_toml( continue; }; let inner = &value[1..close]; - let package_val = package_re.captures(inner).map(|c| c[1].to_string()); + if workspace_key_re.is_match(inner) { + pending.push(Pending::NeedsWorkspacePin(key.clone())); + continue; + } + let package_val = cargo_toml_inline_string(inner, "package"); let is_ours = match &package_val { Some(p) => p == crate_name, None => key == crate_name, }; if !is_ours { + if workspace { + ws_entries.insert(key.clone(), CargoWorkspaceEntry::OtherPackage); + } continue; } - if workspace_key_re.is_match(inner) { - pending.push(Pending::NeedsWorkspacePin(key.clone())); - continue; - } - let req = version_val_re.captures(inner).map(|c| c[1].to_string()); + let req = cargo_toml_inline_string(inner, "version"); match cargo_req_selects(req.as_deref(), version, other_versions) { CargoReqMatch::NotOurs => { excluded.extend(req); @@ -2080,8 +2117,7 @@ fn plan_cargo_toml( pending.push(Pending::Refuse( "declared as a path/git dependency".to_string(), )); - } else if let Some(c) = registry_val_re.captures(inner) { - let value = c[1].to_string(); + } else if let Some(value) = cargo_toml_inline_string(inner, "registry") { if value == reg { pending.push(Pending::Action(CargoTomlAction::Already)); if workspace { @@ -2216,12 +2252,13 @@ fn plan_cargo_toml( actions.push(CargoTomlAction::InheritsWorkspace); } // Inherits another version of the crate: not this dep. - Some(CargoWorkspaceEntry::OtherVersion) => {} - None => { + Some(CargoWorkspaceEntry::OtherVersion | CargoWorkspaceEntry::OtherPackage) => {} + None if key == crate_name => { return Err("inherits from [workspace.dependencies] with no rewritable \ entry for it" .to_string()); } + None => {} }, Pending::Refuse(reason) => return Err(reason), } @@ -2570,7 +2607,19 @@ fn plan_cargo_config( let header = format!("[registries.{reg}]"); let index_line = format!("index = \"{index_url}\""); let lines: Vec<&str> = config.split('\n').collect(); - let header_idx = lines.iter().position(|l| l.trim() == header); + let header_idx = lines.iter().position(|line| { + if !line.trim_start().starts_with('[') { + return false; + } + let Ok(document) = line.parse::() else { + return false; + }; + document + .get("registries") + .and_then(|registries| registries.get(reg)) + .and_then(toml_edit::Item::as_table) + .is_some_and(|table| !table.is_implicit()) + }); if let Some(i) = header_idx { let mut end = lines.len(); for (j, l) in lines.iter().enumerate().skip(i + 1) { @@ -2583,7 +2632,17 @@ fn plan_cargo_config( while end > i + 1 && lines[end - 1].trim().is_empty() { end -= 1; } - let healthy = lines[i + 1..end].iter().any(|l| l.trim() == index_line); + let healthy = lines[i + 1..end] + .join("\n") + .parse::() + .ok() + .and_then(|document| { + document + .get("index") + .and_then(toml_edit::Item::as_str) + .map(|index| index == index_url) + }) + .unwrap_or(false); if healthy { return None; } @@ -9011,6 +9070,83 @@ mod tests { assert!(r.confirmed_cargo_uuids.contains(CARGO_UUID)); } + #[test] + fn cargo_dotted_literal_renames_refuse_every_declaration() { + for alias in ["alias", "\"alias\"", "'alias'"] { + for package_key in ["package", "\"package\"", "'package'"] { + let manifest = format!( + "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n\ + [dependencies]\nserde = \"1.0.190\"\n\ + {alias}.{package_key} = 'serde'\n{alias}.version = '1.0.190'\n" + ); + assert!(manifest.parse::().is_ok()); + let result = + rewrite_registry_redirect(&cargo_files(&manifest), &[cargo_sparse_override()]); + assert!(result.files.is_empty(), "{manifest}"); + assert!(result.edits.is_empty(), "{manifest}"); + assert!(result.confirmed_cargo_uuids.is_empty(), "{manifest}"); + assert!(result + .warnings + .iter() + .any(|warning| warning.code == "redirect_cargo_toml_dep_unrewritable")); + } + } + } + + #[test] + fn cargo_literal_renames_pin_inline_and_table_forms() { + for declaration in [ + "[dependencies]\nalias = { package = 'serde', version = '1.0.190' }\n", + "[dependencies.alias]\npackage = 'serde'\nversion = '1.0.190'\n", + "[dependencies]\nalias = { 'package' = 'serde', 'version' = '1.0.190' }\n", + "[dependencies.'alias']\n'package' = 'serde'\n'version' = '1.0.190'\n", + ] { + let manifest = + format!("[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n{declaration}"); + let files = cargo_files(&manifest); + let result = rewrite_registry_redirect(&files, &[cargo_sparse_override()]); + assert!(result.warnings.is_empty(), "{:?}", result.warnings); + assert!(result.confirmed_cargo_uuids.contains(CARGO_UUID)); + let updated = &result.files["Cargo.toml"]; + let document = updated.parse::().unwrap(); + assert_eq!( + document["dependencies"]["alias"]["registry"].as_str(), + Some(cargo_reg().as_str()) + ); + assert_eq!( + cargo_socket_registry_pin(updated, "serde"), + Some(cargo_reg()) + ); + let mut rerun_files = files; + rerun_files.extend(result.files); + let rerun = rewrite_registry_redirect(&rerun_files, &[cargo_sparse_override()]); + assert!(rerun.files.is_empty()); + assert!(rerun.warnings.is_empty(), "{:?}", rerun.warnings); + assert!(rerun.confirmed_cargo_uuids.contains(CARGO_UUID)); + } + } + + #[test] + fn cargo_literal_registry_pins_are_superseded() { + let previous = "socket-patch-11111111-1111-1111-1111-111111111111"; + for declaration in [ + format!("[dependencies]\nalias = {{ package = 'serde', version = '1.0.190', registry = '{previous}' }}\n"), + format!("[dependencies.alias]\npackage = 'serde'\nversion = '1.0.190'\nregistry = '{previous}' # previous pin\n"), + format!("[dependencies.alias]\npackage = 'serde'\nversion = '1.0.190'\n'registry' = '{previous}'\n"), + ] { + let manifest = format!( + "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n{declaration}" + ); + let result = rewrite_registry_redirect(&cargo_files(&manifest), &[cargo_sparse_override()]); + assert!(result.warnings.is_empty(), "{:?}", result.warnings); + let updated = &result.files["Cargo.toml"]; + let document = updated.parse::().unwrap(); + assert_eq!(document["dependencies"]["alias"]["registry"].as_str(), Some(cargo_reg().as_str())); + assert_eq!(cargo_socket_registry_pin(updated, "serde"), Some(cargo_reg())); + assert!(result.confirmed_cargo_uuids.contains(CARGO_UUID)); + } + } + /// AUDIT A5(a) alone: when the ONLY key match renames a different crate, /// the dep is genuinely not declared → not-found, and NOTHING is written /// (no config block, no lock repoint). @@ -9267,6 +9403,54 @@ mod tests { assert!(second.confirmed_cargo_uuids.contains(CARGO_UUID)); } + #[test] + fn cargo_config_quoted_commented_headers_are_reused() { + for header in [ + format!("[registries.{}] # managed registry", cargo_reg()), + format!("[registries.\"{}\"]", cargo_reg()), + format!("['registries'.'{}'] # managed registry", cargo_reg()), + format!("[ \"registries\" . '{}' ]", cargo_reg()), + ] { + for index in [ + format!("index = \"{}\" # current", cargo_index_url()), + format!("index = '{}'", cargo_index_url()), + format!("'index' = '{}' # current", cargo_index_url()), + ] { + let config = format!("{header}\n{index}\n\n[build]\njobs = 4\n"); + assert!(config.parse::().is_ok()); + assert!( + plan_cargo_config( + &config, + ".cargo/config.toml", + &cargo_reg(), + &cargo_index_url() + ) + .is_none(), + "{config}" + ); + } + let config = + format!("{header}\nindex = 'sparse+https://old.example/'\n\n[build]\njobs = 4\n"); + let plan = plan_cargo_config( + &config, + ".cargo/config.toml", + &cargo_reg(), + &cargo_index_url(), + ) + .expect("stale registry repaired"); + let document = plan + .content + .parse::() + .expect("no duplicate tables"); + assert_eq!( + document["registries"][&cargo_reg()]["index"].as_str(), + Some(cargo_index_url().as_str()) + ); + assert_eq!(document["build"]["jobs"].as_integer(), Some(4)); + assert_eq!(plan.edit.action, "rewritten"); + } + } + /// A degraded managed block (header intact, index line commented or /// stale) is regenerated in place rather than trusted. #[test] @@ -9710,6 +9894,96 @@ mod tests { assert!(r.confirmed_cargo_uuids.contains(CARGO_UUID)); } + #[test] + fn cargo_workspace_member_inherits_renamed_dependency() { + for workspace_entry in [ + "[workspace.dependencies]\nserial = { package = \"serde\", version = \"=1.0.190\", features = [\"std\"] }\n", + "[workspace.dependencies.serial]\npackage = \"serde\"\nversion = \"=1.0.190\"\nfeatures = [\"std\"]\n", + ] { + for declaration in [ + "[dependencies]\nserial.workspace = true\n", + "[dependencies]\nserial = { workspace = true, features = [\"derive\"] }\n", + "[dependencies.serial]\nworkspace = true\n", + "[dev-dependencies]\nserial.workspace = true\n", + "[build-dependencies]\nserial = { workspace = true }\n", + "[target.'cfg(unix)'.dependencies.serial]\nworkspace = true\n", + ] { + let mut files = cargo_files(&format!( + "[workspace]\nmembers = [\"consumer\"]\n\n{workspace_entry}" + )); + files.insert( + "consumer/Cargo.toml".into(), + format!( + "[package]\nname = \"consumer\"\nversion = \"0.1.0\"\n\n{declaration}" + ), + ); + files.get_mut("Cargo.lock").unwrap().push_str( + "\n[[package]]\nname = \"consumer\"\nversion = \"0.1.0\"\ndependencies = [\"serde\"]\n", + ); + + let result = rewrite_registry_redirect(&files, &[cargo_sparse_override()]); + assert!(result.warnings.is_empty(), "{:?}", result.warnings); + assert!(result.confirmed_cargo_uuids.contains(CARGO_UUID)); + assert!(result.files["Cargo.toml"] + .contains(&format!("registry = \"{}\"", cargo_reg()))); + assert!(!result.files.contains_key("consumer/Cargo.toml")); + assert!(result.files["Cargo.lock"].contains(&cargo_index_url())); + + files.extend(result.files); + let repeated = rewrite_registry_redirect(&files, &[cargo_sparse_override()]); + assert!(repeated.warnings.is_empty(), "{:?}", repeated.warnings); + assert!(repeated.edits.is_empty() && repeated.files.is_empty()); + assert!(repeated.confirmed_cargo_uuids.contains(CARGO_UUID)); + } + } + } + + #[test] + fn cargo_root_inherits_renamed_dependency_before_workspace_declaration() { + let files = cargo_files( + "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n\ + [dependencies]\nserial.workspace = true\n\n\ + [workspace.dependencies]\nserial = { package = \"serde\", version = \"=1.0.190\" }\n", + ); + let result = rewrite_registry_redirect(&files, &[cargo_sparse_override()]); + assert!(result.warnings.is_empty(), "{:?}", result.warnings); + assert!(result.confirmed_cargo_uuids.contains(CARGO_UUID)); + assert!(result.files["Cargo.toml"].contains("serial.workspace = true")); + assert!(result.files["Cargo.toml"].contains(&format!( + "serial = {{ package = \"serde\", version = \"=1.0.190\", registry = \"{}\" }}", + cargo_reg() + ))); + } + + #[test] + fn cargo_workspace_inherited_key_renaming_another_package_is_ignored() { + for other_entry in [ + "[workspace.dependencies]\nserde = { package = \"unrelated\", version = \"1\" }\n", + "[workspace.dependencies.serde]\npackage = \"unrelated\"\nversion = \"1\"\n", + ] { + let root = format!( + "[workspace]\nmembers = [\"consumer\"]\n\n\ + {other_entry}\n\ + [workspace.dependencies.serial]\npackage = \"serde\"\nversion = \"=1.0.190\"\n" + ); + let mut files = cargo_files(&root); + files.insert( + "consumer/Cargo.toml".into(), + "[package]\nname = \"consumer\"\nversion = \"0.1.0\"\n\n\ + [dependencies]\nserde.workspace = true\nserial.workspace = true\n" + .into(), + ); + files.get_mut("Cargo.lock").unwrap().push_str( + "\n[[package]]\nname = \"consumer\"\nversion = \"0.1.0\"\ndependencies = [\"serde\"]\n", + ); + let result = rewrite_registry_redirect(&files, &[cargo_sparse_override()]); + assert!(result.warnings.is_empty(), "{:?}", result.warnings); + assert!(result.confirmed_cargo_uuids.contains(CARGO_UUID)); + assert!(result.files["Cargo.toml"].contains(other_entry)); + assert!(!result.files.contains_key("consumer/Cargo.toml")); + } + } + /// A member that cannot be pinned (a path dependency here) refuses the /// WHOLE dep: the root stays untouched too. #[test] @@ -15072,6 +15346,111 @@ packages: ); } + #[test] + fn cargo_source_less_dependents_match_both_name_and_version() { + let root = "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n\ + [dependencies]\ninside = { package = \"foo\", path = \"inside\" }\n\ + outside = { package = \"foo\", path = \"../outside\" }\n"; + let mut files = cargo_files(root); + files.insert( + "inside/Cargo.toml".into(), + "[package]\nname = \"foo\"\nversion = \"0.1.0\"\n\n\ + [dependencies]\nserde = \"1.0.190\"\n" + .into(), + ); + files.get_mut("Cargo.lock").unwrap().push_str( + "\n[[package]]\nname = \"app\"\nversion = \"0.1.0\"\n\ + dependencies = [\"foo 0.1.0\", \"foo 0.2.0\"]\n\n\ + [[package]]\nname = \"foo\"\nversion = \"0.1.0\"\ndependencies = [\"serde\"]\n\n\ + [[package]]\nname = \"foo\"\nversion = \"0.2.0\"\ndependencies = [\"serde\"]\n", + ); + let refused = rewrite_registry_redirect(&files, &[cargo_sparse_override()]); + assert!(refused.files.is_empty() && refused.edits.is_empty()); + assert!(refused.confirmed_cargo_uuids.is_empty()); + assert_eq!( + warning_codes(&refused), + vec!["redirect_cargo_transitive_dependents"] + ); + assert!(refused.warnings[0] + .detail + .contains("foo 0.2.0 (a path package")); + + files.insert("Cargo.toml".into(), root.replace("../outside", "outside")); + files.insert( + "outside/Cargo.toml".into(), + "[package]\nname = \"foo\"\nversion = \"0.2.0\"\n\n\ + [dependencies]\nserde = \"1.0.190\"\n" + .into(), + ); + let accepted = rewrite_registry_redirect(&files, &[cargo_sparse_override()]); + assert!(accepted.warnings.is_empty(), "{:?}", accepted.warnings); + assert!(accepted.confirmed_cargo_uuids.contains(CARGO_UUID)); + assert!(accepted.files.contains_key("inside/Cargo.toml")); + assert!(accepted.files.contains_key("outside/Cargo.toml")); + } + + #[test] + fn cargo_source_less_dependents_resolve_workspace_and_default_versions() { + for (version_field, locked_version) in [ + ("version.workspace = true\n", "0.2.0"), + ("version = { workspace = true }\n", "0.2.0"), + ("version.workspace = true\nworkspace = \"..\"\n", "0.2.0"), + ("", "0.0.0"), + ] { + let mut files = cargo_files( + "[workspace]\nmembers = [\"consumer\"]\n\n\ + [workspace.package]\nversion = \"0.2.0\"\n", + ); + files.insert( + "consumer/Cargo.toml".into(), + format!( + "[package]\nname = \"consumer\"\n{version_field}\n\ + [dependencies]\nserde = \"1.0.190\"\n" + ), + ); + files.get_mut("Cargo.lock").unwrap().push_str(&format!( + "\n[[package]]\nname = \"consumer\"\nversion = \"{locked_version}\"\n\ + dependencies = [\"serde\"]\n" + )); + let result = rewrite_registry_redirect(&files, &[cargo_sparse_override()]); + assert!( + result.warnings.is_empty(), + "{version_field}: {:?}", + result.warnings + ); + assert!(result.confirmed_cargo_uuids.contains(CARGO_UUID)); + } + } + + #[test] + fn cargo_package_identity_keeps_workspace_version_ownership() { + let manifest = "[package]\nname = \"consumer\"\nversion.workspace = true\n"; + assert_eq!( + cargo_manifest_package_id( + &format!("{manifest}\n[workspace.package]\nversion = \"0.3.0\"\n"), + "consumer/Cargo.toml", + Some("0.2.0"), + ), + Some(("consumer".into(), "0.3.0".into())), + ); + assert_eq!( + cargo_manifest_package_id( + &format!("{manifest}\n[workspace]\n"), + "consumer/Cargo.toml", + Some("0.2.0"), + ), + None, + ); + assert_eq!( + cargo_manifest_package_id( + &format!("{manifest}workspace = \"../../other\"\n"), + "consumer/Cargo.toml", + Some("0.2.0"), + ), + None, + ); + } + /// A checksum-less entry whose `source` line ends the block (the /// trailing newline sits outside the block region) still gets its pin. #[test] From a156d87f4e3246df9422bc930f622906d122203a Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Fri, 25 Sep 2026 14:46:41 -0400 Subject: [PATCH 02/46] Refuse incomplete Cargo manifest redirects Validate planned dependency pins against parsed TOML before writing any files. Legal root and target dotted or inline declarations that the source-preserving editor cannot rewrite now refuse the entire patch instead of leaving a mixture of original and patched sources. Validate unchanged member manifests too, while preserving workspace inheritance and declarations for other package versions. Malformed TOML is refused without changing the project. Validated with 73 Cargo unit tests and the real converter/API/CLI matrix, including an actual Cargo build proving the dotted manifest is valid and remains byte-identical after refusal. Assisted-by: Codex:gpt-6-astra --- .../src/patch/redirect/mod.rs | 240 +++++++++++++++--- 1 file changed, 198 insertions(+), 42 deletions(-) diff --git a/crates/socket-patch-core/src/patch/redirect/mod.rs b/crates/socket-patch-core/src/patch/redirect/mod.rs index b22fe03e..f15515b7 100644 --- a/crates/socket-patch-core/src/patch/redirect/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/mod.rs @@ -894,6 +894,18 @@ fn rewrite_cargo( &root_workspace, ) { Ok(plan) => { + if let Err(reason) = validate_cargo_toml_pins( + &plan.content, + &dep.name, + &dep.version, + &other_versions, + ®, + &plan.workspace, + &root_workspace, + ) { + refused = Some((path.clone(), reason)); + break; + } if path == "Cargo.toml" { root_workspace = plan.workspace.clone(); } @@ -1740,6 +1752,96 @@ fn cargo_toml_inline_string(inner: &str, key: &str) -> Option { .map(str::to_string) } +fn validate_cargo_toml_pins( + content: &str, + crate_name: &str, + version: &str, + other_versions: &[String], + registry: &str, + workspace: &BTreeMap, + inherited: &BTreeMap, +) -> Result<(), String> { + let document = content + .parse::() + .map_err(|_| "the planned manifest does not parse as TOML".to_string())?; + let unpinned = |dependencies: &dyn toml_edit::TableLike| { + dependencies.iter().find_map(|(key, entry)| { + let table = entry.as_table_like(); + let field = |name: &str| table.and_then(|table| table.get(name)); + if field("workspace").and_then(toml_edit::Item::as_bool) == Some(true) { + return match workspace.get(key).or(inherited.get(key)) { + Some( + CargoWorkspaceEntry::Pinned + | CargoWorkspaceEntry::OtherVersion + | CargoWorkspaceEntry::OtherPackage, + ) => None, + None if key != crate_name => None, + None => Some(key.to_string()), + }; + } + let name = field("package") + .and_then(toml_edit::Item::as_str) + .unwrap_or(key); + if name != crate_name { + return None; + } + let requirement = entry + .as_str() + .or_else(|| field("version").and_then(toml_edit::Item::as_str)); + match cargo_req_selects(requirement, version, other_versions) { + CargoReqMatch::NotOurs => return None, + CargoReqMatch::Ambiguous => return Some(key.to_string()), + CargoReqMatch::Ours => {} + } + let is_pinned = field("registry").and_then(toml_edit::Item::as_str) == Some(registry) + && field("path").is_none() + && field("git").is_none() + && field("registry-index").is_none(); + (!is_pinned).then(|| key.to_string()) + }) + }; + let mut scopes: Vec<&dyn toml_edit::TableLike> = vec![document.as_table()]; + if let Some(targets) = document + .get("target") + .and_then(toml_edit::Item::as_table_like) + { + scopes.extend( + targets + .iter() + .filter_map(|(_, target)| target.as_table_like()), + ); + } + for scope in scopes { + for kind in [ + "dependencies", + "dev-dependencies", + "build-dependencies", + "dev_dependencies", + "build_dependencies", + ] { + if let Some(key) = scope + .get(kind) + .and_then(toml_edit::Item::as_table_like) + .and_then(&unpinned) + { + return Err(format!("dependency declaration {key} was not pinned")); + } + } + } + if let Some(key) = document + .get("workspace") + .and_then(toml_edit::Item::as_table_like) + .and_then(|workspace| workspace.get("dependencies")) + .and_then(toml_edit::Item::as_table_like) + .and_then(unpinned) + { + return Err(format!( + "workspace dependency declaration {key} was not pinned" + )); + } + Ok(()) +} + struct CargoTomlPlan { content: String, edits: Vec, @@ -9070,6 +9172,86 @@ mod tests { assert!(r.confirmed_cargo_uuids.contains(CARGO_UUID)); } + #[test] + fn cargo_root_dependency_forms_cannot_leave_partial_redirect() { + for dependency in [ + "dependencies.serde = \"1.0.190\"", + "dependencies = { serde = \"1.0.190\" }", + "dependencies = { serde = { version = \"1.0.190\" } }", + "target.'cfg(unix)'.dependencies.serde = \"1.0.190\"", + "workspace.dependencies.serde = \"1.0.190\"", + "workspace = { dependencies = { serde = \"1.0.190\" } }", + ] { + let manifest = format!( + "{dependency}\n\n[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n\ + [dev-dependencies]\nserde = \"1.0.190\"\n" + ); + assert!(manifest.parse::().is_ok()); + let result = + rewrite_registry_redirect(&cargo_files(&manifest), &[cargo_sparse_override()]); + assert!(result.files.is_empty(), "{manifest}: {:?}", result.files); + assert!(result.edits.is_empty(), "{manifest}"); + assert!(result.confirmed_cargo_uuids.is_empty(), "{manifest}"); + assert!(result + .warnings + .iter() + .any(|warning| warning.code == "redirect_cargo_toml_dep_unrewritable")); + } + } + + #[test] + fn cargo_semantic_pin_guard_preserves_other_version_declarations() { + let manifest = "dependencies.serde = \"0.9\"\n\n\ + [package]\nname = \"app\"\nversion = \"0.1.0\"\n\n\ + [dev-dependencies]\nserde = \"1.0.190\"\n"; + let mut files = cargo_files(manifest); + files.get_mut("Cargo.lock").unwrap().push_str(&format!( + "\n[[package]]\nname = \"serde\"\nversion = \"0.9.15\"\n\ + source = \"registry+https://github.com/rust-lang/crates.io-index\"\n\ + checksum = \"{}\"\n", + "a".repeat(64) + )); + let result = rewrite_registry_redirect(&files, &[cargo_sparse_override()]); + assert!(result.warnings.is_empty(), "{:?}", result.warnings); + assert!(result.confirmed_cargo_uuids.contains(CARGO_UUID)); + let document = result.files["Cargo.toml"] + .parse::() + .unwrap(); + assert_eq!(document["dependencies"]["serde"].as_str(), Some("0.9")); + assert_eq!( + document["dev-dependencies"]["serde"]["registry"].as_str(), + Some(cargo_reg().as_str()) + ); + } + + #[test] + fn cargo_semantic_pin_guard_checks_unchanged_members() { + let mut files = cargo_files( + "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n\ + [workspace]\nmembers = [\"member\"]\n\n\ + [dependencies]\nserde = \"1.0.190\"\n", + ); + files.insert( + "member/Cargo.toml".to_string(), + "dependencies = { serde = \"1.0.190\" }\n\n\ + [package]\nname = \"member\"\nversion = \"0.1.0\"\n" + .to_string(), + ); + files.get_mut("Cargo.lock").unwrap().push_str( + "\n[[package]]\nname = \"member\"\nversion = \"0.1.0\"\n\ + dependencies = [\"serde\"]\n", + ); + let result = rewrite_registry_redirect(&files, &[cargo_sparse_override()]); + assert!(result.files.is_empty()); + assert!(result.edits.is_empty()); + assert!(result.confirmed_cargo_uuids.is_empty()); + assert!(result.warnings.iter().any(|warning| { + warning.code == "redirect_cargo_toml_dep_unrewritable" + && warning.detail.contains("member/Cargo.toml") + && warning.detail.contains("was not pinned") + })); + } + #[test] fn cargo_dotted_literal_renames_refuse_every_declaration() { for alias in ["alias", "\"alias\"", "'alias'"] { @@ -16535,13 +16717,8 @@ packages: // tolerance legs, workspace-inheritance satisfaction, and the remaining // diagnosis spellings. - /// Malformed Cargo.toml section headers (unbalanced quote in a segment, - /// an unclosed `[dependencies`) must classify as non-dependency sections - /// — their entries stay byte-identical — and garbage lines inside the - /// real [dependencies] table are skipped while the real entry still - /// gains the pin. #[test] - fn cargo_malformed_headers_and_table_lines_are_skipped_not_fatal() { + fn cargo_malformed_manifest_headers_and_lines_refuse_redirect() { let files = cargo_files( "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n\ [target.'cfg(unix).dependencies]\nserde = \"9.9.9\"\n\n\ @@ -16549,50 +16726,29 @@ packages: [dependencies]\n= \"junk\"\njunk\nserde = \"1.0.190\"\n", ); let r = rewrite_registry_redirect(&files, &[cargo_sparse_override()]); - assert!( - r.warnings.is_empty(), - "garbage headers/lines are skipped, not refused: {:?}", - r.warnings - ); - let toml = r.files.get("Cargo.toml").expect("Cargo.toml rewritten"); - let pinned = format!( - "serde = {{ version = \"1.0.190\", registry = \"{}\" }}", - cargo_reg() - ); - assert_eq!( - toml.matches(&pinned).count(), - 1, - "only the real [dependencies] entry is pinned: {toml}" - ); - assert!( - toml.contains("serde = \"9.9.9\"") && toml.contains("serde = \"8.8.8\""), - "entries under malformed headers stay byte-identical: {toml}" - ); - assert!( - toml.contains("= \"junk\"\njunk\n"), - "garbage table lines survive untouched: {toml}" - ); + assert!(r.files.is_empty()); + assert!(r.edits.is_empty()); + assert!(r.confirmed_cargo_uuids.is_empty()); + assert!(r.warnings.iter().any(|warning| { + warning.code == "redirect_cargo_toml_dep_unrewritable" + && warning.detail.contains("does not parse as TOML") + })); } - /// Unparseable lines INSIDE a `[dependencies.]` table block (a bare - /// `= …`, a key token with no `=`) are skipped by the block scanner while - /// the block still gains its `registry` pin right after the header. #[test] - fn cargo_dep_entry_block_garbage_lines_are_skipped() { + fn cargo_malformed_dep_entry_block_refuses_redirect() { let files = cargo_files( "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n\ [dependencies.serde]\n= \"zap\"\npackage \"serde\"\nversion = \"1.0.190\"\n", ); let r = rewrite_registry_redirect(&files, &[cargo_sparse_override()]); - assert!(r.warnings.is_empty(), "{:?}", r.warnings); - let toml = r.files.get("Cargo.toml").expect("Cargo.toml rewritten"); - assert!( - toml.contains(&format!( - "[dependencies.serde]\nregistry = \"{}\"\n= \"zap\"\npackage \"serde\"\nversion = \"1.0.190\"", - cargo_reg() - )), - "registry pin inserted after the header, garbage lines untouched: {toml}" - ); + assert!(r.files.is_empty()); + assert!(r.edits.is_empty()); + assert!(r.confirmed_cargo_uuids.is_empty()); + assert!(r.warnings.iter().any(|warning| { + warning.code == "redirect_cargo_toml_dep_unrewritable" + && warning.detail.contains("does not parse as TOML") + })); } /// A `[workspace.dependencies]` entry ALREADY pinned to the managed From 5b42bb173d41b9cc657bfb7523e5abc26735d5fa Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Fri, 25 Sep 2026 15:19:01 -0400 Subject: [PATCH 03/46] Keep Cargo refusal checks lint-clean Combine identical refusal branches for dotted dependencies without changing which declarations are refused. Full workspace Clippy and all 73 Cargo redirect tests pass. Assisted-by: Codex:gpt-6-astra --- .../socket-patch-core/src/patch/redirect/mod.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/crates/socket-patch-core/src/patch/redirect/mod.rs b/crates/socket-patch-core/src/patch/redirect/mod.rs index f15515b7..ccfcadac 100644 --- a/crates/socket-patch-core/src/patch/redirect/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/mod.rs @@ -2151,16 +2151,13 @@ fn plan_cargo_toml( let sub = parse_cargo_entry_key(dotted); if sub.as_ref().is_some_and(|(key, _)| key == "workspace") { pending.push(Pending::NeedsWorkspacePin(key.clone())); - } else if key == crate_name { - pending.push(Pending::Refuse( - "declared with dotted keys this rewriter does not edit".to_string(), - )); - } else if sub - .filter(|(key, _)| key == "package") - .and_then(|(_, rest)| rest.trim_start().strip_prefix('=')) - .and_then(cargo_toml_string) - .as_deref() - == Some(crate_name) + } else if key == crate_name + || sub + .filter(|(key, _)| key == "package") + .and_then(|(_, rest)| rest.trim_start().strip_prefix('=')) + .and_then(cargo_toml_string) + .as_deref() + == Some(crate_name) { pending.push(Pending::Refuse( "declared with dotted keys this rewriter does not edit".to_string(), From 4c1f822ca03dde105bdd3d0071e1c90c94ec7301 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Fri, 25 Sep 2026 19:29:19 -0400 Subject: [PATCH 04/46] Fail closed on ledger kinds this release lacks A redirect ledger written by a newer socket-patch (for example a vlt lock edit) could be half-reverted: rollback dropped the npm record and left the lockfile it did not understand still pointing at the hosted artifact, with nothing tracking it. Now an unknown hosted edit kind holds every record in the ledger, and rollback of one package, remove and the hosted-to-vendored takeover refuse with nothing written when that edit names the package. repair skips vendored npm entries whose flavor it does not know instead of judging or rebuilding them with the wrong layout rules. The contract and changelog say vlt ledgers need the release that adds vlt support. Assisted-by: Claude Code:claude-opus-5-5 --- CHANGELOG.md | 14 ++ crates/socket-patch-cli/CLI_CONTRACT.md | 5 +- .../src/commands/repair_vendor.rs | 16 +++ .../tests/in_process_rollback_hosted.rs | 117 +++++++++++++++++ .../tests/repair_vendor_flavors_e2e.rs | 46 +++++++ .../src/patch/redirect/replay.rs | 102 +++++++++++++-- .../src/patch/redirect/takeover.rs | 122 ++++++++++++++++++ .../src/vendor/npm_flavor.rs | 32 +++++ crates/socket-patch-core/src/vendor/verify.rs | 36 ++++++ 9 files changed, 477 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21874c7e..f42adb9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -627,6 +627,20 @@ into the new version's section — see docs/releasing.md. ### Fixed +- **Ledgers written by a newer socket-patch are never half-reverted.** + A hosted redirect edit kind this release does not understand used to + let `rollback` drop the npm record beside it, leaving that lockfile + redirected with nothing tracking it. Such an edit now holds every + record in the redirect ledger, and `rollback `, `remove` and the + hosted-to-vendored takeover refuse, with nothing written, when it + names the purl ("the redirect ledger holds a {kind} edit this + socket-patch release does not understand; upgrade socket-patch"). + `repair` skips vendored npm entries whose `flavor` it does not know + (`vendor_wiring_unknown_revert_blocked`) instead of rebuilding them + with the wrong layout rules. vlt ledgers (`redirect_vlt_lock_node`, + `flavor: "vlt"`) require the socket-patch release that adds vlt + support. + - **Hosted Go redirects no longer claim patches that did not land.** `scan`/`get --mode hosted` counted a Go module as redirected (recorded it in the redirect ledger, so `vex` attested it) whenever any project diff --git a/crates/socket-patch-cli/CLI_CONTRACT.md b/crates/socket-patch-cli/CLI_CONTRACT.md index f6991b70..0583b606 100644 --- a/crates/socket-patch-cli/CLI_CONTRACT.md +++ b/crates/socket-patch-cli/CLI_CONTRACT.md @@ -923,7 +923,8 @@ Restore the system but keep the local patch state for a later re-apply: manifest * **Per-purl reverts** exist for **cargo, golang and the npm family** (`redirect_revert_supported`): staged, fail-closed on drift, and honoring `dry_run` (every inverse and drift check resolves like a wet run; nothing flushes and the ledger is untouched). npm purls on projects with bun-lock edits DEFER to the whole-ledger replay (below) whenever it will run — the scope covers every record, and the replay stages the bun group all-or-nothing. A SCOPED unwind (`rollback `, or `remove ` while other hosted records remain) takes the per-purl revert instead: it claims that purl's `redirect_bun_lock_package` edits by the recorded line's spec (`@` registry spec, or a hosted URL whose tarball leaf is `-.tgz`) and replays them like the yarn/pnpm text kinds (whole-line fragments, CRLF-exact); a sibling version's edit is neither claimed nor a refusal; an edit that mentions the package but is not a bun packages-entry line refuses with the unscoped-`rollback` remedy. Pinned by `tests/in_process_vendor_bun_takeover.rs` (`bun_scoped_rollback_of_one_of_two_hosted_records_unwinds_only_that_purl` and the `remove` twin). Native binary `redirect_bun_lockb_package` snapshots follow the same scoped ownership rule and restore only the claimed package records; unrelated binary resolutions stay intact. yarn lock blocks (`redirect_yarn_berry_entry` / `redirect_yarn_classic_entry`) are recorded in the lock's on-disk line endings and replayed byte-exactly; when a `core.autocrlf` checkout has since flipped the lock's UNIFORM ending (LF ↔ CRLF — the committed ledger keeps its fragments verbatim), this per-purl revert and the whole-ledger replay below match the recorded blocks respelled in the live ending and restore in that ending (v5.0). A lock with mixed endings proves nothing and still refuses as drift. * **Whole-ledger reverse replay** (`revert_remaining_redirect_edits`, core `patch/redirect/replay.rs`) runs whenever the in-scope hosted record set equals the FULL ledger record set — however the scope was spelled (bare `rollback`, `rollback '**'`, an identifier set covering every record; `remove` reuses the same eligibility rule). It walks every remaining ledger edit in reverse write order through a **per-kind inverse table**, staged and committed **per ecosystem group, all-or-nothing**: one drifted, ambiguous (a fragment appearing more than once), or unhandled edit refuses the whole group byte-untouched while other groups proceed. This covers **gem, golang, pypi, composer, bun**, the yarn/pnpm text kinds (normally claimed by the per-purl npm revert first), and the **non-package rideshare edits** — the pnpm `trustLockfile` auto-config (a pristine created scaffold is deleted; a user-modified one keeps the file and loses only the `trustLockfile: true` line, warned as `redirect_pnpm_trust_scaffold_modified`) — plus a "last one out turns off the lights" pass: when the record map empties but non-package edits remain, they are replayed in the same persist, so the trust edit never strands. The npm `.npmrc` `allow-remote=all` auto-config (`redirect_npmrc_allow_remote`) replays in the `npm` group (a pristine created file is deleted; otherwise only the line is removed, warned as `redirect_npmrc_allow_remote_modified` for a modified created file) and is ALSO claimed by the per-purl npm revert of the last package-lock entry, so a scoped unwind never strands it. -* **maven and nuget fail closed**: their structured-metadata kinds (`redirect_maven_repository` / `redirect_maven_dep_management` / `redirect_maven_config` / `redirect_maven_trusted_checksums`, `redirect_nuget_source` / `redirect_nuget_lock`) have no revert implementation, so any such edit refuses its whole group (the maven `` suffix rewrite alone IS invertible, but it rides the same all-or-nothing group). The refusal keeps their records + edits in the ledger and names the remedy: re-run `scan --mode hosted` to normalize, or restore the lockfiles from version control. Unknown future kinds refuse the same way (forward-compat). +* **maven and nuget fail closed**: their structured-metadata kinds (`redirect_maven_repository` / `redirect_maven_dep_management` / `redirect_maven_config` / `redirect_maven_trusted_checksums`, `redirect_nuget_source` / `redirect_nuget_lock`) have no revert implementation, so any such edit refuses its whole group (the maven `` suffix rewrite alone IS invertible, but it rides the same all-or-nothing group). The refusal keeps their records + edits in the ledger and names the remedy: re-run `scan --mode hosted` to normalize, or restore the lockfiles from version control. +* **Unknown edit kinds fail closed (forward compatibility).** A ledger edit kind this release has no inverse for (written by a newer socket-patch) refuses in the replay's reserved `unknown` group, and every record of every ecosystem is held while that group refuses, so no record is dropped beside an edit it may own. A per-purl revert (`rollback `, `remove`, the hosted→vendored takeover) refuses with nothing written when any unknown `redirect_*` edit's `key`, `original` or `new` contains the purl's `@`: "the redirect ledger holds a {kind} edit this socket-patch release does not understand; upgrade socket-patch". vlt ledgers (`redirect_vlt_lock_node` edits, vendored entries with `flavor: "vlt"`) require the socket-patch release that adds vlt support. The ledger `version` stays 1: compatibility is decided per kind. * **Scoped runs** (paths / identifiers / `--ecosystems`) that do NOT cover the full record set get per-purl reverts only; in-scope hosted purls of ecosystems without one fail closed — `rollback` reports them in `hosted.unsupported` (exit 1), `remove` as the top-level `hosted_revert_unsupported` error — with the remedy "run an unscoped `socket-patch rollback` to unwind ALL hosted redirects, or re-run `scan --mode hosted`". * **Ledger accounting**: exactly the replayed (or already-at-original) edits are dropped; a record is dropped only when every group its ecosystem writes ended clean, so refused groups keep both edits and records — the intermediate-but-coherent ledger a retry needs. The mutated ledger is persisted (delete-when-empty); a failed persist rides `hosted.failed` / `hosted_revert_failed`. @@ -1221,7 +1222,7 @@ Every `--json` invocation emits a single JSON object that follows the **unified | `already_vendored` | `skipped` | vendor: artifact + wiring already in sync for this patch uuid. | | `unsafe_coordinates` | `failed` | vendor: purl/uuid would escape `.socket/vendor/` (tampered manifest/state); refused before any write. | | `revert_failed` | `failed` | vendor --revert: a recorded entry could not be reverted. | -| `vendor_wiring_unknown_revert_blocked` | `skipped` (beside the `failed`/`revert_failed` event) | vendor --revert: the ledger entry was reconstructed by `repair` without wiring records and the live lockfile still resolves through the artifact — the revert refuses (fail-closed) instead of deleting a tarball the lock points at. Recovery: `socket-patch repair`, then restore the pre-vendor lock (or re-lock without the override) and re-run the revert. | +| `vendor_wiring_unknown_revert_blocked` | `skipped` (beside the `failed`/`revert_failed` event) | vendor --revert: the ledger entry was reconstructed by `repair` without wiring records and the live lockfile still resolves through the artifact — the revert refuses (fail-closed) instead of deleting a tarball the lock points at. Recovery: `socket-patch repair`, then restore the pre-vendor lock (or re-lock without the override) and re-run the revert. repair: an npm ledger entry whose `flavor` this release does not know (written by a newer socket-patch) is skipped, never health-checked or rebuilt, and the artifact, wiring and ledger stay as found (a lone `skipped` event; the run's exit is unaffected). Recovery: upgrade socket-patch. | | `ecosystem_not_setup` | `skipped` | vex: the patch is applied and byte-verified but its ecosystem has no install hook configured and is not declared in the manifest's `setup.manual`, so it is omitted from the document (Property 7). Previously invisible in `--json`. | | `stale_install` | `skipped` | vex (in-run `scan --mode hosted --vex`): a hosted stale-install probe found positively unpatched installed bytes, so the purl is omitted even under `--vex-no-verify` (see the gem / Python stale-install guards). | | `record_unavailable` | `skipped` | vex (manifest-less): a lockfile-wired patch has no local record (manifest, redirect ledger, vendor ledger) and none could be fetched — `--offline`, transport error, 404, or a refused (paid) patch. Omitted, never attested from the `socket-patch.vendor.json` marker. | diff --git a/crates/socket-patch-cli/src/commands/repair_vendor.rs b/crates/socket-patch-cli/src/commands/repair_vendor.rs index c283c8d3..8d03076c 100644 --- a/crates/socket-patch-cli/src/commands/repair_vendor.rs +++ b/crates/socket-patch-cli/src/commands/repair_vendor.rs @@ -876,6 +876,22 @@ pub(crate) async fn repair_vendored_artifacts_with_references( format!("the ledger entry cannot be verified ({reason}); fix state.json"), ); } + ArtifactHealth::UnknownFlavor { flavor } => { + record_warning( + env, + purl, + &VendorWarning::new( + "vendor_wiring_unknown_revert_blocked", + format!( + "{} was vendored for the npm flavor `{flavor}`, which this \ + socket-patch release does not understand; left untouched — \ + upgrade socket-patch", + normalize_purl(purl) + ), + ), + common, + ); + } health @ (ArtifactHealth::Missing | ArtifactHealth::Corrupt { .. }) => { let reason = if matches!(health, ArtifactHealth::Missing) { "vendor_artifact_missing" diff --git a/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs b/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs index dcb3f171..e9b3d3ca 100644 --- a/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs +++ b/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs @@ -853,6 +853,123 @@ async fn scoped_unsupported_ecosystem_fails_closed() { ); } +/// A ledger written by a newer socket-patch carries a hosted edit kind this +/// release has no revert for (`redirect_vlt_lock_node`). A scoped rollback +/// of the purl it names must refuse with nothing written, and an unscoped +/// one must keep the record while that edit survives. +async fn write_vlt_ledger_fixture(root: &Path) -> String { + let vlt_new = format!( + "\"~npm~left-pad@1.2.3\": [0,\"left-pad\",\"sha512-PATCHEDpatched==\",\"{LP_HOSTED_URL}\"]" + ); + let vlt_lock = format!( + "{{\n \"lockfileVersion\": 1,\n \"options\": {{}},\n \"nodes\": {{\n {vlt_new}\n }},\n \"edges\": {{\n \"file~_d left-pad\": \"prod 1.2.3 ~npm~left-pad@1.2.3\"\n }}\n}}\n" + ); + std::fs::write(root.join("vlt-lock.json"), &vlt_lock).unwrap(); + std::fs::write( + root.join("yarn.lock"), + yarn_lock_content(&yarn_redirected_block()), + ) + .unwrap(); + std::fs::write( + root.join("Gemfile.lock"), + gemfile_lock_content(GEM_PATCH_REMOTE), + ) + .unwrap(); + write_hosted_ledger( + root, + vec![ + (LP_PURL, patch_record(LP_UUID, "GHSA-lpad-aaaa-bbbb")), + (GEM_PURL, patch_record(GEM_UUID, "GHSA-gems-cccc-dddd")), + ], + vec![ + yarn_classic_edit(), + gem_source_edit(), + FileEdit { + path: "vlt-lock.json".to_string(), + kind: "redirect_vlt_lock_node".to_string(), + action: "rewritten".to_string(), + key: Some("left-pad@1.2.3".to_string()), + original: Some(Value::String( + "\"~npm~left-pad@1.2.3\": [0,\"left-pad\",\"sha512-UPSTREAMupstream==\"]" + .to_string(), + )), + new: Some(Value::String(vlt_new)), + }, + ], + ) + .await; + vlt_lock +} + +#[tokio::test] +#[serial] +async fn scoped_rollback_refuses_a_purl_named_by_an_unknown_edit_kind() { + let tmp = tempfile::tempdir().unwrap(); + let vlt_lock = write_vlt_ledger_fixture(tmp.path()).await; + let ledger_before = std::fs::read(ledger_path(tmp.path())).unwrap(); + let yarn_before = std::fs::read_to_string(tmp.path().join("yarn.lock")).unwrap(); + + let (code, envelope) = run_rollback_subprocess(tmp.path(), &[LP_PURL]); + assert_eq!(code, 1, "{envelope}"); + assert_eq!(envelope["status"], "partial_failure", "{envelope}"); + assert_eq!( + envelope["hosted"]["reverted"], + serde_json::json!([]), + "{envelope}" + ); + let failed = envelope["hosted"]["failed"].as_array().unwrap(); + assert_eq!(failed.len(), 1, "{envelope}"); + assert_eq!(failed[0]["purl"], LP_PURL); + assert!( + failed[0]["error"] + .as_str() + .unwrap() + .contains("redirect_vlt_lock_node edit this socket-patch release does not understand"), + "{envelope}" + ); + assert_eq!( + std::fs::read(ledger_path(tmp.path())).unwrap(), + ledger_before + ); + assert_eq!( + std::fs::read_to_string(tmp.path().join("yarn.lock")).unwrap(), + yarn_before + ); + assert_eq!( + std::fs::read_to_string(tmp.path().join("vlt-lock.json")).unwrap(), + vlt_lock + ); +} + +#[tokio::test] +#[serial] +async fn unscoped_rollback_holds_the_record_beside_an_unknown_edit_kind() { + let tmp = tempfile::tempdir().unwrap(); + let vlt_lock = write_vlt_ledger_fixture(tmp.path()).await; + + let code = rollback_in_process(tmp.path(), Vec::new(), false).await; + assert_eq!( + code, 1, + "an unknown edit kind must fail the rollback closed" + ); + assert_eq!( + std::fs::read_to_string(tmp.path().join("vlt-lock.json")).unwrap(), + vlt_lock + ); + let ledger: Value = + serde_json::from_slice(&std::fs::read(ledger_path(tmp.path())).unwrap()).unwrap(); + assert!(ledger["records"].get(LP_PURL).is_some(), "{ledger}"); + assert!(ledger["records"].get(GEM_PURL).is_some(), "{ledger}"); + assert!( + ledger["edits"] + .as_array() + .unwrap() + .iter() + .any(|e| e["kind"] == "redirect_vlt_lock_node"), + "{ledger}" + ); +} + // --------------------------------------------------------------------------- // 4. unscoped rollback replays the unsupported ecosystems // --------------------------------------------------------------------------- diff --git a/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e.rs b/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e.rs index 5e028045..c934abd4 100644 --- a/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e.rs +++ b/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e.rs @@ -601,6 +601,52 @@ async fn repair_rebuilds_corrupt_bun_tarball() { } } +/// An entry stamped with a flavor this release has no backend for (written +/// by a newer socket-patch, e.g. `vlt`) is never judged or rebuilt: repair +/// warns `vendor_wiring_unknown_revert_blocked` and leaves the ledger, the +/// lock and the (here deleted) artifact exactly as found. +#[tokio::test] +async fn repair_skips_an_entry_with_an_unknown_flavor() { + let flavor = Flavor::Pnpm; + let mock = MockServer::start().await; + mount_patch_api(&mock).await; + let tmp = tempfile::tempdir().unwrap(); + write_fixture(tmp.path(), flavor); + let tgz = vendor_project(tmp.path(), &mock.uri(), flavor); + std::fs::remove_file(&tgz).unwrap(); + + let state_path = tmp.path().join(".socket/vendor/state.json"); + let mut v: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(&state_path).unwrap()).unwrap(); + v["entries"][PURL]["flavor"] = serde_json::json!("vlt"); + std::fs::write(&state_path, serde_json::to_vec_pretty(&v).unwrap()).unwrap(); + let state_before = std::fs::read(&state_path).unwrap(); + let lock_before = std::fs::read(tmp.path().join(flavor.lock_name())).unwrap(); + + let (code, stdout, stderr) = run_cli(tmp.path(), &mock.uri(), &["repair"]); + assert_eq!(code, 0, "stdout={stdout} stderr={stderr}"); + let env = parse_env(&stdout); + assert!( + !events_of(&env).iter().any(|e| e["action"] == "rebuilt"), + "{env}" + ); + assert!( + events_of(&env).iter().any(|e| e["action"] == "skipped" + && e["purl"] == PURL + && e["errorCode"] == "vendor_wiring_unknown_revert_blocked"), + "{env}" + ); + assert!( + !tgz.exists(), + "an unknown-flavor artifact must not be rebuilt" + ); + assert_eq!(std::fs::read(&state_path).unwrap(), state_before); + assert_eq!( + std::fs::read(tmp.path().join(flavor.lock_name())).unwrap(), + lock_before + ); +} + // ── (c) tampered ledger sha → fail-closed ────────────────────────────────── async fn tampered_ledger_fails_closed(flavor: Flavor) { diff --git a/crates/socket-patch-core/src/patch/redirect/replay.rs b/crates/socket-patch-core/src/patch/redirect/replay.rs index 324aa583..575a41d6 100644 --- a/crates/socket-patch-core/src/patch/redirect/replay.rs +++ b/crates/socket-patch-core/src/patch/redirect/replay.rs @@ -178,29 +178,36 @@ fn classify(kind: &str, action: &str) -> (&'static str, Inverse) { } } +/// Is `kind` a hosted-redirect edit this release has no replay arm for +/// (a newer socket-patch's writer)? +pub(super) fn is_unclassified_kind(kind: &str, action: &str) -> bool { + classify(kind, action).0 == "unknown" +} + /// The replay groups a record's ecosystem can have written edits into — /// the drop rule holds a record while ANY of its groups refused. npm -/// purls fan across every npm-family lock flavor. +/// purls fan across every npm-family lock flavor. Every ecosystem also +/// lists the reserved "unknown" group: an edit kind this release cannot +/// classify may belong to any record (a newer release's writer for a new +/// lock flavor), so dropping a record beside one would strand that edit. fn groups_for_record_purl(purl: &str) -> &'static [&'static str] { if purl.starts_with("pkg:npm/") { - &["npm", "yarn", "pnpm", "bun"] + &["npm", "yarn", "pnpm", "bun", "unknown"] } else if purl.starts_with("pkg:cargo/") { - &["cargo"] + &["cargo", "unknown"] } else if purl.starts_with("pkg:gem/") { - &["gem"] + &["gem", "unknown"] } else if purl.starts_with("pkg:pypi/") { - &["pypi"] + &["pypi", "unknown"] } else if purl.starts_with("pkg:composer/") { - &["composer"] + &["composer", "unknown"] } else if purl.starts_with("pkg:golang/") { - &["golang"] + &["golang", "unknown"] } else if purl.starts_with("pkg:maven/") { - &["maven"] + &["maven", "unknown"] } else if purl.starts_with("pkg:nuget/") { - &["nuget"] + &["nuget", "unknown"] } else { - // Unknown ecosystems fail closed: tie them to the reserved - // "unknown" group, which refuses whenever it holds edits. &["unknown"] } } @@ -2020,6 +2027,79 @@ mod tests { assert_eq!(state.edits.len(), 1); } + const VLT_LOCK: &str = "{\n \"lockfileVersion\": 0,\n \"nodes\": {\n \"~npm~minimist@1.2.8\": [0,\"minimist\",\"sha512-p\",\"https://patch.socket.dev/npm/minimist/1.2.8/t/u/minimist-1.2.8.tgz\"]\n },\n \"edges\": {}\n}\n"; + + fn vlt_lock_node_edit() -> FileEdit { + FileEdit { + key: Some("minimist@1.2.8".into()), + ..edit( + "vlt-lock.json", + "redirect_vlt_lock_node", + "rewritten", + Some("\"~npm~minimist@1.2.8\": [0,\"minimist\",\"sha512-r\"]"), + Some( + "\"~npm~minimist@1.2.8\": [0,\"minimist\",\"sha512-p\",\ + \"https://patch.socket.dev/npm/minimist/1.2.8/t/u/minimist-1.2.8.tgz\"]", + ), + ) + } + } + + #[tokio::test] + async fn unclassified_kind_holds_the_npm_record_and_every_other_record() { + for dry_run in [true, false] { + let dir = TempDir::new().unwrap(); + write(dir.path(), "vlt-lock.json", VLT_LOCK).await; + write(dir.path(), "composer.lock", "https://patch.example/c\n").await; + let mut state = state_with( + vec![ + vlt_lock_node_edit(), + edit( + "composer.lock", + "redirect_composer_dist", + "rewritten", + Some("https://packagist.example/c"), + Some("https://patch.example/c"), + ), + ], + &["pkg:npm/minimist@1.2.8", "pkg:composer/v/c@1.0.0"], + ); + let out = revert_remaining_redirect_edits(dir.path(), &mut state, dry_run).await; + assert_eq!(out.refusals.len(), 1, "{out:?}"); + assert_eq!(out.refusals[0].group, "unknown"); + assert!(out.dropped_records.is_empty(), "{out:?}"); + assert!(state.records.contains_key("pkg:npm/minimist@1.2.8")); + assert!(state.records.contains_key("pkg:composer/v/c@1.0.0")); + assert_eq!(read(dir.path(), "vlt-lock.json").await, VLT_LOCK); + assert!(state + .edits + .iter() + .any(|e| e.kind == "redirect_vlt_lock_node")); + } + } + + #[test] + fn every_ecosystem_group_list_includes_the_unknown_group() { + for purl in [ + "pkg:npm/a@1", + "pkg:cargo/a@1", + "pkg:gem/a@1", + "pkg:pypi/a@1", + "pkg:composer/v/a@1", + "pkg:golang/example.com/a@v1.0.0", + "pkg:maven/g/a@1", + "pkg:nuget/A@1", + "pkg:hex/a@1", + ] { + assert!(groups_for_record_purl(purl).contains(&"unknown"), "{purl}"); + } + assert!(is_unclassified_kind("redirect_vlt_lock_node", "rewritten")); + assert!(!is_unclassified_kind( + "redirect_bun_lock_package", + "rewritten" + )); + } + #[tokio::test] async fn leftover_npm_json_edit_refuses_and_holds_every_npm_family_record() { let dir = TempDir::new().unwrap(); diff --git a/crates/socket-patch-core/src/patch/redirect/takeover.rs b/crates/socket-patch-core/src/patch/redirect/takeover.rs index 5280cf91..a5fb403e 100644 --- a/crates/socket-patch-core/src/patch/redirect/takeover.rs +++ b/crates/socket-patch-core/src/patch/redirect/takeover.rs @@ -129,6 +129,36 @@ fn find_record_key(state: &RedirectState, purl: &str) -> Result<(String, String) Ok((record_key, strip_purl_qualifiers(purl).to_string())) } +/// Refuse a per-purl claim while the ledger holds a `redirect_*` edit this +/// release cannot classify that mentions `@`: claiming the +/// rest and dropping the record would strand that edit (half a takeover). +fn refuse_unclassified_edits( + state: &RedirectState, + name: &str, + version: &str, +) -> Result<(), String> { + let needle = format!("{name}@{version}"); + let mentions = |v: &Option| match v { + Some(Value::String(s)) => s.contains(&needle), + Some(other) => other.to_string().contains(&needle), + None => false, + }; + match state.edits.iter().find(|e| { + e.kind.starts_with("redirect_") + && super::replay::is_unclassified_kind(&e.kind, &e.action) + && (e.key.as_deref().is_some_and(|k| k.contains(&needle)) + || mentions(&e.original) + || mentions(&e.new)) + }) { + Some(e) => Err(format!( + "the redirect ledger holds a {} edit this socket-patch release does not \ + understand; upgrade socket-patch", + e.kind + )), + None => Ok(()), + } +} + /// Drop the claimed edits (by ledger index) and the purl's record from the /// ledger — only after every inverse applied cleanly. The caller persists. fn drop_claimed(state: &mut RedirectState, claimed: Vec, record_key: &str) { @@ -224,6 +254,7 @@ pub async fn revert_cargo_redirect_purl( return Err(format!("not a cargo purl: {purl}")); }; let (name, version) = (name.into_owned(), version.into_owned()); + refuse_unclassified_edits(state, &name, &version)?; let lock_key = format!("{name}@{version}"); // Manifest edits are keyed by crate NAME (the shared golden ledger @@ -481,6 +512,7 @@ pub async fn revert_golang_redirect_purl( return Err(format!("not a golang purl: {purl}")); }; let (module, version) = (module.into_owned(), version.into_owned()); + refuse_unclassified_edits(state, &module, &version)?; let lhs = format!("{module} {version} =>"); let is_replace_edit = |e: &FileEdit| { matches!( @@ -761,6 +793,7 @@ pub async fn revert_npm_redirect_purl( return Err(format!("not an npm purl: {purl}")); }; let (name, version) = (name.into_owned(), version.into_owned()); + refuse_unclassified_edits(state, &name, &version)?; let lock_key = format!("{name}@{version}"); // The package-lock/shrinkwrap files any `redirect_npm_lock_entry` edits @@ -2451,6 +2484,95 @@ mod tests { assert!(state.edits.is_empty(), "edits dropped"); } + fn vlt_lock_node_edit(name: &str, version: &str) -> FileEdit { + FileEdit { + path: "vlt-lock.json".into(), + kind: "redirect_vlt_lock_node".into(), + action: "rewritten".into(), + key: Some(format!("{name}@{version}")), + original: Some(Value::String(format!( + "\"~npm~{name}@{version}\": [0,\"{name}\",\"sha512-r\"]" + ))), + new: Some(Value::String(format!( + "\"~npm~{name}@{version}\": [0,\"{name}\",\"sha512-p\",\"{NPM_URL}\"]" + ))), + } + } + + #[tokio::test] + async fn npm_unclassified_edit_naming_the_purl_refuses_the_claim_untouched() { + for dry_run in [true, false] { + let (tmp, mut state) = npm_redirected_fixture("yarn.lock", &classic_pristine()).await; + let root = tmp.path(); + state.edits.push(vlt_lock_node_edit("left-pad", "1.3.0")); + let wired = tokio::fs::read_to_string(root.join("yarn.lock")) + .await + .unwrap(); + let before = state.clone(); + let err = revert_redirect_purl(root, &mut state, NPM_PURL, dry_run) + .await + .unwrap_err(); + assert_eq!( + err, + "the redirect ledger holds a redirect_vlt_lock_node edit this socket-patch \ + release does not understand; upgrade socket-patch" + ); + assert_eq!( + serde_json::to_value(&state).unwrap(), + serde_json::to_value(&before).unwrap(), + "nothing claimed" + ); + assert_eq!( + tokio::fs::read_to_string(root.join("yarn.lock")) + .await + .unwrap(), + wired + ); + } + } + + #[tokio::test] + async fn npm_unclassified_edit_for_another_package_does_not_block_the_claim() { + let (tmp, mut state) = npm_redirected_fixture("yarn.lock", &classic_pristine()).await; + let root = tmp.path(); + state.edits.push(vlt_lock_node_edit("left-pad", "1.3.1")); + revert_redirect_purl(root, &mut state, NPM_PURL, false) + .await + .expect("revert succeeds"); + assert_eq!(state.edits.len(), 1); + assert_eq!(state.edits[0].kind, "redirect_vlt_lock_node"); + } + + #[tokio::test] + async fn cargo_and_golang_claims_refuse_an_unclassified_edit_naming_them() { + let tmp = tempfile::tempdir().unwrap(); + for (purl, name, version) in [ + ("pkg:cargo/serde@1.0.0", "serde", "1.0.0"), + ("pkg:golang/example.com/m@v1.2.3", "example.com/m", "v1.2.3"), + ] { + let mut state = RedirectState::new(); + state.records.insert(purl.into(), record()); + state.edits.push(FileEdit { + path: "future.lock".into(), + kind: "redirect_future_lock_entry".into(), + action: "rewritten".into(), + key: None, + original: Some(serde_json::json!({ "id": format!("{name}@{version}") })), + new: Some(Value::String("x".into())), + }); + let before = state.clone(); + let err = revert_redirect_purl(tmp.path(), &mut state, purl, false) + .await + .unwrap_err(); + assert!(err.contains("redirect_future_lock_entry edit"), "{err}"); + assert_eq!( + serde_json::to_value(&state).unwrap(), + serde_json::to_value(&before).unwrap(), + "{purl}" + ); + } + } + #[tokio::test] async fn npm_berry_lock_round_trips_and_drops_ledger_entries() { let (tmp, mut state) = npm_redirected_fixture("yarn.lock", &berry_pristine()).await; diff --git a/crates/socket-patch-core/src/vendor/npm_flavor.rs b/crates/socket-patch-core/src/vendor/npm_flavor.rs index 41176de7..46c4d08c 100644 --- a/crates/socket-patch-core/src/vendor/npm_flavor.rs +++ b/crates/socket-patch-core/src/vendor/npm_flavor.rs @@ -51,6 +51,15 @@ pub(crate) enum NpmLockFlavor { } impl NpmLockFlavor { + const ALL: [NpmLockFlavor; 6] = [ + NpmLockFlavor::PackageLock, + NpmLockFlavor::YarnClassic, + NpmLockFlavor::YarnBerry, + NpmLockFlavor::Pnpm, + NpmLockFlavor::PnpmLegacy, + NpmLockFlavor::Bun, + ]; + /// The stable string recorded as [`VendorEntry::flavor`]. fn as_str(self) -> &'static str { match self { @@ -454,6 +463,14 @@ pub(super) async fn lock_text_mentions_uuid( any_readable.then_some(false) } +/// Does this build have a backend for an npm entry's recorded flavor? +/// `None` is a pre-flavor (package-lock) ledger. An unknown flavor was +/// wired by a newer socket-patch, so health checks and rebuilds must not +/// judge it by this build's layout rules. +pub fn npm_flavor_is_known(flavor: Option<&str>) -> bool { + flavor.is_none_or(|f| NpmLockFlavor::ALL.iter().any(|known| known.as_str() == f)) +} + /// Revert one recorded npm vendor entry through the flavor that wired it. /// Entries from before the flavor field existed (`None`) are package-lock /// wirings; an unknown flavor fails CLOSED (an older binary must not guess @@ -554,6 +571,17 @@ mod tests { #[test] fn flavor_strings_are_stable() { + assert_eq!( + NpmLockFlavor::ALL.map(NpmLockFlavor::as_str), + [ + "package-lock", + "yarn-classic", + "yarn-berry", + "pnpm", + "pnpm-legacy", + "bun" + ] + ); assert_eq!(NpmLockFlavor::PackageLock.as_str(), "package-lock"); assert_eq!(NpmLockFlavor::YarnClassic.as_str(), "yarn-classic"); assert_eq!(NpmLockFlavor::Pnpm.as_str(), "pnpm"); @@ -1046,6 +1074,9 @@ mod tests { assert!(!outcome.success); assert!(outcome.error.as_deref().unwrap().contains("future-pm")); + assert!(!npm_flavor_is_known(Some("future-pm"))); + assert!(!npm_flavor_is_known(Some("vlt"))); + // Every known flavor routes to its backend; with no wiring records and // nothing on disk each reverts trivially (None = a pre-flavor ledger). for flavor in [ @@ -1057,6 +1088,7 @@ mod tests { Some("pnpm-legacy".to_string()), Some("bun".to_string()), ] { + assert!(npm_flavor_is_known(flavor.as_deref()), "{flavor:?}"); entry.flavor = flavor.clone(); let outcome = revert_npm_any(&entry, tmp.path(), false).await; assert!(outcome.success, "flavor {flavor:?}: {:?}", outcome.error); diff --git a/crates/socket-patch-core/src/vendor/verify.rs b/crates/socket-patch-core/src/vendor/verify.rs index 32c0e58b..2b034320 100644 --- a/crates/socket-patch-core/src/vendor/verify.rs +++ b/crates/socket-patch-core/src/vendor/verify.rs @@ -438,6 +438,9 @@ pub enum ArtifactHealth { /// The entry can't be judged (poisoned path, empty record): fail /// closed, never rebuild from it. Unverifiable { reason: String }, + /// An npm entry wired by a flavor this build has no backend for (a + /// newer socket-patch): its layout is not ours to judge or rebuild. + UnknownFlavor { flavor: String }, } /// Health-check one vendored artifact against its patch record: the @@ -453,6 +456,12 @@ pub async fn check_vendored_artifact( entry: &VendorEntry, record: &PatchRecord, ) -> ArtifactHealth { + if entry.ecosystem == "npm" && !super::npm_flavor::npm_flavor_is_known(entry.flavor.as_deref()) + { + return ArtifactHealth::UnknownFlavor { + flavor: entry.flavor.clone().unwrap_or_default(), + }; + } match verify_vendored_patch_record(project_root, entry, record).await { Err(tag) => { // A broken member copy must not hide a simultaneously corrupt @@ -1432,6 +1441,33 @@ mod tests { ); } + #[tokio::test] + async fn unknown_npm_flavor_is_never_judged_by_this_builds_layout() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let rel = format!(".socket/vendor/npm/{UUID}/left-pad-1.3.0/node_modules/left-pad"); + tokio::fs::create_dir_all(root.join(&rel).join("node_modules")) + .await + .unwrap(); + tokio::fs::write(root.join(&rel).join("index.js"), b"tampered") + .await + .unwrap(); + let rec = record(UUID, "package/index.js"); + let mut ent = entry("npm", UUID, &rel); + ent.flavor = Some("vlt".into()); + assert_eq!( + check_vendored_artifact(root, &ent, &rec).await, + ArtifactHealth::UnknownFlavor { + flavor: "vlt".into() + } + ); + ent.flavor = Some("bun".into()); + assert!(matches!( + check_vendored_artifact(root, &ent, &rec).await, + ArtifactHealth::Corrupt { .. } + )); + } + /// SECURITY: the zip entry-count cap fails a tampered wheel closed — /// one entry past the cap (even zero-byte entries) is rejected up /// front, while a wheel at exactly the cap still reads (no off-by-one From 329c0404a8baf620374b37f0cb1f26b9eeb1e5c8 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Fri, 25 Sep 2026 19:50:44 -0400 Subject: [PATCH 05/46] Keep unknown ledger edits out of vendor reconcile The end-of-run hosted-to-vendored reconcile in `vendor` and `scan --mode vendored` could still drop a redirect record together with a ledger edit this release does not understand (for example a vlt-lock.json edit), leaving that lockfile on the hosted URL with nothing tracking it while reporting the migration as reconciled. It now leaves such a package in the ledger and prints the manual cleanup advisory instead. An unknown edit now only blocks the package it actually names, so an edit for left-pad or @scope/pad no longer refuses a rollback of pad. Whole-ledger rollbacks that hit an unknown edit now say to upgrade socket-patch rather than suggesting a rescan that cannot help. The changelog and contract now say that such a rollback still unwinds the lockfiles this release understands while keeping every record. Assisted-by: Claude Code:claude-opus-5-5 --- CHANGELOG.md | 13 +- crates/socket-patch-cli/CLI_CONTRACT.md | 2 +- .../tests/in_process_rollback_hosted.rs | 120 +++++++---- .../src/patch/redirect/replay.rs | 39 +++- .../src/patch/redirect/state.rs | 191 +++++++++++++++++- .../src/patch/redirect/takeover.rs | 31 +-- 6 files changed, 322 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f42adb9d..c3a2ca45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -631,10 +631,15 @@ into the new version's section — see docs/releasing.md. A hosted redirect edit kind this release does not understand used to let `rollback` drop the npm record beside it, leaving that lockfile redirected with nothing tracking it. Such an edit now holds every - record in the redirect ledger, and `rollback `, `remove` and the - hosted-to-vendored takeover refuse, with nothing written, when it - names the purl ("the redirect ledger holds a {kind} edit this - socket-patch release does not understand; upgrade socket-patch"). + record in the redirect ledger ("the redirect ledger holds a {kind} + edit this socket-patch release does not understand; upgrade + socket-patch"). When it names a purl, that purl's own revert in + `rollback `, `remove` and the hosted-to-vendored takeover + refuses with nothing written, and the takeover's ledger reconcile + leaves the purl for the manual cleanup. When the scope still covers + every hosted record, the whole-ledger replay goes on to unwind the + lockfiles this release understands, but keeps every record and the + unknown edit. `repair` skips vendored npm entries whose `flavor` it does not know (`vendor_wiring_unknown_revert_blocked`) instead of rebuilding them with the wrong layout rules. vlt ledgers (`redirect_vlt_lock_node`, diff --git a/crates/socket-patch-cli/CLI_CONTRACT.md b/crates/socket-patch-cli/CLI_CONTRACT.md index 0583b606..bc130bdb 100644 --- a/crates/socket-patch-cli/CLI_CONTRACT.md +++ b/crates/socket-patch-cli/CLI_CONTRACT.md @@ -924,7 +924,7 @@ Restore the system but keep the local patch state for a later re-apply: manifest * **Per-purl reverts** exist for **cargo, golang and the npm family** (`redirect_revert_supported`): staged, fail-closed on drift, and honoring `dry_run` (every inverse and drift check resolves like a wet run; nothing flushes and the ledger is untouched). npm purls on projects with bun-lock edits DEFER to the whole-ledger replay (below) whenever it will run — the scope covers every record, and the replay stages the bun group all-or-nothing. A SCOPED unwind (`rollback `, or `remove ` while other hosted records remain) takes the per-purl revert instead: it claims that purl's `redirect_bun_lock_package` edits by the recorded line's spec (`@` registry spec, or a hosted URL whose tarball leaf is `-.tgz`) and replays them like the yarn/pnpm text kinds (whole-line fragments, CRLF-exact); a sibling version's edit is neither claimed nor a refusal; an edit that mentions the package but is not a bun packages-entry line refuses with the unscoped-`rollback` remedy. Pinned by `tests/in_process_vendor_bun_takeover.rs` (`bun_scoped_rollback_of_one_of_two_hosted_records_unwinds_only_that_purl` and the `remove` twin). Native binary `redirect_bun_lockb_package` snapshots follow the same scoped ownership rule and restore only the claimed package records; unrelated binary resolutions stay intact. yarn lock blocks (`redirect_yarn_berry_entry` / `redirect_yarn_classic_entry`) are recorded in the lock's on-disk line endings and replayed byte-exactly; when a `core.autocrlf` checkout has since flipped the lock's UNIFORM ending (LF ↔ CRLF — the committed ledger keeps its fragments verbatim), this per-purl revert and the whole-ledger replay below match the recorded blocks respelled in the live ending and restore in that ending (v5.0). A lock with mixed endings proves nothing and still refuses as drift. * **Whole-ledger reverse replay** (`revert_remaining_redirect_edits`, core `patch/redirect/replay.rs`) runs whenever the in-scope hosted record set equals the FULL ledger record set — however the scope was spelled (bare `rollback`, `rollback '**'`, an identifier set covering every record; `remove` reuses the same eligibility rule). It walks every remaining ledger edit in reverse write order through a **per-kind inverse table**, staged and committed **per ecosystem group, all-or-nothing**: one drifted, ambiguous (a fragment appearing more than once), or unhandled edit refuses the whole group byte-untouched while other groups proceed. This covers **gem, golang, pypi, composer, bun**, the yarn/pnpm text kinds (normally claimed by the per-purl npm revert first), and the **non-package rideshare edits** — the pnpm `trustLockfile` auto-config (a pristine created scaffold is deleted; a user-modified one keeps the file and loses only the `trustLockfile: true` line, warned as `redirect_pnpm_trust_scaffold_modified`) — plus a "last one out turns off the lights" pass: when the record map empties but non-package edits remain, they are replayed in the same persist, so the trust edit never strands. The npm `.npmrc` `allow-remote=all` auto-config (`redirect_npmrc_allow_remote`) replays in the `npm` group (a pristine created file is deleted; otherwise only the line is removed, warned as `redirect_npmrc_allow_remote_modified` for a modified created file) and is ALSO claimed by the per-purl npm revert of the last package-lock entry, so a scoped unwind never strands it. * **maven and nuget fail closed**: their structured-metadata kinds (`redirect_maven_repository` / `redirect_maven_dep_management` / `redirect_maven_config` / `redirect_maven_trusted_checksums`, `redirect_nuget_source` / `redirect_nuget_lock`) have no revert implementation, so any such edit refuses its whole group (the maven `` suffix rewrite alone IS invertible, but it rides the same all-or-nothing group). The refusal keeps their records + edits in the ledger and names the remedy: re-run `scan --mode hosted` to normalize, or restore the lockfiles from version control. -* **Unknown edit kinds fail closed (forward compatibility).** A ledger edit kind this release has no inverse for (written by a newer socket-patch) refuses in the replay's reserved `unknown` group, and every record of every ecosystem is held while that group refuses, so no record is dropped beside an edit it may own. A per-purl revert (`rollback `, `remove`, the hosted→vendored takeover) refuses with nothing written when any unknown `redirect_*` edit's `key`, `original` or `new` contains the purl's `@`: "the redirect ledger holds a {kind} edit this socket-patch release does not understand; upgrade socket-patch". vlt ledgers (`redirect_vlt_lock_node` edits, vendored entries with `flavor: "vlt"`) require the socket-patch release that adds vlt support. The ledger `version` stays 1: compatibility is decided per kind. +* **Unknown edit kinds fail closed (forward compatibility).** A ledger edit kind this release has no inverse for (written by a newer socket-patch) refuses in the replay's reserved `unknown` group with "the redirect ledger holds a {kind} edit this socket-patch release does not understand; upgrade socket-patch", and every record of every ecosystem is held while that group refuses, so no record is dropped beside an edit it may own. The other groups still unwind on disk and drop their edits; only their records wait until the unknown group clears. A per-purl revert (`rollback `, `remove`, the hosted→vendored takeover) refuses with the same text, and with nothing written, when any unknown `redirect_*` edit's `key`, `original` or `new` names the purl's `@` (at a package-name boundary: `left-pad@1.3.0` does not name `pad@1.3.0`, nor does `@scope/a@1.0.0` name `a@1.0.0`). When that scope covers every record, the whole-ledger replay above still runs after the refusal. The vendored flows' takeover reconcile (`vendor_supersedes_redirect`) drops nothing for such a purl and falls back to the manual advisory. vlt ledgers (`redirect_vlt_lock_node` edits, vendored entries with `flavor: "vlt"`) require the socket-patch release that adds vlt support. The ledger `version` stays 1: compatibility is decided per kind. * **Scoped runs** (paths / identifiers / `--ecosystems`) that do NOT cover the full record set get per-purl reverts only; in-scope hosted purls of ecosystems without one fail closed — `rollback` reports them in `hosted.unsupported` (exit 1), `remove` as the top-level `hosted_revert_unsupported` error — with the remedy "run an unscoped `socket-patch rollback` to unwind ALL hosted redirects, or re-run `scan --mode hosted`". * **Ledger accounting**: exactly the replayed (or already-at-original) edits are dropped; a record is dropped only when every group its ecosystem writes ended clean, so refused groups keep both edits and records — the intermediate-but-coherent ledger a retry needs. The mutated ledger is persisted (delete-when-empty); a failed persist rides `hosted.failed` / `hosted_revert_failed`. diff --git a/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs b/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs index e9b3d3ca..bd177041 100644 --- a/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs +++ b/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs @@ -857,7 +857,7 @@ async fn scoped_unsupported_ecosystem_fails_closed() { /// release has no revert for (`redirect_vlt_lock_node`). A scoped rollback /// of the purl it names must refuse with nothing written, and an unscoped /// one must keep the record while that edit survives. -async fn write_vlt_ledger_fixture(root: &Path) -> String { +async fn write_vlt_ledger_fixture(root: &Path, with_gem: bool) -> String { let vlt_new = format!( "\"~npm~left-pad@1.2.3\": [0,\"left-pad\",\"sha512-PATCHEDpatched==\",\"{LP_HOSTED_URL}\"]" ); @@ -870,42 +870,46 @@ async fn write_vlt_ledger_fixture(root: &Path) -> String { yarn_lock_content(&yarn_redirected_block()), ) .unwrap(); - std::fs::write( - root.join("Gemfile.lock"), - gemfile_lock_content(GEM_PATCH_REMOTE), - ) - .unwrap(); - write_hosted_ledger( - root, - vec![ - (LP_PURL, patch_record(LP_UUID, "GHSA-lpad-aaaa-bbbb")), - (GEM_PURL, patch_record(GEM_UUID, "GHSA-gems-cccc-dddd")), - ], - vec![ - yarn_classic_edit(), - gem_source_edit(), - FileEdit { - path: "vlt-lock.json".to_string(), - kind: "redirect_vlt_lock_node".to_string(), - action: "rewritten".to_string(), - key: Some("left-pad@1.2.3".to_string()), - original: Some(Value::String( - "\"~npm~left-pad@1.2.3\": [0,\"left-pad\",\"sha512-UPSTREAMupstream==\"]" - .to_string(), - )), - new: Some(Value::String(vlt_new)), - }, - ], - ) - .await; + let mut records = vec![(LP_PURL, patch_record(LP_UUID, "GHSA-lpad-aaaa-bbbb"))]; + let mut edits = vec![yarn_classic_edit()]; + if with_gem { + std::fs::write( + root.join("Gemfile.lock"), + gemfile_lock_content(GEM_PATCH_REMOTE), + ) + .unwrap(); + records.push((GEM_PURL, patch_record(GEM_UUID, "GHSA-gems-cccc-dddd"))); + edits.push(gem_source_edit()); + } + edits.push(FileEdit { + path: "vlt-lock.json".to_string(), + kind: "redirect_vlt_lock_node".to_string(), + action: "rewritten".to_string(), + key: Some("left-pad@1.2.3".to_string()), + original: Some(Value::String( + "\"~npm~left-pad@1.2.3\": [0,\"left-pad\",\"sha512-UPSTREAMupstream==\"]".to_string(), + )), + new: Some(Value::String(vlt_new)), + }); + write_hosted_ledger(root, records, edits).await; vlt_lock } +fn ledger_edit_kinds(root: &Path) -> Vec { + let ledger: Value = serde_json::from_slice(&std::fs::read(ledger_path(root)).unwrap()).unwrap(); + ledger["edits"] + .as_array() + .unwrap() + .iter() + .map(|e| e["kind"].as_str().unwrap().to_string()) + .collect() +} + #[tokio::test] #[serial] async fn scoped_rollback_refuses_a_purl_named_by_an_unknown_edit_kind() { let tmp = tempfile::tempdir().unwrap(); - let vlt_lock = write_vlt_ledger_fixture(tmp.path()).await; + let vlt_lock = write_vlt_ledger_fixture(tmp.path(), true).await; let ledger_before = std::fs::read(ledger_path(tmp.path())).unwrap(); let yarn_before = std::fs::read_to_string(tmp.path().join("yarn.lock")).unwrap(); @@ -945,7 +949,7 @@ async fn scoped_rollback_refuses_a_purl_named_by_an_unknown_edit_kind() { #[serial] async fn unscoped_rollback_holds_the_record_beside_an_unknown_edit_kind() { let tmp = tempfile::tempdir().unwrap(); - let vlt_lock = write_vlt_ledger_fixture(tmp.path()).await; + let vlt_lock = write_vlt_ledger_fixture(tmp.path(), true).await; let code = rollback_in_process(tmp.path(), Vec::new(), false).await; assert_eq!( @@ -960,14 +964,54 @@ async fn unscoped_rollback_holds_the_record_beside_an_unknown_edit_kind() { serde_json::from_slice(&std::fs::read(ledger_path(tmp.path())).unwrap()).unwrap(); assert!(ledger["records"].get(LP_PURL).is_some(), "{ledger}"); assert!(ledger["records"].get(GEM_PURL).is_some(), "{ledger}"); - assert!( - ledger["edits"] - .as_array() - .unwrap() - .iter() - .any(|e| e["kind"] == "redirect_vlt_lock_node"), - "{ledger}" + // The groups this release understands still unwind on disk; their + // records wait for the unknown group to clear. + assert_eq!(ledger_edit_kinds(tmp.path()), ["redirect_vlt_lock_node"]); + assert_eq!( + std::fs::read_to_string(tmp.path().join("yarn.lock")).unwrap(), + yarn_lock_content(&yarn_original_block()) ); + assert_eq!( + std::fs::read_to_string(tmp.path().join("Gemfile.lock")).unwrap(), + gemfile_lock_content(GEM_UPSTREAM_REMOTE) + ); +} + +/// With the purl as the only hosted record the scope covers the whole +/// ledger: the per-purl claim refuses, then the replay still unwinds +/// yarn.lock but holds the record and the unknown edit. +#[tokio::test] +#[serial] +async fn scoped_rollback_of_the_only_record_holds_it_beside_an_unknown_edit_kind() { + let tmp = tempfile::tempdir().unwrap(); + let vlt_lock = write_vlt_ledger_fixture(tmp.path(), false).await; + + let (code, envelope) = run_rollback_subprocess(tmp.path(), &[LP_PURL]); + assert_eq!(code, 1, "{envelope}"); + assert_eq!( + envelope["hosted"]["reverted"], + serde_json::json!([]), + "{envelope}" + ); + let failed: Vec<&str> = envelope["hosted"]["failed"] + .as_array() + .unwrap() + .iter() + .map(|f| f["purl"].as_str().unwrap()) + .collect(); + assert_eq!(failed, [LP_PURL, "group:unknown"], "{envelope}"); + assert_eq!( + std::fs::read_to_string(tmp.path().join("vlt-lock.json")).unwrap(), + vlt_lock + ); + assert_eq!( + std::fs::read_to_string(tmp.path().join("yarn.lock")).unwrap(), + yarn_lock_content(&yarn_original_block()) + ); + let ledger: Value = + serde_json::from_slice(&std::fs::read(ledger_path(tmp.path())).unwrap()).unwrap(); + assert!(ledger["records"].get(LP_PURL).is_some(), "{ledger}"); + assert_eq!(ledger_edit_kinds(tmp.path()), ["redirect_vlt_lock_node"]); } // --------------------------------------------------------------------------- diff --git a/crates/socket-patch-core/src/patch/redirect/replay.rs b/crates/socket-patch-core/src/patch/redirect/replay.rs index 575a41d6..f0c29b37 100644 --- a/crates/socket-patch-core/src/patch/redirect/replay.rs +++ b/crates/socket-patch-core/src/patch/redirect/replay.rs @@ -542,15 +542,21 @@ pub async fn revert_remaining_redirect_edits( continue 'group; } Inverse::Unsupported => { - refuse( + let reason = if *group == "unknown" { + format!( + "the redirect ledger holds a {} edit this socket-patch release \ + does not understand; upgrade socket-patch", + edit.kind + ) + } else { format!( "no hosted-redirect revert implementation for {} — re-run \ `scan --mode hosted` to normalize, or restore the file from \ version control", edit.kind - ), - &mut outcome, - ); + ) + }; + refuse(reason, &mut outcome); refused_groups.insert(group); continue 'group; } @@ -2067,14 +2073,31 @@ mod tests { let out = revert_remaining_redirect_edits(dir.path(), &mut state, dry_run).await; assert_eq!(out.refusals.len(), 1, "{out:?}"); assert_eq!(out.refusals[0].group, "unknown"); + assert_eq!( + out.refusals[0].reason, + "the redirect ledger holds a redirect_vlt_lock_node edit this socket-patch \ + release does not understand; upgrade socket-patch" + ); assert!(out.dropped_records.is_empty(), "{out:?}"); assert!(state.records.contains_key("pkg:npm/minimist@1.2.8")); assert!(state.records.contains_key("pkg:composer/v/c@1.0.0")); assert_eq!(read(dir.path(), "vlt-lock.json").await, VLT_LOCK); - assert!(state - .edits - .iter() - .any(|e| e.kind == "redirect_vlt_lock_node")); + let kinds: Vec<&str> = state.edits.iter().map(|e| e.kind.as_str()).collect(); + // The composer group still unwinds on disk; only its record waits + // for the unknown group to clear. + if dry_run { + assert_eq!( + read(dir.path(), "composer.lock").await, + "https://patch.example/c\n" + ); + assert_eq!(kinds, ["redirect_vlt_lock_node", "redirect_composer_dist"]); + } else { + assert_eq!( + read(dir.path(), "composer.lock").await, + "https://packagist.example/c\n" + ); + assert_eq!(kinds, ["redirect_vlt_lock_node"]); + } } } diff --git a/crates/socket-patch-core/src/patch/redirect/state.rs b/crates/socket-patch-core/src/patch/redirect/state.rs index 129012a0..420ecde9 100644 --- a/crates/socket-patch-core/src/patch/redirect/state.rs +++ b/crates/socket-patch-core/src/patch/redirect/state.rs @@ -75,6 +75,59 @@ impl RedirectState { .cloned() .collect() } + + /// The first `redirect_*` edit this release cannot classify (a newer + /// socket-patch's writer) whose `key`, `original` or `new` names + /// `@` at a package-name boundary. Anything that claims + /// that package's ledger data must refuse while one exists: dropping the + /// record or its known edits would strand the unknown one. + pub(crate) fn unclassified_edit_naming(&self, name: &str, version: &str) -> Option<&FileEdit> { + let needle = format!("{name}@{version}"); + let scoped = name.starts_with('@'); + let names = |v: &Option| match v { + Some(serde_json::Value::String(s)) => names_at_boundary(s, &needle, scoped), + Some(other) => names_at_boundary(&other.to_string(), &needle, scoped), + None => false, + }; + self.edits.iter().find(|e| { + is_unclassified_redirect_edit(e) + && (e + .key + .as_deref() + .is_some_and(|k| names_at_boundary(k, &needle, scoped)) + || names(&e.original) + || names(&e.new)) + }) + } +} + +fn is_unclassified_redirect_edit(edit: &FileEdit) -> bool { + edit.kind.starts_with("redirect_") + && super::replay::is_unclassified_kind(&edit.kind, &edit.action) +} + +fn is_name_char(c: char) -> bool { + c.is_ascii_alphanumeric() || matches!(c, '-' | '.' | '_') +} + +/// Does `text` contain `needle` starting at a package-name boundary? A +/// match glued to a longer name (`left-pad@…` for `pad@…`) or to a scope +/// (`@scope/a@…` for an unscoped `a@…`) names a different package. +fn names_at_boundary(text: &str, needle: &str, scoped: bool) -> bool { + text.match_indices(needle).any(|(at, _)| { + let before = &text[..at]; + match before.chars().next_back() { + None => true, + Some('/') => { + scoped + || !before[..before.len() - 1] + .rsplit(|c: char| !(is_name_char(c) || c == '@')) + .next() + .is_some_and(|segment| segment.starts_with('@')) + } + Some(c) => !is_name_char(c), + } + }) } impl Default for RedirectState { @@ -280,6 +333,10 @@ pub async fn save_redirect_state( /// keyed `"trustLockfile"`) stay: they belong to the hosted flow's own /// config surface and other still-redirected package(s) may ride on them. /// +/// A `redirect_*` edit kind this release cannot classify that names the +/// purl or would be claimed by the rules above drops nothing and returns +/// `false`: its lockfile may still resolve the hosted artifact. +/// /// Returns whether anything was removed. The caller persists the mutated /// ledger via [`persist_redirect_state`] (atomic; an emptied ledger is /// deleted). @@ -293,6 +350,10 @@ pub fn drop_superseded_purl(state: &mut RedirectState, purl: &str) -> bool { }; let (name, version) = (name.to_string(), version.to_string()); + if state.unclassified_edit_naming(&name, &version).is_some() { + return false; + } + let record_keys = state.record_keys_for(purl); // THIS purl's patch uuid(s), captured before the records are removed — // the artifact anchor (see the doc comment). Distinct purls (including @@ -307,16 +368,12 @@ pub fn drop_superseded_purl(state: &mut RedirectState, purl: &str) -> bool { // revert data. Fail closed to the version-exact-only path instead. .filter(|u| !u.is_empty()) .collect(); - for key in &record_keys { - state.records.remove(key); - } let name_at_version = format!("{name}@{version}"); - let edits_before = state.edits.len(); - state.edits.retain(|e| { + let claims = |e: &FileEdit| { let Some(key) = e.key.as_deref() else { // No key ⇒ not attributable to any package; keep. - return true; + return false; }; // Version-exact instance keys: `name@version`, pnpm v6 peer-suffixed // `name@version(peer…)`, pnpm v5 respelled `name@version_peer…`. @@ -337,8 +394,21 @@ pub fn drop_superseded_purl(state: &mut RedirectState, purl: &str) -> bool { }; uuids.iter().any(|uuid| text.contains(uuid.as_str())) }); - !(version_exact || anchored) - }); + version_exact || anchored + }; + if state + .edits + .iter() + .any(|e| is_unclassified_redirect_edit(e) && claims(e)) + { + return false; + } + + for key in &record_keys { + state.records.remove(key); + } + let edits_before = state.edits.len(); + state.edits.retain(|e| !claims(e)); !record_keys.is_empty() || state.edits.len() != edits_before } @@ -771,6 +841,111 @@ mod tests { ); } + fn vlt_node_edit(name: &str, version: &str, url: &str) -> FileEdit { + FileEdit { + path: "vlt-lock.json".to_string(), + kind: "redirect_vlt_lock_node".to_string(), + action: "rewritten".to_string(), + key: Some(format!("{name}@{version}")), + original: Some(serde_json::json!(format!( + "\"~npm~{name}@{version}\": [0,\"{name}\",\"sha512-r\"]" + ))), + new: Some(serde_json::json!(format!( + "\"~npm~{name}@{version}\": [0,\"{name}\",\"sha512-p\",\"{url}\"]" + ))), + } + } + + #[test] + fn drop_superseded_purl_drops_nothing_beside_an_unclassified_edit_naming_it() { + let url = hosted_url("left-pad", "1.3.0", SAMPLE_UUID); + let unanchored = hosted_url("left-pad", "1.3.0", "0e0e0e0e-0000-4000-8000-000000000000"); + for (with_record, vlt_key, vlt_url) in [ + (true, "left-pad@1.3.0", url.as_str()), + (false, "left-pad@1.3.0", url.as_str()), + (false, "left-pad@1.3.0~custom", unanchored.as_str()), + ] { + let mut state = RedirectState::new(); + if with_record { + state + .records + .insert("pkg:npm/left-pad@1.3.0".to_string(), sample_record()); + } + state.edits = vec![ + edit_resolved( + "yarn.lock", + "redirect_yarn_classic_entry", + "left-pad@1.3.0", + &url, + ), + FileEdit { + key: Some(vlt_key.to_string()), + ..vlt_node_edit("left-pad", "1.3.0", vlt_url) + }, + ]; + let before = serde_json::to_value(&state).unwrap(); + assert!(!drop_superseded_purl(&mut state, "pkg:npm/left-pad@1.3.0")); + assert_eq!( + serde_json::to_value(&state).unwrap(), + before, + "{with_record} {vlt_key}" + ); + } + } + + #[test] + fn drop_superseded_purl_drops_nothing_when_an_unclassified_edit_is_anchored() { + let mut state = RedirectState::new(); + state + .records + .insert("pkg:npm/left-pad@1.3.0".to_string(), sample_record()); + state.edits = vec![FileEdit { + key: Some("nodes/0".to_string()), + ..edit_resolved( + "future.lock", + "redirect_future_lock_entry", + "unused", + &hosted_url("left-pad", "1.3.0", SAMPLE_UUID), + ) + }]; + let before = serde_json::to_value(&state).unwrap(); + assert!(!drop_superseded_purl(&mut state, "pkg:npm/left-pad@1.3.0")); + assert_eq!(serde_json::to_value(&state).unwrap(), before); + } + + #[test] + fn drop_superseded_purl_ignores_an_unclassified_edit_for_another_package() { + let mut state = RedirectState::new(); + state + .records + .insert("pkg:npm/pad@1.3.0".to_string(), sample_record()); + state.edits = vec![ + edit( + "pnpm-lock.yaml", + "redirect_pnpm_resolution", + Some("pad@1.3.0"), + ), + vlt_node_edit( + "left-pad", + "1.3.0", + &hosted_url("left-pad", "1.3.0", "0e0e0e0e-0000-4000-8000-000000000000"), + ), + vlt_node_edit( + "@scope/pad", + "1.3.0", + &hosted_url( + "@scope/pad", + "1.3.0", + "1e1e1e1e-0000-4000-8000-000000000000", + ), + ), + ]; + assert!(drop_superseded_purl(&mut state, "pkg:npm/pad@1.3.0")); + assert!(state.records.is_empty()); + let kinds: Vec<&str> = state.edits.iter().map(|e| e.kind.as_str()).collect(); + assert_eq!(kinds, ["redirect_vlt_lock_node", "redirect_vlt_lock_node"]); + } + /// A version-boundary key (`left-pad@1.3.10`) and a different package /// whose name merely ends with the target's (`not-left-pad`) must never /// be claimed — the `/`-boundary and `(`-boundary checks are load-bearing. diff --git a/crates/socket-patch-core/src/patch/redirect/takeover.rs b/crates/socket-patch-core/src/patch/redirect/takeover.rs index a5fb403e..18d71993 100644 --- a/crates/socket-patch-core/src/patch/redirect/takeover.rs +++ b/crates/socket-patch-core/src/patch/redirect/takeover.rs @@ -130,26 +130,14 @@ fn find_record_key(state: &RedirectState, purl: &str) -> Result<(String, String) } /// Refuse a per-purl claim while the ledger holds a `redirect_*` edit this -/// release cannot classify that mentions `@`: claiming the -/// rest and dropping the record would strand that edit (half a takeover). +/// release cannot classify that names `@`: claiming the rest +/// and dropping the record would strand that edit (half a takeover). fn refuse_unclassified_edits( state: &RedirectState, name: &str, version: &str, ) -> Result<(), String> { - let needle = format!("{name}@{version}"); - let mentions = |v: &Option| match v { - Some(Value::String(s)) => s.contains(&needle), - Some(other) => other.to_string().contains(&needle), - None => false, - }; - match state.edits.iter().find(|e| { - e.kind.starts_with("redirect_") - && super::replay::is_unclassified_kind(&e.kind, &e.action) - && (e.key.as_deref().is_some_and(|k| k.contains(&needle)) - || mentions(&e.original) - || mentions(&e.new)) - }) { + match state.unclassified_edit_naming(name, version) { Some(e) => Err(format!( "the redirect ledger holds a {} edit this socket-patch release does not \ understand; upgrade socket-patch", @@ -2543,6 +2531,19 @@ mod tests { assert_eq!(state.edits[0].kind, "redirect_vlt_lock_node"); } + #[tokio::test] + async fn npm_unclassified_edit_for_a_longer_or_scoped_name_does_not_block_the_claim() { + for other in ["long-left-pad", "@scope/left-pad"] { + let (tmp, mut state) = npm_redirected_fixture("yarn.lock", &classic_pristine()).await; + state.edits.push(vlt_lock_node_edit(other, "1.3.0")); + revert_redirect_purl(tmp.path(), &mut state, NPM_PURL, false) + .await + .unwrap_or_else(|e| panic!("{other}: {e}")); + assert_eq!(state.edits.len(), 1, "{other}"); + assert_eq!(state.edits[0].kind, "redirect_vlt_lock_node", "{other}"); + } + } + #[tokio::test] async fn cargo_and_golang_claims_refuse_an_unclassified_edit_naming_them() { let tmp = tempfile::tempdir().unwrap(); From 8b578fce3773765c6405bcf4bbb4372a12704905 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Fri, 25 Sep 2026 19:50:44 -0400 Subject: [PATCH 06/46] Route npm vendor flavors through one parser repair and the vendored health check decide whether an npm flavor is known from a hand-kept list that the revert dispatch did not share, so a release adding a flavor could revert its entries yet still skip them in repair as unknown. The known set, the revert routing and the in-use probe now all come from a single string-to-flavor mapping with exhaustive matches. Assisted-by: Claude Code:claude-opus-5-5 --- .../src/vendor/npm_flavor.rs | 87 ++++++++++--------- 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/crates/socket-patch-core/src/vendor/npm_flavor.rs b/crates/socket-patch-core/src/vendor/npm_flavor.rs index 46c4d08c..1bb6a6b3 100644 --- a/crates/socket-patch-core/src/vendor/npm_flavor.rs +++ b/crates/socket-patch-core/src/vendor/npm_flavor.rs @@ -51,15 +51,6 @@ pub(crate) enum NpmLockFlavor { } impl NpmLockFlavor { - const ALL: [NpmLockFlavor; 6] = [ - NpmLockFlavor::PackageLock, - NpmLockFlavor::YarnClassic, - NpmLockFlavor::YarnBerry, - NpmLockFlavor::Pnpm, - NpmLockFlavor::PnpmLegacy, - NpmLockFlavor::Bun, - ]; - /// The stable string recorded as [`VendorEntry::flavor`]. fn as_str(self) -> &'static str { match self { @@ -71,6 +62,22 @@ impl NpmLockFlavor { NpmLockFlavor::Bun => "bun", } } + + /// The flavor a [`VendorEntry::flavor`] names, `None` for one this build + /// has no backend for. A pre-flavor ledger (`None`) is package-lock. The + /// revert and in-use dispatch go through this, so every flavor they can + /// route is known to [`npm_flavor_is_known`]. + fn from_recorded(flavor: Option<&str>) -> Option { + match flavor { + None | Some("package-lock") => Some(NpmLockFlavor::PackageLock), + Some("yarn-classic") => Some(NpmLockFlavor::YarnClassic), + Some("yarn-berry") => Some(NpmLockFlavor::YarnBerry), + Some("pnpm") => Some(NpmLockFlavor::Pnpm), + Some(pnpm_lock_legacy::FLAVOR) => Some(NpmLockFlavor::PnpmLegacy), + Some("bun") => Some(NpmLockFlavor::Bun), + Some(_) => None, + } + } } /// Yarn berry Plug'n'Play loaders: packages live inside `.yarn/cache/` zips, @@ -397,9 +404,9 @@ pub async fn vendor_npm_any( /// fail-safe. Detached entries are wired into the lock exactly like /// manifest-tracked ones, so the probe applies to every entry. pub async fn vendored_entry_in_use(entry: &VendorEntry, project_root: &Path) -> Option { - match entry.flavor.as_deref() { - Some("pnpm") => pnpm_lock::pnpm_entry_in_use(entry, project_root).await, - Some("pnpm-legacy") => { + match NpmLockFlavor::from_recorded(entry.flavor.as_deref())? { + NpmLockFlavor::Pnpm => pnpm_lock::pnpm_entry_in_use(entry, project_root).await, + NpmLockFlavor::PnpmLegacy => { pnpm_lock_legacy::pnpm_legacy_entry_in_use(entry, project_root).await } // The remaining flavors wire resolutions into the lock itself @@ -408,13 +415,13 @@ pub async fn vendored_entry_in_use(entry: &VendorEntry, project_root: &Path) -> // resolution still points at the artifact. Both npm locks are // probed: npm <= 11 installs from the shrinkwrap, npm 12 from the // package-lock beside it. - None | Some("package-lock") => { + NpmLockFlavor::PackageLock => { lock_text_mentions_uuid(project_root, &NPM_LOCKS, &entry.uuid).await } - Some("yarn-classic") | Some("yarn-berry") => { + NpmLockFlavor::YarnClassic | NpmLockFlavor::YarnBerry => { lock_text_mentions_uuid(project_root, &["yarn.lock"], &entry.uuid).await } - Some("bun") => { + NpmLockFlavor::Bun => { if super::lock_inventory::bun::bun_text_lock_present(project_root).await { return lock_text_mentions_uuid(project_root, &[BUN_LOCK], &entry.uuid).await; } @@ -431,7 +438,6 @@ pub async fn vendored_entry_in_use(entry: &VendorEntry, project_root: &Path) -> .any(|package| package.resolution.contains(&needle)), ) } - Some(_) => None, // unknown flavor: cannot determine } } @@ -468,7 +474,7 @@ pub(super) async fn lock_text_mentions_uuid( /// wired by a newer socket-patch, so health checks and rebuilds must not /// judge it by this build's layout rules. pub fn npm_flavor_is_known(flavor: Option<&str>) -> bool { - flavor.is_none_or(|f| NpmLockFlavor::ALL.iter().any(|known| known.as_str() == f)) + NpmLockFlavor::from_recorded(flavor).is_some() } /// Revert one recorded npm vendor entry through the flavor that wired it. @@ -490,23 +496,26 @@ pub async fn revert_npm_any_opts( project_root: &Path, opts: RevertOpts, ) -> RevertOutcome { - match entry.flavor.as_deref() { - None | Some("package-lock") => npm_lock::revert_npm_opts(entry, project_root, opts).await, - Some("yarn-classic") => { + let Some(flavor) = NpmLockFlavor::from_recorded(entry.flavor.as_deref()) else { + return RevertOutcome::failed(format!( + "this socket-patch build cannot revert npm vendor flavor `{}` — upgrade \ + socket-patch and re-run", + entry.flavor.as_deref().unwrap_or_default() + )); + }; + match flavor { + NpmLockFlavor::PackageLock => npm_lock::revert_npm_opts(entry, project_root, opts).await, + NpmLockFlavor::YarnClassic => { yarn_classic_lock::revert_yarn_classic_opts(entry, project_root, opts).await } - Some("yarn-berry") => { + NpmLockFlavor::YarnBerry => { yarn_berry_lock::revert_yarn_berry_opts(entry, project_root, opts).await } - Some("pnpm") => pnpm_lock::revert_pnpm_opts(entry, project_root, opts).await, - Some("pnpm-legacy") => { + NpmLockFlavor::Pnpm => pnpm_lock::revert_pnpm_opts(entry, project_root, opts).await, + NpmLockFlavor::PnpmLegacy => { pnpm_lock_legacy::revert_pnpm_legacy_opts(entry, project_root, opts).await } - Some("bun") => bun_lock::revert_bun_opts(entry, project_root, opts).await, - Some(other) => RevertOutcome::failed(format!( - "this socket-patch build cannot revert npm vendor flavor `{other}` — upgrade \ - socket-patch and re-run" - )), + NpmLockFlavor::Bun => bun_lock::revert_bun_opts(entry, project_root, opts).await, } } @@ -571,17 +580,17 @@ mod tests { #[test] fn flavor_strings_are_stable() { - assert_eq!( - NpmLockFlavor::ALL.map(NpmLockFlavor::as_str), - [ - "package-lock", - "yarn-classic", - "yarn-berry", - "pnpm", - "pnpm-legacy", - "bun" - ] - ); + use NpmLockFlavor::*; + for flavor in [PackageLock, YarnClassic, YarnBerry, Pnpm, PnpmLegacy, Bun] { + match flavor { + PackageLock | YarnClassic | YarnBerry | Pnpm | PnpmLegacy | Bun => {} + } + assert_eq!( + NpmLockFlavor::from_recorded(Some(flavor.as_str())), + Some(flavor) + ); + } + assert_eq!(NpmLockFlavor::from_recorded(None), Some(PackageLock)); assert_eq!(NpmLockFlavor::PackageLock.as_str(), "package-lock"); assert_eq!(NpmLockFlavor::YarnClassic.as_str(), "yarn-classic"); assert_eq!(NpmLockFlavor::Pnpm.as_str(), "pnpm"); From 4aa2d1dbf888ca5b41ce36dd442b8bc3fa3686d8 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Fri, 25 Sep 2026 20:18:21 -0400 Subject: [PATCH 07/46] Add vlt lockfile text and DepID primitives Lays the groundwork for vlt support without changing any behavior yet. socket-patch can now decode vlt's package ids in both lockfile encodings (vlt up to 1.0.0-rc.14 and every later release), recognize vlt's default registry the way vlt does, read vlt-lock.json strictly (a BOM, a non-object or an unknown lockfileVersion is refused, never parsed around), and read and write its one-entry-per-line node and edge lines without re-serializing the file. It also reproduces vlt's own lockfile ordering, pinned by a golden generated with Node's collator, so later vendored edits leave a lock that vlt ci keeps byte-identical. Fixture directories that hold tables rather than projects are kept out of the VEX golden corpus. Assisted-by: Claude Code:claude-opus-5-5 --- crates/socket-patch-core/src/constants.rs | 15 + crates/socket-patch-core/src/vendor/mod.rs | 2 + .../src/vendor/vlt_lock_text.rs | 2321 +++++++++++++++++ .../src/vex/discover/testing/golden.rs | 14 + .../tests/fixtures/vlt/collation-golden.json | 1675 ++++++++++++ scripts/gen-vlt-collation-golden.mjs | 244 ++ 6 files changed, 4271 insertions(+) create mode 100644 crates/socket-patch-core/src/vendor/vlt_lock_text.rs create mode 100644 crates/socket-patch-core/tests/fixtures/vlt/collation-golden.json create mode 100644 scripts/gen-vlt-collation-golden.mjs diff --git a/crates/socket-patch-core/src/constants.rs b/crates/socket-patch-core/src/constants.rs index c9009e80..8aa1a1ae 100644 --- a/crates/socket-patch-core/src/constants.rs +++ b/crates/socket-patch-core/src/constants.rs @@ -194,4 +194,19 @@ pub mod npm_family { pub const BUN_LOCK: &str = "bun.lock"; /// Bun's binary lock. pub const BUN_LOCKB: &str = "bun.lockb"; + /// vlt's committed lock (the root one only; a nested lock is its own + /// project). + pub const VLT_LOCK: &str = "vlt-lock.json"; + /// vlt's project config, read-only for every mode. + pub const VLT_CONFIG: &str = "vlt.json"; + /// vlt's hidden lock, the installed graph vlt trusts without re-checking. + pub const VLT_HIDDEN_LOCK_REL: &str = "node_modules/.vlt-lock.json"; + /// vlt's per-project package store, one `/` entry per node. + pub const VLT_STORE_DIR: &str = "node_modules/.vlt"; + /// Workspace globs of vlt <= 0.0.0-12 (`{"packages": ...}`). + pub const VLT_LEGACY_WORKSPACES: &str = "vlt-workspaces.json"; + /// Any one in the cwd makes setup treat the project as vlt's + /// (`VLT_STORE_DIR` only as a directory). + pub const VLT_SETUP_MARKERS: [&str; 4] = + [VLT_LOCK, VLT_CONFIG, VLT_HIDDEN_LOCK_REL, VLT_STORE_DIR]; } diff --git a/crates/socket-patch-core/src/vendor/mod.rs b/crates/socket-patch-core/src/vendor/mod.rs index 0421c09a..da961b13 100644 --- a/crates/socket-patch-core/src/vendor/mod.rs +++ b/crates/socket-patch-core/src/vendor/mod.rs @@ -92,6 +92,8 @@ pub(crate) mod service_fetch; pub(crate) mod test_support; mod toml_surgery; pub(crate) mod verify; +#[allow(dead_code)] +pub(crate) mod vlt_lock_text; pub(crate) mod yarn_berry_lock; pub(crate) mod yarn_classic_lock; #[cfg(test)] diff --git a/crates/socket-patch-core/src/vendor/vlt_lock_text.rs b/crates/socket-patch-core/src/vendor/vlt_lock_text.rs new file mode 100644 index 00000000..fc929a55 --- /dev/null +++ b/crates/socket-patch-core/src/vendor/vlt_lock_text.rs @@ -0,0 +1,2321 @@ +//! Text primitives for vlt's `vlt-lock.json`, shared by the hosted +//! rewriter, the vendored backend, the crawler, lock inventory and VEX. +//! +//! vlt writes one node or edge per line (`save.ts` `extraFormat`), so every +//! write is a line splice under the strict grammar here; nothing is +//! re-serialized. DepIDs come in two encodings, decided by prefix only: +//! legacy (`·`, `encodeURIComponent` with `@` raw and `/` as `§`, vlt ≤ +//! 1.0.0-rc.14) and tilde (`~`, `_X` escapes with `/` as `+`). The codec rows +//! in the tests are shared verbatim with depscan's `vlt-dep-id.test.ts`. + +use std::cmp::Ordering; +use std::sync::LazyLock; + +use regex::Regex; +use serde_json::{Map, Value}; + +use crate::patch::path_safety::is_canonical_uuid; +use crate::utils::uri::encode_uri_component; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum DepIdEra { + Legacy, + Tilde, +} + +impl DepIdEra { + pub(crate) fn delimiter(self) -> char { + match self { + DepIdEra::Legacy => '·', + DepIdEra::Tilde => '~', + } + } + + /// The grammar vlt uses for ids it writes into a lock of this version: + /// `1` is tilde, `0` and an absent version are legacy. + pub(crate) fn for_lockfile_version(version: Option) -> Self { + if version == Some(1) { + DepIdEra::Tilde + } else { + DepIdEra::Legacy + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum DepIdKind { + Registry, + Git, + File, + Remote, + Workspace, +} + +const PREFIXED_KINDS: [(&str, DepIdKind); 4] = [ + ("git", DepIdKind::Git), + ("file", DepIdKind::File), + ("remote", DepIdKind::Remote), + ("workspace", DepIdKind::Workspace), +]; + +/// A split DepID. `first` and `second` are decoded; `extra` is the raw +/// fourth (registry, git) or third (file, remote, workspace) field, which +/// must still decode. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct DepId { + pub(crate) era: DepIdEra, + pub(crate) kind: DepIdKind, + pub(crate) first: String, + pub(crate) second: Option, + pub(crate) extra: Option, +} + +impl DepId { + /// `(name, version)` of a registry id, from any registry segment. + pub(crate) fn registry_identity(&self) -> Option<(&str, &str)> { + match (self.kind, self.second.as_deref()) { + (DepIdKind::Registry, Some(second)) => registry_name_version(second), + _ => None, + } + } +} + +pub(crate) fn dep_id_era(id: &str) -> Option { + [DepIdEra::Legacy, DepIdEra::Tilde].into_iter().find(|era| { + let d = era.delimiter(); + id.starts_with(d) + || PREFIXED_KINDS.iter().any(|(kind, _)| { + id.strip_prefix(kind) + .is_some_and(|rest| rest.starts_with(d)) + }) + }) +} + +/// Split a DepID into its fields, following vlt's `isDepID` shape: registry +/// and git ids carry two or three fields after the type, file, remote and +/// workspace ids one or two. Any other shape, an empty required field or an +/// undecodable segment is `None`, and an undecodable id never matches. +pub(crate) fn split_dep_id(id: &str) -> Option { + let era = dep_id_era(id)?; + let fields: Vec<&str> = id.split(era.delimiter()).collect(); + let kind = match fields[0] { + "" => DepIdKind::Registry, + type_field => PREFIXED_KINDS + .iter() + .find(|(name, _)| *name == type_field) + .map(|(_, kind)| *kind)?, + }; + let first = decode_segment(fields.get(1)?, era)?; + if kind != DepIdKind::Registry && first.is_empty() { + return None; + } + let (second, extra) = match kind { + DepIdKind::Registry | DepIdKind::Git => { + if !(3..=4).contains(&fields.len()) { + return None; + } + let second = decode_segment(fields[2], era).filter(|s| !s.is_empty())?; + (Some(second), fields.get(3)) + } + DepIdKind::File | DepIdKind::Remote | DepIdKind::Workspace => { + if fields.len() > 3 { + return None; + } + (None, fields.get(2)) + } + }; + if let Some(extra) = extra { + decode_segment(extra, era)?; + } + Some(DepId { + era, + kind, + first, + second, + extra: extra.map(|e| (*e).to_string()), + }) +} + +/// The `file` DepID vlt writes for a project-relative directory. +pub(crate) fn file_dep_id(path: &str, era: DepIdEra) -> String { + format!("file{}{}", era.delimiter(), encode_segment(path, era)) +} + +/// Encode one DepID segment the way vlt spells lock keys and `.vlt/` +/// store dir names. +pub(crate) fn encode_segment(s: &str, era: DepIdEra) -> String { + match era { + DepIdEra::Tilde => encode_tilde(s), + DepIdEra::Legacy => encode_uri_component(s) + .replace("%40", "@") + .replace("%2F", "§"), + } +} + +/// Decode one DepID segment; `None` when a legacy segment holds a malformed +/// `%` escape or escapes that are not UTF-8 (`decodeURIComponent` throws). +pub(crate) fn decode_segment(s: &str, era: DepIdEra) -> Option { + match era { + DepIdEra::Tilde => Some(decode_tilde(s)), + DepIdEra::Legacy => decode_legacy(s), + } +} + +fn tilde_escape(c: char) -> Option<&'static str> { + Some(match c { + '_' => "__", + '+' => "_p", + '\\' => "_b", + ':' => "_c", + '~' => "_t", + '<' => "_l", + '>' => "_g", + '"' => "_q", + '|' => "_i", + '?' => "_m", + '*' => "_a", + ' ' => "_s", + _ => return None, + }) +} + +fn tilde_unescape(c: char) -> Option { + Some(match c { + '_' => '_', + 'p' => '+', + 'b' => '\\', + 'c' => ':', + 't' => '~', + 'l' => '<', + 'g' => '>', + 'q' => '"', + 'i' => '|', + 'm' => '?', + 'a' => '*', + 'd' => '.', + 's' => ' ', + _ => return None, + }) +} + +fn encode_tilde(s: &str) -> String { + let mut out = String::with_capacity(s.len()); + for c in s.chars() { + if c == '/' { + out.push('+'); + } else if let Some(escaped) = tilde_escape(c) { + out.push_str(escaped); + } else if (c as u32) <= 0x1f { + out.push_str(&format!("_{:02X}", c as u32)); + } else { + out.push(c); + } + } + if out.ends_with('.') { + out.pop(); + out.push_str("_d"); + } + out +} + +fn decode_tilde(s: &str) -> String { + let chars: Vec = s.chars().collect(); + let mut out = String::with_capacity(s.len()); + let mut i = 0; + while i < chars.len() { + let c = chars[i]; + if c == '+' { + out.push('/'); + i += 1; + continue; + } + if c != '_' { + out.push(c); + i += 1; + continue; + } + if let Some(unescaped) = chars.get(i + 1).and_then(|n| tilde_unescape(*n)) { + out.push(unescaped); + i += 2; + continue; + } + if let (Some(high @ ('0' | '1')), Some(low)) = (chars.get(i + 1), chars.get(i + 2)) { + if let Some(low) = low.to_digit(16) { + let high = high.to_digit(16).unwrap_or_default(); + out.push(char::from((high * 16 + low) as u8)); + i += 3; + continue; + } + } + out.push('_'); + i += 1; + } + out +} + +/// `decodeURIComponent(s.replaceAll('@','%40').replaceAll('§','%2F'))`, +/// validating every `%XX` itself: a lenient percent decoder would pass a +/// malformed escape through where JS throws. +fn decode_legacy(s: &str) -> Option { + let s = s.replace('§', "%2F"); + let bytes = s.as_bytes(); + let mut out = Vec::with_capacity(bytes.len()); + let mut i = 0; + while i < bytes.len() { + if bytes[i] == b'%' { + let high = char::from(*bytes.get(i + 1)?).to_digit(16)?; + let low = char::from(*bytes.get(i + 2)?).to_digit(16)?; + out.push((high * 16 + low) as u8); + i += 3; + } else { + out.push(bytes[i]); + i += 1; + } + } + String::from_utf8(out).ok() +} + +static SEMVER_RE: LazyLock = LazyLock::new(|| { + Regex::new( + r"^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-((?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$", + ) + .expect("semver regex") +}); + +const NPM_NAME_MAX_LENGTH: usize = 214; + +/// The semver.org 2.0.0 grammar exactly (build metadata allowed; no `v`, +/// `=`, ranges or leading zeros), the same regex as the TS twin. +pub(crate) fn is_strict_semver(version: &str) -> bool { + SEMVER_RE.is_match(version) +} + +fn is_npm_name_part(part: &str) -> bool { + let unreserved = |c: char| c.is_ascii_alphanumeric() || "-~!*'()".contains(c); + let mut chars = part.chars(); + chars.next().is_some_and(unreserved) && chars.all(|c| unreserved(c) || c == '.' || c == '_') +} + +/// npm's name rule for existing packages: an optional `@scope/` plus a name, +/// URL-safe characters only, no leading `.` or `_`, at most 214 chars. +pub(crate) fn is_registry_package_name(name: &str) -> bool { + if name.len() > NPM_NAME_MAX_LENGTH { + return false; + } + match name.strip_prefix('@') { + None => is_npm_name_part(name), + Some(scoped) => scoped + .split_once('/') + .is_some_and(|(scope, bare)| is_npm_name_part(scope) && is_npm_name_part(bare)), + } +} + +/// `(name, version)` from a registry id's decoded `name@version`, split at +/// the last `@` past index 0. The DepID version is authoritative for +/// identity, so a non-semver version gives `None`. +pub(crate) fn registry_name_version(second: &str) -> Option<(&str, &str)> { + let at = second.rfind('@').filter(|&i| i > 0)?; + let (name, version) = (&second[..at], &second[at + 1..]); + (is_registry_package_name(name) && is_strict_semver(version)).then_some((name, version)) +} + +/// The store decoder: `(full name, version)` of a `.vlt/` entry, +/// from any registry segment (the store holds every installed copy) and +/// ignoring the extra. Git, remote, file, workspace and undecodable ids are +/// `None`; the crawler still probes those by package.json. +pub(crate) fn decode_vlt_dep_id(dir_name: &str) -> Option<(String, String)> { + let id = split_dep_id(dir_name)?; + let (name, version) = id.registry_identity()?; + Some((name.to_string(), version.to_string())) +} + +fn with_trailing_slash(url: &str) -> String { + if url.ends_with('/') { + url.to_string() + } else { + format!("{url}/") + } +} + +/// Does a decoded registry segment name vlt's default registry, given the +/// lock's `options` (vlt `usesDefaultRegistry`)? `''`, the default alias +/// (`default-registry-alias`, else `npm`), an alias whose URL is +/// `options.registry`, or `options.registry` itself as a URL segment. +/// Every other segment (named aliases, scoped registries, jsr) is foreign. +pub(crate) fn is_default_registry(segment: &str, options: Option<&Map>) -> bool { + if segment.is_empty() { + return true; + } + let alias = match options.and_then(|o| o.get("default-registry-alias")) { + None | Some(Value::Null) => Some("npm"), + Some(Value::String(alias)) => Some(alias.as_str()), + Some(_) => None, + }; + if alias == Some(segment) { + return true; + } + let Some(registry) = options + .and_then(|o| o.get("registry")) + .and_then(Value::as_str) + else { + return false; + }; + let registry = with_trailing_slash(registry); + let aliased = options + .and_then(|o| o.get("registries")) + .and_then(|r| r.get(segment)) + .and_then(Value::as_str); + if aliased.is_some_and(|url| with_trailing_slash(url) == registry) { + return true; + } + reqwest::Url::parse(segment).is_ok_and(|url| matches!(url.scheme(), "http" | "https")) + && with_trailing_slash(segment) == registry +} + +// ── lock-level sniff ───────────────────────────────────────────────────── + +/// A `vlt-lock.json` that parsed as a JSON object with a known version. +#[derive(Debug, Clone)] +pub(crate) struct ParsedLock { + /// `None` when `lockfileVersion` is absent (vlt ≤ 0.0.0-18). + pub(crate) version: Option, + pub(crate) json: Map, +} + +impl ParsedLock { + pub(crate) fn options(&self) -> Option<&Map> { + self.json.get("options").and_then(Value::as_object) + } + + pub(crate) fn nodes(&self) -> Option<&Map> { + self.json.get("nodes").and_then(Value::as_object) + } + + pub(crate) fn edges(&self) -> Option<&Map> { + self.json.get("edges").and_then(Value::as_object) + } + + /// The grammar for ids written into this lock. + pub(crate) fn new_id_era(&self) -> DepIdEra { + DepIdEra::for_lockfile_version(self.version) + } +} + +/// What reading a `vlt-lock.json` found. vlt itself cannot read a BOM or a +/// non-object, and fails on any version but `0` and `1`, so none of those +/// is ever parsed around. +#[derive(Debug, Clone)] +pub(crate) enum LockSniff { + Readable(ParsedLock), + /// Starts with U+FEFF; never stripped. + Bom, + /// Not a JSON object. serde_json also refuses a lone surrogate escape, + /// which JS `JSON.parse` would accept. + NotJsonObject, + /// `lockfileVersion` is present but not the integer token `0` or `1` + /// (`1.0`, `1e0`, `"1"`, `2`, `null`, ...), rendered as JSON. + UnsupportedVersion(String), +} + +pub(crate) fn sniff_lock(text: &str) -> LockSniff { + if text.starts_with('\u{feff}') { + return LockSniff::Bom; + } + let Ok(Value::Object(json)) = serde_json::from_str::(text) else { + return LockSniff::NotJsonObject; + }; + let version = match json.get("lockfileVersion") { + None => None, + Some(v) => match v.as_u64() { + Some(n @ (0 | 1)) => Some(n), + _ => return LockSniff::UnsupportedVersion(v.to_string()), + }, + }; + LockSniff::Readable(ParsedLock { version, json }) +} + +// ── sections ───────────────────────────────────────────────────────────── + +/// The lines of a lock, split on `\n`; each keeps its own trailing `\r`, +/// and joining with `\n` gives the text back. +pub(crate) fn split_lines(text: &str) -> Vec<&str> { + text.split('\n').collect() +} + +fn strip_cr(line: &str) -> (&str, bool) { + match line.strip_suffix('\r') { + Some(body) => (body, true), + None => (line, false), + } +} + +/// Where a top-level `nodes` or `edges` section sits in the lock's lines. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum SectionSpan { + /// The one-line empty form ` "nodes": {},`. + Inline { line: usize }, + /// ` "nodes": {` at `open`, entries strictly between, ` },` at `close`. + Block { open: usize, close: usize }, +} + +impl SectionSpan { + pub(crate) fn entry_lines(self) -> std::ops::Range { + match self { + SectionSpan::Inline { line } => line + 1..line + 1, + SectionSpan::Block { open, close } => open + 1..close, + } + } +} + +fn locate_section(lines: &[&str], name: &str) -> Option { + let header = format!(" \"{name}\": {{"); + let (open, rest) = lines.iter().enumerate().find_map(|(i, line)| { + let (body, _) = strip_cr(line); + body.strip_prefix(header.as_str()).map(|rest| (i, rest)) + })?; + match rest { + "" => lines + .iter() + .enumerate() + .skip(open + 1) + .find(|(_, line)| matches!(strip_cr(line).0, " }" | " },")) + .map(|(close, _)| SectionSpan::Block { open, close }), + "}" | "}," => Some(SectionSpan::Inline { line: open }), + _ => None, + } +} + +/// The nodes section in vlt's canonical layout. `None` when absent or laid +/// out any other way; a caller whose parsed lock has nodes refuses then. +pub(crate) fn nodes_block(lines: &[&str]) -> Option { + locate_section(lines, "nodes") +} + +pub(crate) fn edges_block(lines: &[&str]) -> Option { + locate_section(lines, "edges") +} + +// ── entry lines ────────────────────────────────────────────────────────── + +const ENTRY_INDENT: &str = " "; + +/// Split entry text `"": ` whose key has no JSON escapes. +fn split_entry_text(text: &str) -> Option<(&str, &str)> { + let rest = text.strip_prefix('"')?; + let close = rest.find(['"', '\\'])?; + if rest.as_bytes()[close] != b'"' { + return None; + } + let value = rest[close + 1..].strip_prefix(": ")?; + Some((&rest[..close], value)) +} + +/// `(entry text, comma, cr)` of a 4-space-indented entry line. +fn split_entry_line(line: &str) -> Option<(&str, bool, bool)> { + let (body, cr) = strip_cr(line); + let body = body.strip_prefix(ENTRY_INDENT)?; + if !body.starts_with('"') { + return None; + } + let (text, comma) = match body.strip_suffix(',') { + Some(text) => (text, true), + None => (body, false), + }; + Some((text, comma, cr)) +} + +/// Render an entry line: 4-space indent, the entry text, a comma on every +/// entry but a section's last, and the line's own `\r`. +pub(crate) fn render_entry_line(entry_text: &str, comma: bool, cr: bool) -> String { + format!( + "{ENTRY_INDENT}{entry_text}{}{}", + if comma { "," } else { "" }, + if cr { "\r" } else { "" } + ) +} + +pub(crate) fn entry_text(key: &str, raw_value: &str) -> String { + format!("\"{key}\": {raw_value}") +} + +/// One node: `"": [E0,E1,E2?,E3?,E4…]` with every element kept as +/// its raw top-level slice. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct NodeEntry<'a> { + pub(crate) key: &'a str, + pub(crate) tuple: &'a str, + pub(crate) elems: Vec<&'a str>, +} + +impl NodeEntry<'_> { + pub(crate) fn name(&self) -> Option { + serde_json::from_str(self.elems[1]).ok() + } + + /// `E2`/`E3` as raw slices; `None` when the tuple is shorter. + pub(crate) fn slot(&self, index: usize) -> Option<&str> { + self.elems.get(index).copied() + } + + pub(crate) fn entry_text(&self) -> String { + entry_text(self.key, self.tuple) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct NodeLine<'a> { + pub(crate) entry: NodeEntry<'a>, + pub(crate) comma: bool, + pub(crate) cr: bool, +} + +pub(crate) fn parse_node_line(line: &str) -> Option> { + let (text, comma, cr) = split_entry_line(line)?; + Some(NodeLine { + entry: parse_node_entry_text(text)?, + comma, + cr, + }) +} + +/// Parse node entry text (a line minus indent, comma and `\r`), the form +/// ledgers record. +pub(crate) fn parse_node_entry_text(text: &str) -> Option> { + let (key, tuple) = split_entry_text(text)?; + let elems = split_tuple_elements(tuple)?; + let is_string = |raw: &str| raw.starts_with('"') && serde_json::from_str::(raw).is_ok(); + let string_or_null = |raw: &&str| *raw == "null" || is_string(raw); + let well_formed = elems.len() >= 2 + && matches!(elems[0], "0" | "1" | "2" | "3") + && is_string(elems[1]) + && elems.get(2).is_none_or(string_or_null) + && elems.get(3).is_none_or(string_or_null); + well_formed.then_some(NodeEntry { key, tuple, elems }) +} + +/// Raw top-level elements of a tuple `[e0,e1,…]`: the text must parse as a +/// JSON array, and elements are separated by exactly one `,` with no +/// surrounding whitespace. +pub(crate) fn split_tuple_elements(tuple: &str) -> Option> { + let parsed = serde_json::from_str::(tuple).ok()?; + let expected = parsed.as_array()?.len(); + let interior = tuple.strip_prefix('[')?.strip_suffix(']')?; + let bytes = interior.as_bytes(); + let mut elems = Vec::new(); + let mut depth = 0usize; + let mut in_string = false; + let mut start = 0; + let mut i = 0; + while i < bytes.len() { + let b = bytes[i]; + if in_string { + match b { + b'\\' => i += 1, + b'"' => in_string = false, + _ => {} + } + } else { + match b { + b'"' => in_string = true, + b'[' | b'{' => depth += 1, + b']' | b'}' => depth = depth.checked_sub(1)?, + b',' if depth == 0 => { + elems.push(&interior[start..i]); + start = i + 1; + } + _ => {} + } + } + i += 1; + } + if !interior.is_empty() { + elems.push(&interior[start..]); + } + let tight = |e: &&str| { + !e.is_empty() + && !e.starts_with(|c: char| c.is_ascii_whitespace()) + && !e.ends_with(|c: char| c.is_ascii_whitespace()) + }; + (elems.len() == expected && elems.iter().all(tight)).then_some(elems) +} + +/// A tuple with slots [2] and [3] replaced and `E0`, `E1`, `E4…` kept raw. +/// An absent slot is `null` when a later element follows and is dropped +/// otherwise, which is how vlt lays out unused slots. +pub(crate) fn render_tuple_with_slots( + elems: &[&str], + slot2: Option<&str>, + slot3: Option<&str>, +) -> String { + let tail = elems.get(4..).unwrap_or_default(); + let mut out: Vec<&str> = elems.iter().take(2).copied().collect(); + let slots = [slot2, slot3]; + let kept = if tail.is_empty() { + slots.iter().rposition(Option::is_some).map_or(0, |i| i + 1) + } else { + 2 + }; + out.extend(slots[..kept].iter().map(|s| s.unwrap_or("null"))); + out.extend_from_slice(tail); + format!("[{}]", out.join(",")) +} + +pub(crate) const EDGE_TYPES: [&str; 5] = ["prod", "dev", "optional", "peer", "peerOptional"]; + +/// One edge: `" ": " "`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct EdgeEntry<'a> { + pub(crate) key: &'a str, + pub(crate) raw_value: &'a str, + pub(crate) value: String, +} + +impl EdgeEntry<'_> { + pub(crate) fn from(&self) -> &str { + self.key.split_once(' ').map_or(self.key, |(from, _)| from) + } + + /// The dependency name (an alias when the spec is `npm:`). + pub(crate) fn dep_name(&self) -> &str { + self.key.split_once(' ').map_or("", |(_, name)| name) + } + + pub(crate) fn edge_type(&self) -> &str { + self.value.split_once(' ').map_or("", |(ty, _)| ty) + } + + /// The bare spec, which may itself contain spaces. + pub(crate) fn spec(&self) -> &str { + let (_, rest) = self.value.split_once(' ').unwrap_or_default(); + rest.rsplit_once(' ').map_or("", |(spec, _)| spec) + } + + /// The target DepID, or `MISSING`. + pub(crate) fn target(&self) -> &str { + self.value.rsplit_once(' ').map_or("", |(_, to)| to) + } + + pub(crate) fn entry_text(&self) -> String { + entry_text(self.key, self.raw_value) + } + + pub(crate) fn sort_key(&self) -> EdgeSortKey<'_> { + EdgeSortKey { + from: self.from(), + edge_type: self.edge_type(), + to: self.target(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct EdgeLine<'a> { + pub(crate) entry: EdgeEntry<'a>, + pub(crate) comma: bool, + pub(crate) cr: bool, +} + +pub(crate) fn parse_edge_line(line: &str) -> Option> { + let (text, comma, cr) = split_entry_line(line)?; + Some(EdgeLine { + entry: parse_edge_entry_text(text)?, + comma, + cr, + }) +} + +pub(crate) fn parse_edge_entry_text(text: &str) -> Option> { + let (key, raw_value) = split_entry_text(text)?; + let (from, name) = key.split_once(' ')?; + if from.is_empty() + || name.is_empty() + || !raw_value.starts_with('"') + || !raw_value.ends_with('"') + { + return None; + } + let value: String = serde_json::from_str(raw_value).ok()?; + let entry = EdgeEntry { + key, + raw_value, + value, + }; + let (_, rest) = entry.value.split_once(' ')?; + let (spec, to) = rest.rsplit_once(' ')?; + let well_formed = EDGE_TYPES.contains(&entry.edge_type()) && !spec.is_empty() && !to.is_empty(); + well_formed.then_some(entry) +} + +/// Root (`file~_d`, `file·.`) and workspace (`workspace~…`, `workspace·…`) +/// importers: edge sources that are never nodes. +pub(crate) fn is_importer_dep_id(id: &str) -> bool { + id == "file~_d" + || id == "file·." + || id.strip_prefix("workspace~").is_some_and(|p| !p.is_empty()) + || id.strip_prefix("workspace·").is_some_and(|p| !p.is_empty()) +} + +// ── ordering ───────────────────────────────────────────────────────────── + +/// The primary order of vlt's `localeCompare(…, 'en')` over the DepID +/// alphabet (Node 24.21 ICU), lowest first; ASCII letters share a primary +/// with their uppercase twin. Pinned by `tests/fixtures/vlt/collation-golden.json`. +const COLLATION_PRIMARY: &str = + " _-,;:!?.·'\"()[]{}§@*/\\&#%`^+<=>|~$0123456789abcdefghijklmnopqrstuvwxyz"; + +fn collation_weight(c: char) -> Option<(usize, bool)> { + let upper = c.is_ascii_uppercase(); + let folded = c.to_ascii_lowercase(); + COLLATION_PRIMARY + .chars() + .position(|p| p == folded) + .map(|primary| (primary, upper)) +} + +/// vlt's `a.localeCompare(b, 'en')`, restricted to the collation table: +/// primary weights first (a shorter prefix sorts first), then case +/// position by position, lowercase first. `None` is "Unknown": a character +/// outside the table (a tilde-era `file:` path with non-ASCII, say), for +/// which callers fall back to in-place placement. +pub(crate) fn vlt_collate(a: &str, b: &str) -> Option { + let weigh = |s: &str| s.chars().map(collation_weight).collect::>>(); + let (wa, wb) = (weigh(a)?, weigh(b)?); + let primary = wa.iter().map(|w| w.0).cmp(wb.iter().map(|w| w.0)); + Some(primary.then_with(|| wa.iter().map(|w| w.1).cmp(wb.iter().map(|w| w.1)))) +} + +/// The fields vlt's `formatEdges` sorts by. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct EdgeSortKey<'a> { + pub(crate) from: &'a str, + pub(crate) edge_type: &'a str, + /// A target DepID or `MISSING`; vlt sorts a missing target as `''`. + pub(crate) to: &'a str, +} + +fn missing_as_empty(to: &str) -> &str { + if to == "MISSING" { + "" + } else { + to + } +} + +/// vlt's edge order: importer sources first, then `from`, `type` and `to` +/// by [`vlt_collate`]. The edge name is not a key. `None` when a needed +/// comparison is Unknown. +pub(crate) fn vlt_edge_cmp(a: EdgeSortKey<'_>, b: EdgeSortKey<'_>) -> Option { + let importer = is_importer_dep_id(b.from).cmp(&is_importer_dep_id(a.from)); + if importer != Ordering::Equal { + return Some(importer); + } + for (x, y) in [ + (a.from, b.from), + (a.edge_type, b.edge_type), + (missing_as_empty(a.to), missing_as_empty(b.to)), + ] { + match vlt_collate(x, y)? { + Ordering::Equal => {} + decided => return Some(decided), + } + } + Some(Ordering::Equal) +} + +// ── vendored path rule ─────────────────────────────────────────────────── + +const VENDOR_NPM_PREFIX: &str = ".socket/vendor/npm/"; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum VendoredShape { + /// `/[@s/]-/node_modules/[@s/]`, the directory + /// artifact socket-patch writes for vlt. + Dir, + /// `/[@s/]-.tgz`, an npm-flavor artifact a user + /// installed with vlt (read-only recognition). + Tgz, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct VendoredPath { + pub(crate) uuid: String, + pub(crate) name: String, + pub(crate) version: String, + pub(crate) shape: VendoredShape, +} + +fn leaf_version<'l>(bare: &str, leaf: &'l str) -> Option<&'l str> { + leaf.strip_prefix(bare)? + .strip_prefix('-') + .filter(|v| is_strict_semver(v)) +} + +/// A decoded `file` path of the vendored directory shape, with the name +/// read from its `node_modules/` segments. +pub(crate) fn parse_vendored_dir_path(path: &str) -> Option { + let segments: Vec<&str> = path.strip_prefix(VENDOR_NPM_PREFIX)?.split('/').collect(); + let (uuid, scope, leaf, bare) = match segments.as_slice() { + [uuid, leaf, "node_modules", bare] => (*uuid, None, *leaf, *bare), + [uuid, scope, leaf, "node_modules", scope_again, bare] if scope == scope_again => { + (*uuid, Some(*scope), *leaf, *bare) + } + _ => return None, + }; + let name = match scope { + Some(scope) if scope.starts_with('@') => format!("{scope}/{bare}"), + Some(_) => return None, + None => bare.to_string(), + }; + if !is_canonical_uuid(uuid) || !is_registry_package_name(&name) { + return None; + } + Some(VendoredPath { + uuid: uuid.to_string(), + version: leaf_version(bare, leaf)?.to_string(), + name, + shape: VendoredShape::Dir, + }) +} + +/// The vendored path rule (hosted matching, vendored target analysis, VEX, +/// depscan): is a decoded `file` path a vendored vlt artifact of `name`? +pub(crate) fn parse_vendored_path(path: &str, name: &str) -> Option { + if let Some(dir) = parse_vendored_dir_path(path) { + return (dir.name == name).then_some(dir); + } + if !is_registry_package_name(name) { + return None; + } + let rest = path.strip_prefix(VENDOR_NPM_PREFIX)?.strip_suffix(".tgz")?; + let (uuid, leaf) = rest.split_once('/')?; + let (leaf_scope, leaf) = match leaf.split_once('/') { + Some((scope, leaf)) => (Some(scope), leaf), + None => (None, leaf), + }; + let (name_scope, bare) = match name.split_once('/') { + Some((scope, bare)) => (Some(scope), bare), + None => (None, name), + }; + if leaf_scope != name_scope || !is_canonical_uuid(uuid) { + return None; + } + Some(VendoredPath { + uuid: uuid.to_string(), + name: name.to_string(), + version: leaf_version(bare, leaf)?.to_string(), + shape: VendoredShape::Tgz, + }) +} + +/// `rel` of the vendored directory artifact: +/// `.socket/vendor/npm//[@s/]-/node_modules/`. +pub(crate) fn vendored_dir_rel(uuid: &str, name: &str, version: &str) -> String { + let leaf = match name.split_once('/') { + Some((scope, bare)) => format!("{scope}/{bare}-{version}"), + None => format!("{name}-{version}"), + }; + format!("{VENDOR_NPM_PREFIX}{uuid}/{leaf}/node_modules/{name}") +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::constants::npm_family::{ + VLT_CONFIG, VLT_HIDDEN_LOCK_REL, VLT_LEGACY_WORKSPACES, VLT_LOCK, VLT_SETUP_MARKERS, + VLT_STORE_DIR, + }; + + const UUID: &str = "0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b"; + + use DepIdEra::{Legacy, Tilde}; + use DepIdKind::{File, Git, Registry, Remote, Workspace}; + + type Split = ( + DepIdEra, + DepIdKind, + &'static str, + Option<&'static str>, + Option<&'static str>, + ); + + struct CodecRow { + id: String, + split: Option, + identity: Option<(&'static str, &'static str)>, + } + + fn row( + id: impl Into, + split: Option, + identity: Option<(&'static str, &'static str)>, + ) -> CodecRow { + CodecRow { + id: id.into(), + split, + identity, + } + } + + fn reg(era: DepIdEra, first: &'static str, second: &'static str) -> Option { + Some((era, Registry, first, Some(second), None)) + } + + fn reg_x( + era: DepIdEra, + first: &'static str, + second: &'static str, + extra: &'static str, + ) -> Option { + Some((era, Registry, first, Some(second), Some(extra))) + } + + fn typed(era: DepIdEra, kind: DepIdKind, first: &'static str) -> Option { + Some((era, kind, first, None, None)) + } + + // Verbatim from depscan `workspaces/lib/src/socket-patch/vlt-dep-id.test.ts` + // CODEC_ROWS (cross-checked there against vlt 1.2.0 and rc.14 + // `splitDepID`). Its lone-surrogate row has no Rust `&str` spelling. + fn codec_rows() -> Vec { + let d19 = |base: &str| base.replace("", UUID); + vec![ + row("··ms@2.1.3", reg(Legacy, "", "ms@2.1.3"), Some(("ms", "2.1.3"))), + row( + "·npm·@isaacs§string-locale-compare@1.1.0", + reg(Legacy, "npm", "@isaacs/string-locale-compare@1.1.0"), + Some(("@isaacs/string-locale-compare", "1.1.0")), + ), + row( + "··@sindresorhus§is@4.6.0", + reg(Legacy, "", "@sindresorhus/is@4.6.0"), + Some(("@sindresorhus/is", "4.6.0")), + ), + row( + "·npm·u@1.0.0%2Bbuild.1", + reg(Legacy, "npm", "u@1.0.0+build.1"), + Some(("u", "1.0.0+build.1")), + ), + row( + "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + reg_x(Legacy, "", "ms@2.1.3", "%3Aroot%20%3E%20%23debug%20%3E%20%23ms"), + Some(("ms", "2.1.3")), + ), + row( + "·npm·x@1.0.0·%E1%B9%97%3A3", + reg_x(Legacy, "npm", "x@1.0.0", "%E1%B9%97%3A3"), + Some(("x", "1.0.0")), + ), + row("~npm~@a+b@1.0.0", reg(Tilde, "npm", "@a/b@1.0.0"), Some(("@a/b", "1.0.0"))), + row("~npm~a__b@1.0.0", reg(Tilde, "npm", "a_b@1.0.0"), Some(("a_b", "1.0.0"))), + row( + "~npm~u@1.0.0_pbuild.1", + reg(Tilde, "npm", "u@1.0.0+build.1"), + Some(("u", "1.0.0+build.1")), + ), + row( + "~npm~is-number@6.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number", + reg_x( + Tilde, + "npm", + "is-number@6.0.0", + "_croot_s_g_s#to-regex-range_s_g_s#is-number", + ), + Some(("is-number", "6.0.0")), + ), + row( + "~npm~react-dom@18.2.0~peer.ace93b147498ef7a", + reg_x(Tilde, "npm", "react-dom@18.2.0", "peer.ace93b147498ef7a"), + Some(("react-dom", "18.2.0")), + ), + row("~npm~x@1~peer.2", reg_x(Tilde, "npm", "x@1", "peer.2"), None), + row( + "~acme~left-pad@1.3.0", + reg(Tilde, "acme", "left-pad@1.3.0"), + Some(("left-pad", "1.3.0")), + ), + row( + "~http_c++127.0.0.1_c4873+~x@1.0.0", + reg(Tilde, "http://127.0.0.1:4873/", "x@1.0.0"), + Some(("x", "1.0.0")), + ), + row( + "~jsr~@jsr+std____semver@1.0.8", + reg(Tilde, "jsr", "@jsr/std__semver@1.0.8"), + Some(("@jsr/std__semver", "1.0.8")), + ), + row( + "git~github_cuser+proj~v1.0.0", + Some((Tilde, Git, "github:user/proj", Some("v1.0.0"), None)), + None, + ), + row("file~_d", typed(Tilde, File, "."), None), + row( + "remote~https_c++e.com+r-1.0.0.tgz", + typed(Tilde, Remote, "https://e.com/r-1.0.0.tgz"), + None, + ), + row("workspace~packages+a", typed(Tilde, Workspace, "packages/a"), None), + row("··foo@1.2.3", reg(Legacy, "", "foo@1.2.3"), Some(("foo", "1.2.3"))), + row("·npm·foo@1.2.3", reg(Legacy, "npm", "foo@1.2.3"), Some(("foo", "1.2.3"))), + row("~npm~foo@1.2.3", reg(Tilde, "npm", "foo@1.2.3"), Some(("foo", "1.2.3"))), + row( + "··@scope§bar@2.0.0", + reg(Legacy, "", "@scope/bar@2.0.0"), + Some(("@scope/bar", "2.0.0")), + ), + row( + "·npm·@scope§bar@2.0.0", + reg(Legacy, "npm", "@scope/bar@2.0.0"), + Some(("@scope/bar", "2.0.0")), + ), + row( + "~npm~@scope+bar@2.0.0", + reg(Tilde, "npm", "@scope/bar@2.0.0"), + Some(("@scope/bar", "2.0.0")), + ), + row( + "··u@1.0.0%2Bbuild.1", + reg(Legacy, "", "u@1.0.0+build.1"), + Some(("u", "1.0.0+build.1")), + ), + row("·acme·y@1.0.0", reg(Legacy, "acme", "y@1.0.0"), Some(("y", "1.0.0"))), + row("~acme~y@1.0.0", reg(Tilde, "acme", "y@1.0.0"), Some(("y", "1.0.0"))), + row( + "·http%3A§§127.0.0.1%3A4873§·x@1.0.0", + reg(Legacy, "http://127.0.0.1:4873/", "x@1.0.0"), + Some(("x", "1.0.0")), + ), + row( + "·npm·ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + reg_x(Legacy, "npm", "ms@2.1.3", "%3Aroot%20%3E%20%23debug%20%3E%20%23ms"), + Some(("ms", "2.1.3")), + ), + row( + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + reg_x(Tilde, "npm", "ms@2.1.3", "_croot_s_g_s#debug_s_g_s#ms"), + Some(("ms", "2.1.3")), + ), + row("·npm·x@1·%E1%B9%97%3A3", reg_x(Legacy, "npm", "x@1", "%E1%B9%97%3A3"), None), + row( + "~npm~x@1~peer.dbd5ca8b03a66489", + reg_x(Tilde, "npm", "x@1", "peer.dbd5ca8b03a66489"), + None, + ), + row( + d19("file·.socket§vendor§npm§§left-pad-1.3.0§node_modules§left-pad"), + Some(( + Legacy, + File, + ".socket/vendor/npm/0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b/left-pad-1.3.0/node_modules/left-pad", + None, + None, + )), + None, + ), + row( + d19("file~.socket+vendor+npm++left-pad-1.3.0+node__modules+left-pad"), + Some(( + Tilde, + File, + ".socket/vendor/npm/0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b/left-pad-1.3.0/node_modules/left-pad", + None, + None, + )), + None, + ), + row( + d19("file·.socket§vendor§npm§§@sc§pkg-1.0.0§node_modules§@sc§pkg"), + Some(( + Legacy, + File, + ".socket/vendor/npm/0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b/@sc/pkg-1.0.0/node_modules/@sc/pkg", + None, + None, + )), + None, + ), + row( + d19("file~.socket+vendor+npm++@sc+pkg-1.0.0+node__modules+@sc+pkg"), + Some(( + Tilde, + File, + ".socket/vendor/npm/0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b/@sc/pkg-1.0.0/node_modules/@sc/pkg", + None, + None, + )), + None, + ), + row("workspace·packages§a", typed(Legacy, Workspace, "packages/a"), None), + row( + "remote·https%3A§§e.com§r-1.0.0.tgz", + typed(Legacy, Remote, "https://e.com/r-1.0.0.tgz"), + None, + ), + row("file·.", typed(Legacy, File, "."), None), + row( + "git·github%3Auser§proj·v1.0.0", + Some((Legacy, Git, "github:user/proj", Some("v1.0.0"), None)), + None, + ), + row("file~.+packages+a", typed(Tilde, File, "./packages/a"), None), + row("file·.§packages§a", typed(Legacy, File, "./packages/a"), None), + row("file~a~peer.1", Some((Tilde, File, "a", None, Some("peer.1"))), None), + row( + "·jsr·@jsr§std__semver@1.0.8", + reg(Legacy, "jsr", "@jsr/std__semver@1.0.8"), + Some(("@jsr/std__semver", "1.0.8")), + ), + row( + "·https%3A§§npm.jsr.io§·@jsr§std__semver@1.0.8", + reg(Legacy, "https://npm.jsr.io/", "@jsr/std__semver@1.0.8"), + Some(("@jsr/std__semver", "1.0.8")), + ), + row("~~a@1.0.0", reg(Tilde, "", "a@1.0.0"), Some(("a", "1.0.0"))), + row("~npm~a@1.0.0~", reg_x(Tilde, "npm", "a@1.0.0", ""), Some(("a", "1.0.0"))), + row("·npm·a~b@1.0.0", reg(Legacy, "npm", "a~b@1.0.0"), Some(("a~b", "1.0.0"))), + row("~npm~a·b@1.0.0", reg(Tilde, "npm", "a·b@1.0.0"), None), + row("~npm~a_@1.0.0", reg(Tilde, "npm", "a_@1.0.0"), Some(("a_", "1.0.0"))), + row("~npm~_1f_0a_zz_@1.0.0", reg(Tilde, "npm", "\u{1f}\n_zz_@1.0.0"), None), + row("·npm·a@1.0.0%ZZ", None, None), + row("·npm·a@1.0.0%4", None, None), + row("·npm·a@1.0.0%C3", None, None), + row("·npm·a@1.0.0%ED%A0%80", None, None), + row("·npm%zz·a@1.0.0", None, None), + row("··ms@2.1.3·%ZZ", None, None), + row("file·.socket%2", None, None), + row("git·github%3Auser·v1%G0", None, None), + row("npm~foo@1.0.0", None, None), + row("foo@1.0.0", None, None), + row("link~x", None, None), + row("GIT~x~y", None, None), + row("~npm~a@1.0.0~extra~more", None, None), + row("file~a~b~c", None, None), + row("~npm", None, None), + row("~npm~", None, None), + row("file~", None, None), + row("git~~sel", None, None), + ] + } + + fn as_split(id: &DepId) -> (DepIdEra, DepIdKind, &str, Option<&str>, Option<&str>) { + ( + id.era, + id.kind, + id.first.as_str(), + id.second.as_deref(), + id.extra.as_deref(), + ) + } + + #[test] + fn splits_and_identifies_every_codec_row() { + for r in codec_rows() { + let split = split_dep_id(&r.id); + assert_eq!(split.as_ref().map(as_split), r.split, "split {}", r.id); + assert_eq!( + split.as_ref().and_then(DepId::registry_identity), + r.identity, + "identity {}", + r.id + ); + assert_eq!( + decode_vlt_dep_id(&r.id), + r.identity.map(|(n, v)| (n.to_string(), v.to_string())), + "store decode {}", + r.id + ); + } + } + + #[test] + fn decides_the_era_by_prefix_only() { + assert_eq!(split_dep_id("~npm~a·b@1.0.0").map(|d| d.era), Some(Tilde)); + assert_eq!(split_dep_id("·npm·a~b@1.0.0").map(|d| d.era), Some(Legacy)); + assert_eq!(split_dep_id("file~a·b").map(|d| d.era), Some(Tilde)); + assert_eq!(split_dep_id("file·a~b").map(|d| d.era), Some(Legacy)); + assert_eq!( + split_dep_id("workspace·a~b~c").map(|d| d.first), + Some("a~b~c".to_string()) + ); + assert_eq!(dep_id_era("filex~a"), None); + assert_eq!(dep_id_era("Workspace~a"), None); + assert_eq!(dep_id_era(""), None); + assert_eq!(DepIdEra::for_lockfile_version(Some(1)), Tilde); + assert_eq!(DepIdEra::for_lockfile_version(Some(0)), Legacy); + assert_eq!(DepIdEra::for_lockfile_version(None), Legacy); + } + + #[test] + fn recovers_name_and_version_from_a_registry_second() { + let long = format!("{}@1.0.0", "a".repeat(215)); + let rows: Vec<(&str, Option<(&str, &str)>)> = vec![ + ("a@1.0.0", Some(("a", "1.0.0"))), + ("@s/p@1.0.0-rc.1+b.2", Some(("@s/p", "1.0.0-rc.1+b.2"))), + ("JSONStream@1.3.5", Some(("JSONStream", "1.3.5"))), + ("a-b.c@0.0.0-0", Some(("a-b.c", "0.0.0-0"))), + ("@s/p", None), + ("p", None), + ("@1.0.0", None), + ("a@", None), + ("@s@1.0.0", None), + ("a@b@1.0.0", None), + ("a@v1.0.0", None), + ("a@=1.0.0", None), + ("a@01.0.0", None), + ("a@1.0", None), + ("a@1.0.0 ", None), + ("a@^1.0.0", None), + (".a@1.0.0", None), + ("_a@1.0.0", None), + ("@s/.p@1.0.0", None), + ("@_s/p@1.0.0", None), + ("a b@1.0.0", None), + ("a/b@1.0.0", None), + (long.as_str(), None), + ]; + for (second, expected) in rows { + assert_eq!(registry_name_version(second), expected, "{second}"); + } + assert!(is_strict_semver("99999999999999999999.0.0")); + assert!(is_strict_semver("1.0.0-0a.01b+001")); + assert!(!is_strict_semver("1.0.0-01")); + assert!(!is_strict_semver("1.0.0\n")); + assert!(!is_strict_semver("١.0.0")); + } + + #[test] + fn encodes_segments_as_vlt_does_and_round_trips() { + let d19 = format!(".socket/vendor/npm/{UUID}/left-pad-1.3.0/node_modules/left-pad"); + let d19_tilde = format!(".socket+vendor+npm+{UUID}+left-pad-1.3.0+node__modules+left-pad"); + let d19_legacy = format!(".socket§vendor§npm§{UUID}§left-pad-1.3.0§node_modules§left-pad"); + let rows: Vec<(&str, &str, &str)> = vec![ + (".", "_d", "."), + ("packages/my_lib", "packages+my__lib", "packages§my_lib"), + ("a b+c:d~e", "a_sb_pc_cd_te", "a%20b%2Bc%3Ad~e"), + ("trailing.", "trailing_d", "trailing."), + ("ctl\n\u{1f}\u{0}", "ctl_0A_1F_00", "ctl%0A%1F%00"), + ("@scope/pkg@1.0.0", "@scope+pkg@1.0.0", "@scope§pkg@1.0.0"), + ("1.0.0+build.1", "1.0.0_pbuild.1", "1.0.0%2Bbuild.1"), + ( + ":root > #debug > #ms", + "_croot_s_g_s#debug_s_g_s#ms", + "%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + ), + ("ṗ:3", "ṗ_c3", "%E1%B9%97%3A3"), + ( + "http://127.0.0.1:4873/", + "http_c++127.0.0.1_c4873+", + "http%3A§§127.0.0.1%3A4873§", + ), + (d19.as_str(), d19_tilde.as_str(), d19_legacy.as_str()), + ( + "back\\slash<>\"|?*", + "back_bslash_l_g_q_i_m_a", + "back%5Cslash%3C%3E%22%7C%3F*", + ), + ("a·b§c", "a·b§c", "a%C2%B7b%C2%A7c"), + ("naïve", "naïve", "na%C3%AFve"), + ("\u{1F600}", "\u{1F600}", "%F0%9F%98%80"), + ("x_", "x__", "x_"), + ("_", "__", "_"), + ("", "", ""), + ]; + for (raw, tilde, legacy) in rows { + for (era, expected) in [(Tilde, tilde), (Legacy, legacy)] { + assert_eq!(encode_segment(raw, era), expected, "{era:?} {raw}"); + assert_eq!( + decode_segment(expected, era).as_deref(), + Some(raw), + "{era:?} {raw} back" + ); + } + } + } + + #[test] + fn decodes_tilde_escapes_the_way_vlt_does() { + let rows = [ + ("_", "_"), + ("a_", "a_"), + ("_z", "_z"), + ("_2A", "_2A"), + ("_1f", "\u{1f}"), + ("_0g", "_0g"), + ("___", "__"), + ("_d_d", ".."), + ]; + for (encoded, decoded) in rows { + assert_eq!( + decode_segment(encoded, Tilde).as_deref(), + Some(decoded), + "{encoded}" + ); + } + } + + #[test] + fn legacy_decode_validates_every_percent_escape() { + for bad in [ + "%ZZ", + "%4", + "%C3", + "%", + "a%2", + "%G0", + "%ED%A0%80", + "%C0%AF", + "%80", + ] { + assert_eq!(decode_segment(bad, Legacy), None, "{bad}"); + } + assert_eq!(decode_segment("%ZZ", Tilde).as_deref(), Some("%ZZ")); + assert_eq!(decode_segment("%25%2f%2F", Legacy).as_deref(), Some("%//")); + assert_eq!(decode_segment("%C2%A7", Legacy).as_deref(), Some("§")); + assert_eq!(decode_segment("a§b@c", Legacy).as_deref(), Some("a/b@c")); + } + + #[test] + fn file_dep_id_matches_the_lock_spelling() { + let rel = vendored_dir_rel(UUID, "@sc/pkg", "1.0.0"); + assert_eq!( + file_dep_id(&rel, Tilde), + format!("file~.socket+vendor+npm+{UUID}+@sc+pkg-1.0.0+node__modules+@sc+pkg") + ); + assert_eq!( + file_dep_id(&rel, Legacy), + format!("file·.socket§vendor§npm§{UUID}§@sc§pkg-1.0.0§node_modules§@sc§pkg") + ); + assert_eq!(file_dep_id(".", Tilde), "file~_d"); + assert_eq!(file_dep_id(".", Legacy), "file·."); + } + + #[test] + fn test_decode_vlt_dep_id_store_entries() { + let owned = |n: &str, v: &str| Some((n.to_string(), v.to_string())); + let rows = [ + ("··ms@2.1.3", owned("ms", "2.1.3")), + ( + "·npm·@isaacs§string-locale-compare@1.1.0", + owned("@isaacs/string-locale-compare", "1.1.0"), + ), + ( + "··@sindresorhus§is@4.6.0", + owned("@sindresorhus/is", "4.6.0"), + ), + ("·npm·u@1.0.0%2Bbuild.1", owned("u", "1.0.0+build.1")), + ( + "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + owned("ms", "2.1.3"), + ), + ("·npm·x@1.0.0·%E1%B9%97%3A3", owned("x", "1.0.0")), + ("~npm~@a+b@1.0.0", owned("@a/b", "1.0.0")), + ("~npm~a__b@1.0.0", owned("a_b", "1.0.0")), + ("~npm~u@1.0.0_pbuild.1", owned("u", "1.0.0+build.1")), + ( + "~npm~is-number@6.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number", + owned("is-number", "6.0.0"), + ), + ( + "~npm~react-dom@18.2.0~peer.ace93b147498ef7a", + owned("react-dom", "18.2.0"), + ), + ("~npm~x@1~peer.2", None), + ("~npm~x@1.0.0~peer.2", owned("x", "1.0.0")), + ("~acme~left-pad@1.3.0", owned("left-pad", "1.3.0")), + ("~http_c++127.0.0.1_c4873+~x@1.0.0", owned("x", "1.0.0")), + ( + "~jsr~@jsr+std____semver@1.0.8", + owned("@jsr/std__semver", "1.0.8"), + ), + ("git~github_cuser+proj~v1.0.0", None), + ("file~.socket+vendor+npm+x+a-1.0.0.tgz", None), + ("remote~https_c++e.com+r-1.0.0.tgz", None), + ("workspace~packages+a", None), + ("·npm·a@1.0.0%ZZ", None), + ("node_modules", None), + (".VLT.DELETE.1.~npm~ms@2.1.3", None), + ]; + for (dir, expected) in rows { + assert_eq!(decode_vlt_dep_id(dir), expected, "{dir}"); + } + } + + fn options(json: &str) -> Map { + serde_json::from_str(json).expect("options json") + } + + #[test] + fn default_registry_predicate() { + let none = None; + let empty = options("{}"); + let scalar = options(r#"{"registry":"http://127.0.0.1:4873"}"#); + let scalar_slash = options(r#"{"registry":"http://127.0.0.1:4873/"}"#); + let alias = options(r#"{"default-registry-alias":"corp"}"#); + let alias_url = options( + r#"{"registry":"https://r.example/npm/","registries":{"corp":"https://r.example/npm","acme":"https://acme.example/"}}"#, + ); + let odd_alias = options(r#"{"default-registry-alias":5}"#); + let null_alias = options(r#"{"default-registry-alias":null}"#); + type Case<'a> = (&'a str, Option<&'a Map>, bool); + let cases: Vec> = vec![ + ("", none, true), + ("npm", none, true), + ("npm", Some(&empty), true), + ("acme", Some(&empty), false), + ("jsr", Some(&empty), false), + ("https://npm.jsr.io/", Some(&empty), false), + ("http://127.0.0.1:4873/", none, false), + ("http://127.0.0.1:4873/", Some(&empty), false), + ("http://127.0.0.1:4873/", Some(&scalar), true), + ("http://127.0.0.1:4873", Some(&scalar), true), + ("http://127.0.0.1:4873/", Some(&scalar_slash), true), + ("http://127.0.0.1:4873", Some(&scalar_slash), true), + ("http://127.0.0.1:4874/", Some(&scalar), false), + ("https://127.0.0.1:4873/", Some(&scalar), false), + ("", Some(&scalar), true), + ("npm", Some(&scalar), true), + ("corp", Some(&alias), true), + ("npm", Some(&alias), false), + ("", Some(&alias), true), + ("corp", Some(&alias_url), true), + ("acme", Some(&alias_url), false), + ("https://r.example/npm", Some(&alias_url), true), + ("https://acme.example/", Some(&alias_url), false), + ("npm", Some(&odd_alias), false), + ("5", Some(&odd_alias), false), + ("npm", Some(&null_alias), true), + ]; + for (segment, opts, expected) in cases { + assert_eq!( + is_default_registry(segment, opts), + expected, + "{segment:?} with {opts:?}" + ); + } + let not_a_url = options(r#"{"registry":"corp"}"#); + assert!(!is_default_registry("corp", Some(¬_a_url))); + } + + fn readable(text: &str) -> ParsedLock { + match sniff_lock(text) { + LockSniff::Readable(lock) => lock, + other => panic!("{text:?} sniffed as {other:?}"), + } + } + + #[test] + fn sniff_decides_on_the_raw_version_token() { + assert_eq!(readable(r#"{"lockfileVersion":0}"#).version, Some(0)); + assert_eq!( + readable(r#"{"lockfileVersion": 1, "nodes": {}}"#).version, + Some(1) + ); + assert_eq!(readable(r#"{"nodes":{}}"#).version, None); + assert_eq!(readable(r#"{"lockfileVersion":1}"#).new_id_era(), Tilde); + assert_eq!(readable(r#"{"lockfileVersion":0}"#).new_id_era(), Legacy); + assert_eq!(readable("{}").new_id_era(), Legacy); + for (token, rendered) in [ + ("1.0", "1.0"), + ("1e0", "1.0"), + ("1.0000000000000001", "1.0"), + ("\"1\"", "\"1\""), + ("2", "2"), + ("-1", "-1"), + ("-0", "-0.0"), + ("null", "null"), + ("true", "true"), + ] { + match sniff_lock(&format!("{{\"lockfileVersion\":{token}}}")) { + LockSniff::UnsupportedVersion(v) => assert_eq!(v, rendered, "{token}"), + other => panic!("{token} sniffed as {other:?}"), + } + } + } + + #[test] + fn sniff_refuses_bom_non_objects_and_lone_surrogates() { + assert!(matches!( + sniff_lock("\u{feff}{\"lockfileVersion\":1}"), + LockSniff::Bom + )); + for text in [ + "", + "[]", + "1", + "\"x\"", + "{", + "{\"a\":1,}", + r#"{"lockfileVersion":1,"nodes":{"\ud800":[0,"a"]}}"#, + r#"{"lockfileVersion":1,"x":"\udc00"}"#, + ] { + assert!( + matches!(sniff_lock(text), LockSniff::NotJsonObject), + "{text:?}" + ); + } + let paired = readable(r#"{"lockfileVersion":1,"x":"😀"}"#); + assert_eq!(paired.json["x"], "\u{1F600}"); + } + + const CANONICAL: &str = concat!( + "{\n", + " \"lockfileVersion\": 1,\n", + " \"options\": {\n", + " \"registries\": {\n", + " \"npm\": \"https://registry.npmjs.org/\"\n", + " }\n", + " },\n", + " \"nodes\": {\n", + " \"~npm~a@1.0.0\": [0,\"a\",\"sha512-a\"],\n", + " \"~npm~b@1.0.0\": [2,\"b\",\"sha512-b\",null,null,null,null,null,{ \"b\": \"cli.js\"}]\n", + " },\n", + " \"edges\": {\n", + " \"file~_d a\": \"prod ^1.0.0 ~npm~a@1.0.0\",\n", + " \"file~_d b\": \"dev >=1 <2 ~npm~b@1.0.0\"\n", + " }\n", + "}\n", + ); + + #[test] + fn locates_canonical_sections() { + let lines = split_lines(CANONICAL); + assert_eq!(lines.join("\n"), CANONICAL); + let nodes = nodes_block(&lines).expect("nodes"); + assert_eq!(nodes, SectionSpan::Block { open: 7, close: 10 }); + assert_eq!(nodes.entry_lines(), 8..10); + let edges = edges_block(&lines).expect("edges"); + assert_eq!( + edges, + SectionSpan::Block { + open: 11, + close: 14 + } + ); + + let crlf = CANONICAL.replace('\n', "\r\n"); + let crlf_lines = split_lines(&crlf); + assert_eq!(nodes_block(&crlf_lines), Some(nodes)); + assert_eq!(edges_block(&crlf_lines), Some(edges)); + assert_eq!(crlf_lines.join("\n"), crlf); + + let empty = "{\n \"nodes\": {},\n \"edges\": {}\n}\n"; + let lines = split_lines(empty); + assert_eq!(nodes_block(&lines), Some(SectionSpan::Inline { line: 1 })); + assert_eq!(edges_block(&lines), Some(SectionSpan::Inline { line: 2 })); + assert!(SectionSpan::Inline { line: 1 }.entry_lines().is_empty()); + } + + #[test] + fn non_canonical_sections_are_not_found() { + for text in [ + "{\n\t\"nodes\": {\n \"~npm~a@1.0.0\": [0,\"a\"]\n\t}\n}\n", + "{\n \"nodes\" : {\n \"~npm~a@1.0.0\": [0,\"a\"]\n }\n}\n", + "{\n \"nodes\": {\n \"~npm~a@1.0.0\": [0,\"a\"]\n }\n}\n", + "{\n \"nodes\": {\n \"~npm~a@1.0.0\": [0,\"a\"]\n", + "{\n \"nodes\": { \"~npm~a@1.0.0\": [0,\"a\"] },\n}\n", + "{\"nodes\":{\"~npm~a@1.0.0\":[0,\"a\"]}}", + ] { + assert_eq!(nodes_block(&split_lines(text)), None, "{text:?}"); + } + } + + fn node(line: &str) -> NodeLine<'_> { + parse_node_line(line).unwrap_or_else(|| panic!("{line:?} is outside the grammar")) + } + + #[test] + fn node_line_grammar_accepts_vlt_shapes() { + let plain = node(" \"~npm~a@1.0.0\": [0,\"a\",\"sha512-x\"],"); + assert_eq!(plain.entry.key, "~npm~a@1.0.0"); + assert_eq!(plain.entry.elems, ["0", "\"a\"", "\"sha512-x\""]); + assert_eq!(plain.entry.name().as_deref(), Some("a")); + assert!(plain.comma && !plain.cr); + assert_eq!( + plain.entry.entry_text(), + "\"~npm~a@1.0.0\": [0,\"a\",\"sha512-x\"]" + ); + assert_eq!(plain.entry.slot(3), None); + + let nested = node(concat!( + " \"~npm~@esbuild+linux-x64@0.19.12\": [1,\"@esbuild/linux-x64\",\"sha512-A==\",", + "\"http://127.0.0.1:4873/x.tgz\",null,null,null,{ \"engines\": { \"node\": \">=12\" },", + " \"os\": [ \"linux\" ], \"cpu\": [ \"x64\" ]}]\r" + )); + assert_eq!(nested.entry.elems.len(), 8); + assert_eq!(nested.entry.slot(4), Some("null")); + assert!(nested.entry.elems[7].starts_with("{ \"engines\"")); + assert!(!nested.comma && nested.cr); + + let two = node(" \"file~.socket+vendor\": [3,\"x\"]"); + assert_eq!(two.entry.elems, ["3", "\"x\""]); + let nulls = node(" \"~npm~a@1.0.0\": [0,\"a\",null,null,\"node_modules/.vlt/x\"]"); + assert_eq!(nulls.entry.slot(2), Some("null")); + let escaped = + node(" \"~npm~a@1.0.0\": [0,\"a\\\"b\",\"s,]\\\\\",null,[1,[2]],{\"k\":[\",\"]}]"); + assert_eq!( + escaped.entry.elems, + [ + "0", + "\"a\\\"b\"", + "\"s,]\\\\\"", + "null", + "[1,[2]]", + "{\"k\":[\",\"]}" + ] + ); + assert_eq!(escaped.entry.name().as_deref(), Some("a\"b")); + } + + #[test] + fn node_line_grammar_rejects_deviations() { + for line in [ + " \"~npm~a@1.0.0\": [0,\"a\"]", + " \"~npm~a@1.0.0\": [0,\"a\"]", + "\t\"~npm~a@1.0.0\": [0,\"a\"]", + " \"~npm~a@1.0.0\" : [0,\"a\"]", + " \"~npm~a@1.0.0\":[0,\"a\"]", + " \"~npm~a@1.0.0\": [0, \"a\"]", + " \"~npm~a@1.0.0\": [0 ,\"a\"]", + " \"~npm~a@1.0.0\": [ 0,\"a\"]", + " \"~npm~a@1.0.0\": [0,\"a\" ]", + " \"~npm~a@1.0.0\": [0,\"a\"] ", + " \"~npm~a@1.0.0\": [0,\"a\"],,", + " \"~npm~a@1.0.0\": [0,\"a\"],\r\r", + " \"~npm~a@1.0.0\": [0,\"a\",]", + " \"~npm~a@1.0.0\": [0,,\"a\"]", + " \"~npm~a@1.0.0\": []", + " \"~npm~a@1.0.0\": [0]", + " \"~npm~a@1.0.0\": [4,\"a\"]", + " \"~npm~a@1.0.0\": [\"0\",\"a\"]", + " \"~npm~a@1.0.0\": [0.0,\"a\"]", + " \"~npm~a@1.0.0\": [0,null]", + " \"~npm~a@1.0.0\": [0,\"a\",1]", + " \"~npm~a@1.0.0\": [0,\"a\",null,{}]", + " \"~npm~a@1.0.0\": {\"0\":\"a\"}", + " \"~npm~a@1.0.0\": [0,\"a\"", + " \"~npm~a@1.0.0\": [0,\"a\"]]", + " \"~npm~a\\u0040.0.0\": [0,\"a\"]", + " \"~npm~a@1.0.0: [0,\"a\"]", + " ~npm~a@1.0.0: [0,\"a\"]", + "", + " },", + ] { + assert_eq!(parse_node_line(line), None, "{line:?}"); + } + } + + #[test] + fn element_splitter_tracks_strings_and_nesting() { + assert_eq!( + split_tuple_elements("[\"a,b\",\"c\\\"]\",[1,{\"x\":[2,3]}],{}]"), + Some(vec!["\"a,b\"", "\"c\\\"]\"", "[1,{\"x\":[2,3]}]", "{}"]) + ); + assert_eq!(split_tuple_elements("[]"), Some(vec![])); + assert_eq!( + split_tuple_elements("[{ \"k\": 1}]"), + Some(vec!["{ \"k\": 1}"]) + ); + for bad in [ + "[1, 2]", "[1,2 ]", "[\n1]", "[1,,2]", "[1,]", "{}", "1", "[1", "[1]x", " [1]", + ] { + assert_eq!(split_tuple_elements(bad), None, "{bad:?}"); + } + } + + #[test] + fn renders_tuples_with_slots() { + let three = ["0", "\"a\"", "\"sha512-old\""]; + let four = ["0", "\"a\"", "\"sha512-new\"", "\"https://h/a.tgz\""]; + let five = [ + "2", + "\"a\"", + "\"sha512-new\"", + "\"https://h/a.tgz\"", + "null", + "null", + "null", + "null", + "{ \"a\": \"cli.js\"}", + ]; + let s2 = Some("\"sha512-new\""); + let s3 = Some("\"https://h/a.tgz\""); + assert_eq!( + render_tuple_with_slots(&["0", "\"a\""], s2, s3), + "[0,\"a\",\"sha512-new\",\"https://h/a.tgz\"]" + ); + assert_eq!( + render_tuple_with_slots(&three, s2, s3), + "[0,\"a\",\"sha512-new\",\"https://h/a.tgz\"]" + ); + assert_eq!( + render_tuple_with_slots(&five[..6], Some("\"sha512-x\""), Some("\"u\"")), + "[2,\"a\",\"sha512-x\",\"u\",null,null]" + ); + let old = Some("\"sha512-old\""); + assert_eq!( + render_tuple_with_slots(&four, old, None), + "[0,\"a\",\"sha512-old\"]" + ); + assert_eq!( + render_tuple_with_slots(&five, old, None), + "[2,\"a\",\"sha512-old\",null,null,null,null,null,{ \"a\": \"cli.js\"}]" + ); + assert_eq!(render_tuple_with_slots(&four, None, None), "[0,\"a\"]"); + assert_eq!( + render_tuple_with_slots(&four, None, Some("\"rel\"")), + "[0,\"a\",null,\"rel\"]" + ); + assert_eq!( + render_tuple_with_slots(&three, Some("null"), Some("\"rel\"")), + "[0,\"a\",null,\"rel\"]" + ); + } + + #[test] + fn entry_text_round_trips_through_lines() { + let text = "\"~npm~a@1.0.0\": [0,\"a\",\"sha512-x\"]"; + let entry = parse_node_entry_text(text).expect("entry text"); + assert_eq!(entry.entry_text(), text); + for (comma, cr) in [(false, false), (true, false), (false, true), (true, true)] { + let line = render_entry_line(text, comma, cr); + let parsed = node(&line); + assert_eq!((parsed.comma, parsed.cr), (comma, cr)); + assert_eq!(parsed.entry, entry); + } + assert_eq!( + render_entry_line(text, true, true), + format!(" {text},\r") + ); + assert_eq!(parse_node_entry_text(&format!(" {text}")), None); + assert_eq!(parse_node_entry_text(&format!("{text},")), None); + assert_eq!(entry_text("k", "\"v\""), "\"k\": \"v\""); + } + + #[test] + fn edge_line_grammar() { + let line = " \"~npm~loose-envify@1.4.0 js-tokens\": \"prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0\",\r"; + let edge = parse_edge_line(line).expect("edge"); + assert!(edge.comma && edge.cr); + assert_eq!(edge.entry.from(), "~npm~loose-envify@1.4.0"); + assert_eq!(edge.entry.dep_name(), "js-tokens"); + assert_eq!(edge.entry.edge_type(), "prod"); + assert_eq!(edge.entry.spec(), "^3.0.0 || ^4.0.0"); + assert_eq!(edge.entry.target(), "~npm~js-tokens@4.0.0"); + assert_eq!( + edge.entry.entry_text(), + "\"~npm~loose-envify@1.4.0 js-tokens\": \"prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0\"" + ); + let missing = parse_edge_line(" \"~npm~tap@15.2.3~peer.6f88d0ccf17dbbdc ts-node\": \"peerOptional >=8.5.2 MISSING\"") + .expect("missing edge"); + assert_eq!(missing.entry.target(), "MISSING"); + assert_eq!(missing.entry.sort_key().to, "MISSING"); + let alias = parse_edge_line( + " \"file·. my-alias\": \"prod npm:left-pad@^1.3.0 ·npm·left-pad@1.3.0\"", + ) + .expect("alias edge"); + assert_eq!(alias.entry.dep_name(), "my-alias"); + assert_eq!(alias.entry.spec(), "npm:left-pad@^1.3.0"); + let escaped = + parse_edge_line(" \"file~_d g\": \"prod github:a/b#\\\"x\\\" git~github_ca+b~x\"") + .expect("escaped edge"); + assert_eq!(escaped.entry.spec(), "github:a/b#\"x\""); + + for bad in [ + " \"file~_d a\": \"build ^1 ~npm~a@1.0.0\"", + " \"file~_d a\": \"prod ~npm~a@1.0.0\"", + " \"file~_d a\": \"prod\"", + " \"file~_d\": \"prod ^1 ~npm~a@1.0.0\"", + " \" a\": \"prod ^1 ~npm~a@1.0.0\"", + " \"file~_d a\": [0,\"a\"]", + " \"file~_d a\": \"prod ^1 ~npm~a@1.0.0\" ", + " \"file~_d a\": \"prod ^1 ~npm~a@1.0.0\"x", + " \"file~_d a\" : \"prod ^1 ~npm~a@1.0.0\"", + ] { + assert_eq!(parse_edge_line(bad), None, "{bad:?}"); + } + } + + #[test] + fn importer_sources() { + for id in [ + "file~_d", + "file·.", + "workspace~packages+a", + "workspace·packages§a", + ] { + assert!(is_importer_dep_id(id), "{id}"); + } + for id in [ + "file~.", + "file·_d", + "file~packages+a", + "workspace~", + "~npm~a@1.0.0", + "file~_d~x", + ] { + assert!(!is_importer_dep_id(id), "{id}"); + } + } + + #[derive(serde::Deserialize)] + struct CollationGolden { + alphabet: String, + nodes: Vec, + edges: Vec<(String, String)>, + } + + fn collation_golden() -> CollationGolden { + let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/vlt/collation-golden.json"); + let text = std::fs::read_to_string(&path).expect("read collation golden"); + serde_json::from_str(&text).expect("parse collation golden") + } + + fn scrambled(items: &[T]) -> Vec { + let n = items.len(); + (0..n).map(|i| items[(i * 7919 + 13) % n].clone()).collect() + } + + #[test] + fn collation_matches_node_icu_golden() { + let golden = collation_golden(); + let alphabet: Vec = golden.alphabet.chars().collect(); + assert_eq!(alphabet.len(), 97); + for pair in alphabet.windows(2) { + let (a, b) = (pair[0].to_string(), pair[1].to_string()); + assert_eq!(vlt_collate(&a, &b), Some(Ordering::Less), "{a:?} < {b:?}"); + } + for c in &alphabet { + assert!(collation_weight(*c).is_some(), "{c:?} in the table"); + } + + assert!(golden.nodes.len() >= 400); + let mut ids = scrambled(&golden.nodes); + assert_ne!(ids, golden.nodes); + ids.sort_by(|a, b| vlt_collate(a, b).expect("in-table ids")); + assert_eq!(ids, golden.nodes); + for pair in golden.nodes.windows(2) { + assert_eq!(vlt_collate(&pair[0], &pair[1]), Some(Ordering::Less)); + assert_eq!(vlt_collate(&pair[1], &pair[0]), Some(Ordering::Greater)); + } + } + + #[test] + fn edge_comparator_matches_vlt_format_edges_golden() { + let golden = collation_golden(); + assert!(golden.edges.len() >= 100); + let texts: Vec = golden + .edges + .iter() + .map(|(k, v)| entry_text(k, &serde_json::to_string(v).expect("json value"))) + .collect(); + let entries: Vec> = texts + .iter() + .map(|t| parse_edge_entry_text(t).unwrap_or_else(|| panic!("{t} is an edge"))) + .collect(); + let mut sorted = scrambled(&entries); + sorted.sort_by(|a, b| vlt_edge_cmp(a.sort_key(), b.sort_key()).expect("in-table edges")); + assert_eq!(sorted, entries); + assert!(entries + .iter() + .any(|e| e.target() == "MISSING" && is_importer_dep_id(e.from()))); + } + + #[test] + fn edge_comparator_keys_and_unknown() { + let key = |from, edge_type, to| EdgeSortKey { + from, + edge_type, + to, + }; + let root = key("file~_d", "prod", "~npm~z@1.0.0"); + let member = key("workspace~packages+a", "dev", "~npm~a@1.0.0"); + let node = key("~npm~a@1.0.0", "dev", "~npm~a@1.0.0"); + assert_eq!(vlt_edge_cmp(root, node), Some(Ordering::Less)); + assert_eq!(vlt_edge_cmp(node, member), Some(Ordering::Greater)); + assert_eq!(vlt_edge_cmp(root, member), Some(Ordering::Less)); + assert_eq!( + vlt_edge_cmp( + key("~npm~a@1.0.0", "peer", "~npm~z@1.0.0"), + key("~npm~a@1.0.0", "prod", "~npm~b@1.0.0") + ), + Some(Ordering::Less) + ); + assert_eq!( + vlt_edge_cmp( + key("~npm~a@1.0.0", "prod", "MISSING"), + key("~npm~a@1.0.0", "prod", "~~a@1.0.0") + ), + Some(Ordering::Less) + ); + assert_eq!(vlt_edge_cmp(node, node), Some(Ordering::Equal)); + + let unicode = key("file~packages+naïve", "prod", "~npm~a@1.0.0"); + assert_eq!(vlt_edge_cmp(root, unicode), Some(Ordering::Less)); + assert_eq!(vlt_edge_cmp(unicode, node), None); + let unknown_target = key("~npm~a@1.0.0", "dev", "file~ünï"); + assert_eq!( + vlt_edge_cmp(unknown_target, key("~npm~a@1.0.0", "prod", "~npm~b@1.0.0")), + Some(Ordering::Less) + ); + assert_eq!(vlt_edge_cmp(unknown_target, node), None); + } + + #[test] + fn collate_is_unknown_outside_the_table() { + // Reachable from real locks: the tilde encoding leaves non-ASCII + // raw, so a `file:` or `remote` path with it lands in a node key. + let tilde_path = file_dep_id("packages/naïve", Tilde); + assert_eq!(tilde_path, "file~packages+naïve"); + assert_eq!(vlt_collate(&tilde_path, "file~packages+a"), None); + assert_eq!(vlt_collate("~npm~a@1.0.0", &tilde_path), None); + // The legacy encoding percent-escapes it, so the same path is known. + let legacy_path = file_dep_id("packages/naïve", Legacy); + assert_eq!( + vlt_collate(&legacy_path, "file·packages§a"), + Some(Ordering::Greater) + ); + assert_eq!(vlt_collate("a\tb", "a b"), None); + assert_eq!(vlt_collate("", ""), Some(Ordering::Equal)); + assert_eq!(vlt_collate("", "a"), Some(Ordering::Less)); + } + + #[test] + fn collate_primary_then_case() { + let cases = [ + ("a", "A", Ordering::Less), + ("aB", "Ab", Ordering::Less), + ("ab", "aB", Ordering::Less), + ("Ab", "b", Ordering::Less), + ("A", "a-b", Ordering::Less), + ("~npm~A@1.0.0", "~npm~a@1.0.0-rc.1", Ordering::Less), + ("~npm~a_b@1.0.0", "~npm~a-b@1.0.0", Ordering::Less), + ("~npm~a__b@1.0.0", "~npm~a_b@1.0.0", Ordering::Less), + ("··ms@2.1.3", "·npm·ms@2.1.3", Ordering::Less), + ("~npm~z@1.0.0", "file~_d", Ordering::Less), + ("file·x", "file~x", Ordering::Less), + ( + "~npm~is-number@7.0.0", + "~npm~is-number@7.0.0~peer.1", + Ordering::Less, + ), + ]; + for (a, b, expected) in cases { + assert_eq!(vlt_collate(a, b), Some(expected), "{a} vs {b}"); + assert_eq!(vlt_collate(b, a), Some(expected.reverse()), "{b} vs {a}"); + } + } + + // Captured with vlt 1.2.0 and rc.14 (design probes peerprobe/t2 and + // e1c/base): every entry line is inside the grammar and both sections + // are in the order the comparators compute. + const CAPTURED_1_2_0: &str = r#"{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~react-dom@18.2.0~peer.ace93b147498ef7a": [0,"react-dom","sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==","https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~react@18.3.1": [0,"react","sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==","https://registry.npmjs.org/react/-/react-18.3.1.tgz"], + "~npm~scheduler@0.23.2": [0,"scheduler","sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==","https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz"] + }, + "edges": { + "workspace~packages+a react-dom": "prod 18.2.0 ~npm~react-dom@18.2.0~peer.ace93b147498ef7a", + "workspace~packages+a react": "prod 18.2.0 ~npm~react@18.2.0", + "workspace~packages+b react-dom": "prod 18.2.0 ~npm~react-dom@18.2.0~peer.ace93b147498ef7a", + "workspace~packages+b react": "prod 18.3.1 ~npm~react@18.3.1", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~react-dom@18.2.0~peer.ace93b147498ef7a react": "peer ^18.2.0 ~npm~react@18.2.0", + "~npm~react-dom@18.2.0~peer.ace93b147498ef7a loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~react-dom@18.2.0~peer.ace93b147498ef7a scheduler": "prod ^0.23.0 ~npm~scheduler@0.23.2", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~react@18.3.1 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~scheduler@0.23.2 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0" + } +} +"#; + + const CAPTURED_RC_14: &str = r#"{ + "lockfileVersion": 0, + "options": { + "registries": {} + }, + "nodes": { + "·npm·d@1.0.2": [0,"d","sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw=="], + "·npm·debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "·npm·es5-ext@0.10.64": [0,"es5-ext","sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg=="], + "·npm·es6-iterator@2.0.3": [0,"es6-iterator","sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g=="], + "·npm·es6-symbol@3.1.4": [0,"es6-symbol","sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg=="], + "·npm·esniff@2.0.1": [0,"esniff","sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg=="], + "·npm·event-emitter@0.3.5": [0,"event-emitter","sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA=="], + "·npm·ext@1.7.0": [0,"ext","sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw=="], + "·npm·left-pad@1.3.0": [0,"left-pad","sha512-bUnDPt4lr1/bnysTf7XK7sg0vTNA27PJ5/BNeRNBoU+h9u5ZY/USq4c/DoM4KWWXt7e08tO7bZbIEavJzyTjUg=="], + "·npm·ms@2.1.2": [0,"ms","sha512-/fZHSQ+GyiEzhN3UXH54WwVflJQXgI75oNXQa8ikM+rDUrERt2tYR9uliVbPIHf6XrOXwTpw+hkrMsC7MPhPgg=="], + "·npm·next-tick@1.1.0": [0,"next-tick","sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="], + "·npm·type@2.7.3": [0,"type","sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="] + }, + "edges": { + "file·. debug": "prod 4.3.4 ·npm·debug@4.3.4", + "file·. es5-ext": "prod 0.10.64 ·npm·es5-ext@0.10.64", + "file·. left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "·npm·d@1.0.2 es5-ext": "prod ^0.10.64 ·npm·es5-ext@0.10.64", + "·npm·d@1.0.2 type": "prod ^2.7.2 ·npm·type@2.7.3", + "·npm·debug@4.3.4 ms": "prod 2.1.2 ·npm·ms@2.1.2", + "·npm·es5-ext@0.10.64 es6-iterator": "prod ^2.0.3 ·npm·es6-iterator@2.0.3", + "·npm·es5-ext@0.10.64 es6-symbol": "prod ^3.1.3 ·npm·es6-symbol@3.1.4", + "·npm·es5-ext@0.10.64 esniff": "prod ^2.0.1 ·npm·esniff@2.0.1", + "·npm·es5-ext@0.10.64 next-tick": "prod ^1.1.0 ·npm·next-tick@1.1.0", + "·npm·es6-iterator@2.0.3 d": "prod 1 ·npm·d@1.0.2", + "·npm·es6-iterator@2.0.3 es5-ext": "prod ^0.10.35 ·npm·es5-ext@0.10.64", + "·npm·es6-iterator@2.0.3 es6-symbol": "prod ^3.1.1 ·npm·es6-symbol@3.1.4", + "·npm·es6-symbol@3.1.4 d": "prod ^1.0.2 ·npm·d@1.0.2", + "·npm·es6-symbol@3.1.4 ext": "prod ^1.7.0 ·npm·ext@1.7.0", + "·npm·esniff@2.0.1 d": "prod ^1.0.1 ·npm·d@1.0.2", + "·npm·esniff@2.0.1 es5-ext": "prod ^0.10.62 ·npm·es5-ext@0.10.64", + "·npm·esniff@2.0.1 event-emitter": "prod ^0.3.5 ·npm·event-emitter@0.3.5", + "·npm·esniff@2.0.1 type": "prod ^2.7.2 ·npm·type@2.7.3", + "·npm·event-emitter@0.3.5 d": "prod 1 ·npm·d@1.0.2", + "·npm·event-emitter@0.3.5 es5-ext": "prod ~0.10.14 ·npm·es5-ext@0.10.64", + "·npm·ext@1.7.0 type": "prod ^2.7.2 ·npm·type@2.7.3" + } +} +"#; + + #[test] + fn captured_locks_parse_and_are_in_vlt_order() { + for (label, text, version) in [("1.2.0", CAPTURED_1_2_0, 1), ("rc.14", CAPTURED_RC_14, 0)] { + let lock = readable(text); + assert_eq!(lock.version, Some(version), "{label}"); + let options = lock.options(); + let lines = split_lines(text); + + let span = nodes_block(&lines).expect("nodes block"); + let nodes: Vec> = lines[span.entry_lines()] + .iter() + .map(|l| parse_node_line(l).unwrap_or_else(|| panic!("{label}: {l}"))) + .collect(); + let json_keys: Vec<&str> = lock + .nodes() + .expect("nodes") + .keys() + .map(String::as_str) + .collect(); + assert_eq!( + nodes.iter().map(|n| n.entry.key).collect::>(), + json_keys + ); + assert!(nodes[..nodes.len() - 1].iter().all(|n| n.comma)); + assert!(!nodes[nodes.len() - 1].comma); + for pair in nodes.windows(2) { + assert_eq!( + vlt_collate(pair[0].entry.key, pair[1].entry.key), + Some(Ordering::Less), + "{label}" + ); + } + for n in &nodes { + let id = split_dep_id(n.entry.key).expect("decodable"); + let (name, _) = id.registry_identity().expect("registry node"); + assert_eq!(n.entry.name().as_deref(), Some(name)); + assert!( + is_default_registry(&id.first, options), + "{label} {}", + n.entry.key + ); + assert_eq!(id.era, DepIdEra::for_lockfile_version(lock.version)); + } + + let span = edges_block(&lines).expect("edges block"); + let edges: Vec> = lines[span.entry_lines()] + .iter() + .map(|l| parse_edge_line(l).unwrap_or_else(|| panic!("{label}: {l}"))) + .collect(); + assert_eq!(edges.len(), lock.edges().expect("edges").len()); + for pair in edges.windows(2) { + assert_ne!( + vlt_edge_cmp(pair[0].entry.sort_key(), pair[1].entry.sort_key()), + Some(Ordering::Greater), + "{label}: {} before {}", + pair[0].entry.key, + pair[1].entry.key + ); + } + } + } + + #[test] + fn vendored_path_rule() { + let dir = |p: &str| p.replace("", UUID); + let expect = |path: &str, name: &str, version: &str, shape| { + let got = parse_vendored_path(path, name).unwrap_or_else(|| panic!("{path}")); + assert_eq!( + got, + VendoredPath { + uuid: UUID.to_string(), + name: name.to_string(), + version: version.to_string(), + shape, + } + ); + }; + let d = ".socket/vendor/npm//left-pad-1.3.0/node_modules/left-pad"; + expect(&dir(d), "left-pad", "1.3.0", VendoredShape::Dir); + expect( + &dir(".socket/vendor/npm//@sc/pkg-1.0.0-rc.1+b/node_modules/@sc/pkg"), + "@sc/pkg", + "1.0.0-rc.1+b", + VendoredShape::Dir, + ); + expect( + &dir(".socket/vendor/npm//a-1.0.0-1.0.0/node_modules/a-1.0.0"), + "a-1.0.0", + "1.0.0", + VendoredShape::Dir, + ); + expect( + &dir(".socket/vendor/npm//left-pad-1.3.0.tgz"), + "left-pad", + "1.3.0", + VendoredShape::Tgz, + ); + expect( + &dir(".socket/vendor/npm//@sindresorhus/is-4.6.0.tgz"), + "@sindresorhus/is", + "4.6.0", + VendoredShape::Tgz, + ); + expect( + &dir(".socket/vendor/npm//pkg2-1.0.0-2.tgz"), + "pkg2", + "1.0.0-2", + VendoredShape::Tgz, + ); + assert_eq!( + parse_vendored_dir_path(&dir(d)).map(|p| p.name), + Some("left-pad".to_string()) + ); + + for (path, name) in [ + (d, "right-pad"), + ( + ".socket/vendor/npm//@a/pkg-1.0.0/node_modules/@b/pkg", + "@a/pkg", + ), + ( + ".socket/vendor/npm//pkg-1.0.0/node_modules/@a/pkg", + "@a/pkg", + ), + ( + ".socket/vendor/npm//@a/pkg-1.0.0/node_modules/pkg", + "pkg", + ), + ( + ".socket/vendor/npm//sc/pkg-1.0.0/node_modules/sc/pkg", + "sc/pkg", + ), + ( + ".socket/vendor/npm//left-pad-1.3/node_modules/left-pad", + "left-pad", + ), + ( + ".socket/vendor/npm//left-pad-v1.3.0/node_modules/left-pad", + "left-pad", + ), + ( + ".socket/vendor/npm//left-pad/node_modules/left-pad", + "left-pad", + ), + ( + ".socket/vendor/npm//left-pad-1.3.0/node_modules/left-pad/", + "left-pad", + ), + ( + ".socket/vendor/npm//left-pad-1.3.0/node_modules/left-pad/x", + "left-pad", + ), + (".socket/vendor/npm//left-pad-1.3.0/left-pad", "left-pad"), + (".socket/vendor/npm//left-pad-1.3.0", "left-pad"), + (".socket/vendor/npm//@sc/pkg-1.0.0.tgz", "pkg"), + (".socket/vendor/npm//pkg-1.0.0.tgz", "@sc/pkg"), + (".socket/vendor/npm//@x/pkg-1.0.0.tgz", "@sc/pkg"), + (".socket/vendor/npm//left-pad-1.3.tgz", "left-pad"), + (".socket/vendor/npm//x/left-pad-1.3.0.tgz", "left-pad"), + ( + ".socket/vendor/cargo//left-pad-1.3.0/node_modules/left-pad", + "left-pad", + ), + ( + "./.socket/vendor/npm//left-pad-1.3.0/node_modules/left-pad", + "left-pad", + ), + (".socket/vendor/npm//.a-1.0.0/node_modules/.a", ".a"), + ( + "vendor/npm//left-pad-1.3.0/node_modules/left-pad", + "left-pad", + ), + ] { + assert_eq!( + parse_vendored_path(&dir(path), name), + None, + "{path} as {name}" + ); + } + let upper = d.replace("", &UUID.to_uppercase()); + assert_eq!(parse_vendored_path(&upper, "left-pad"), None); + assert_eq!( + parse_vendored_path(&d.replace("", "not-a-uuid"), "left-pad"), + None + ); + } + + #[test] + fn vendored_dir_rel_is_the_d19_layout() { + for (name, version) in [ + ("left-pad", "1.3.0"), + ("@sc/pkg", "1.0.0-rc.1"), + ("a-1.0.0", "1.0.0"), + ] { + let rel = vendored_dir_rel(UUID, name, version); + let parsed = parse_vendored_dir_path(&rel).expect("round trip"); + assert_eq!( + (parsed.name.as_str(), parsed.version.as_str()), + (name, version) + ); + assert_eq!(parse_vendored_path(&rel, name), Some(parsed)); + let tilde = file_dep_id(&rel, Tilde); + let legacy = file_dep_id(&rel, Legacy); + for id in [tilde, legacy] { + let split = split_dep_id(&id).expect("file id"); + assert_eq!((split.kind, split.first.as_str()), (File, rel.as_str())); + } + } + assert_eq!( + vendored_dir_rel(UUID, "left-pad", "1.3.0"), + format!(".socket/vendor/npm/{UUID}/left-pad-1.3.0/node_modules/left-pad") + ); + } + + #[test] + fn vlt_file_names_are_pinned() { + assert_eq!(VLT_LOCK, "vlt-lock.json"); + assert_eq!(VLT_CONFIG, "vlt.json"); + assert_eq!(VLT_HIDDEN_LOCK_REL, "node_modules/.vlt-lock.json"); + assert_eq!(VLT_STORE_DIR, "node_modules/.vlt"); + assert_eq!(VLT_LEGACY_WORKSPACES, "vlt-workspaces.json"); + assert_eq!( + VLT_SETUP_MARKERS, + [ + "vlt-lock.json", + "vlt.json", + "node_modules/.vlt-lock.json", + "node_modules/.vlt" + ] + ); + } +} diff --git a/crates/socket-patch-core/src/vex/discover/testing/golden.rs b/crates/socket-patch-core/src/vex/discover/testing/golden.rs index 1f84a93d..6aa760e3 100644 --- a/crates/socket-patch-core/src/vex/discover/testing/golden.rs +++ b/crates/socket-patch-core/src/vex/discover/testing/golden.rs @@ -276,6 +276,8 @@ fn corpus() -> Vec { let name = top.file_name().unwrap().to_string_lossy().into_owned(); match name.as_str() { GOLDEN_DIR => {} + // Tables and store listings, not projects. + "vlt" | "vlt-trees" | "vendor" => {} // Redirect cases: `///{input,expected}` — // each side is a project root (nested files included). "redirect" => { @@ -477,6 +479,18 @@ mod tests { ); } + #[test] + fn table_fixture_dirs_are_not_corpus_projects() { + let corpus = corpus(); + for skipped in ["vlt/", "vlt-trees/", "vendor/"] { + assert!( + corpus.iter().all(|(name, _, _)| !name.starts_with(skipped)), + "{skipped} fixtures joined the corpus" + ); + } + assert!(fixtures_root().join("vlt/collation-golden.json").is_file()); + } + #[test] fn families_group_redirect_cases_by_ecosystem() { assert_eq!(family("redirect/npm/bun/case/input"), "redirect-npm"); diff --git a/crates/socket-patch-core/tests/fixtures/vlt/collation-golden.json b/crates/socket-patch-core/tests/fixtures/vlt/collation-golden.json new file mode 100644 index 00000000..35d1d9b3 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt/collation-golden.json @@ -0,0 +1,1675 @@ +{ + "alphabet": " _-,;:!?.·'\"()[]{}§@*/\\&#%`^+<=>|~$0123456789aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ", + "nodes": [ + "··@a§b@1.0.0", + "··@A§b@1.0.0", + "··@a§b@1.0.0-rc.1", + "··@A§b@1.0.0-rc.1", + "··@a§b@1.0.0%2Bbuild.1", + "··@A§b@1.0.0%2Bbuild.1", + "··@a§b@10.0.0", + "··@A§b@10.0.0", + "··@a§b@2.1.3", + "··@A§b@2.1.3", + "··@isaacs§string-locale-compare@1.0.0", + "··@isaacs§string-locale-compare@1.0.0-rc.1", + "··@isaacs§string-locale-compare@1.0.0%2Bbuild.1", + "··@isaacs§string-locale-compare@10.0.0", + "··@isaacs§string-locale-compare@2.1.3", + "··@jsr§std__semver@1.0.0", + "··@jsr§std__semver@1.0.0-rc.1", + "··@jsr§std__semver@1.0.0%2Bbuild.1", + "··@jsr§std__semver@10.0.0", + "··@jsr§std__semver@2.1.3", + "··@scope_x§bar@1.0.0", + "··@scope_x§bar@1.0.0-rc.1", + "··@scope_x§bar@1.0.0%2Bbuild.1", + "··@scope_x§bar@10.0.0", + "··@scope_x§bar@2.1.3", + "··@scope§bar-baz@1.0.0", + "··@scope§bar-baz@1.0.0-rc.1", + "··@scope§bar-baz@1.0.0%2Bbuild.1", + "··@scope§bar-baz@10.0.0", + "··@scope§bar-baz@2.1.3", + "··@scope§bar@1.0.0", + "··@scope§bar@1.0.0-rc.1", + "··@scope§bar@1.0.0%2Bbuild.1", + "··@scope§bar@10.0.0", + "··@scope§bar@2.1.3", + "··@sindresorhus§is@1.0.0", + "··@sindresorhus§is@1.0.0-rc.1", + "··@sindresorhus§is@1.0.0%2Bbuild.1", + "··@sindresorhus§is@10.0.0", + "··@sindresorhus§is@2.1.3", + "··0x@1.0.0", + "··0x@1.0.0-rc.1", + "··0x@1.0.0%2Bbuild.1", + "··0x@10.0.0", + "··0x@2.1.3", + "··a__b@1.0.0", + "··a__b@1.0.0-rc.1", + "··a__b@1.0.0%2Bbuild.1", + "··a__b@10.0.0", + "··a__b@2.1.3", + "··a_b@1.0.0", + "··a_b@1.0.0-rc.1", + "··a_b@1.0.0%2Bbuild.1", + "··a_b@10.0.0", + "··a_b@2.1.3", + "··a-b@1.0.0", + "··a-b@1.0.0-rc.1", + "··a-b@1.0.0%2Bbuild.1", + "··a-b@10.0.0", + "··a-b@2.1.3", + "··a.b@1.0.0", + "··a.b@1.0.0-rc.1", + "··a.b@1.0.0%2Bbuild.1", + "··a.b@10.0.0", + "··a.b@2.1.3", + "··a@1.0.0", + "··A@1.0.0", + "··a@1.0.0-rc.1", + "··A@1.0.0-rc.1", + "··a@1.0.0%2Bbuild.1", + "··A@1.0.0%2Bbuild.1", + "··a@10.0.0", + "··A@10.0.0", + "··a@2.1.3", + "··A@2.1.3", + "··a~b@1.0.0", + "··a~b@1.0.0-rc.1", + "··a~b@1.0.0%2Bbuild.1", + "··a~b@10.0.0", + "··a~b@2.1.3", + "··a1@1.0.0", + "··a1@1.0.0-rc.1", + "··a1@1.0.0%2Bbuild.1", + "··a1@10.0.0", + "··a1@2.1.3", + "··ab@1.0.0", + "··aB@1.0.0", + "··Ab@1.0.0", + "··ab@1.0.0-rc.1", + "··aB@1.0.0-rc.1", + "··Ab@1.0.0-rc.1", + "··ab@1.0.0%2Bbuild.1", + "··aB@1.0.0%2Bbuild.1", + "··Ab@1.0.0%2Bbuild.1", + "··ab@10.0.0", + "··aB@10.0.0", + "··Ab@10.0.0", + "··ab@2.1.3", + "··aB@2.1.3", + "··Ab@2.1.3", + "··is-number@1.0.0", + "··is-number@1.0.0-rc.1", + "··is-number@1.0.0%2Bbuild.1", + "··is-number@10.0.0", + "··is-number@2.1.3", + "··JSONStream@1.0.0", + "··JSONStream@1.0.0-rc.1", + "··JSONStream@1.0.0%2Bbuild.1", + "··JSONStream@10.0.0", + "··JSONStream@2.1.3", + "··left-pad@1.0.0", + "··left-pad@1.0.0-rc.1", + "··left-pad@1.0.0%2Bbuild.1", + "··left-pad@10.0.0", + "··left-pad@2.1.3", + "··ms@1.0.0", + "··MS@1.0.0", + "··ms@1.0.0-rc.1", + "··MS@1.0.0-rc.1", + "··ms@1.0.0%2Bbuild.1", + "··MS@1.0.0%2Bbuild.1", + "··ms@10.0.0", + "··MS@10.0.0", + "··ms@2.1.3", + "··MS@2.1.3", + "··react-dom@1.0.0", + "··react-dom@1.0.0-rc.1", + "··react-dom@1.0.0%2Bbuild.1", + "··react-dom@10.0.0", + "··react-dom@2.1.3", + "··react@1.0.0", + "··react@1.0.0-rc.1", + "··react@1.0.0%2Bbuild.1", + "··react@10.0.0", + "··react@2.1.3", + "··use-sync-external-store@1.0.0", + "··use-sync-external-store@1.0.0-rc.1", + "··use-sync-external-store@1.0.0%2Bbuild.1", + "··use-sync-external-store@10.0.0", + "··use-sync-external-store@2.1.3", + "··z@1.0.0", + "··z@1.0.0-rc.1", + "··z@1.0.0%2Bbuild.1", + "··z@10.0.0", + "··z@2.1.3", + "··zz@1.0.0", + "··zz@1.0.0-rc.1", + "··zz@1.0.0%2Bbuild.1", + "··zz@10.0.0", + "··zz@2.1.3", + "·acme·@a§b@1.0.0", + "·acme·@a§b@10.0.0", + "·acme·@A§b@2.1.3", + "·acme·@isaacs§string-locale-compare@2.1.3", + "·acme·@jsr§std__semver@1.0.0-rc.1", + "·acme·@scope_x§bar@1.0.0", + "·acme·@scope_x§bar@10.0.0", + "·acme·@scope§bar-baz@1.0.0-rc.1", + "·acme·@scope§bar@1.0.0%2Bbuild.1", + "·acme·@sindresorhus§is@1.0.0%2Bbuild.1", + "·acme·0x@2.1.3", + "·acme·a__b@2.1.3", + "·acme·a_b@1.0.0", + "·acme·a_b@10.0.0", + "·acme·a-b@1.0.0-rc.1", + "·acme·a.b@1.0.0%2Bbuild.1", + "·acme·A@1.0.0-rc.1", + "·acme·a@1.0.0%2Bbuild.1", + "·acme·a~b@1.0.0", + "·acme·a~b@10.0.0", + "·acme·a1@1.0.0-rc.1", + "·acme·ab@1.0.0", + "·acme·Ab@1.0.0%2Bbuild.1", + "·acme·ab@10.0.0", + "·acme·aB@2.1.3", + "·acme·is-number@1.0.0%2Bbuild.1", + "·acme·JSONStream@1.0.0%2Bbuild.1", + "·acme·left-pad@2.1.3", + "·acme·MS@1.0.0%2Bbuild.1", + "·acme·ms@2.1.3", + "·acme·react-dom@1.0.0-rc.1", + "·acme·react@1.0.0", + "·acme·react@10.0.0", + "·acme·use-sync-external-store@1.0.0-rc.1", + "·acme·z@1.0.0-rc.1", + "·acme·zz@1.0.0", + "·acme·zz@10.0.0", + "·http%3A§§127.0.0.1%3A4873§·@A§b@1.0.0", + "·http%3A§§127.0.0.1%3A4873§·@a§b@1.0.0-rc.1", + "·http%3A§§127.0.0.1%3A4873§·@A§b@10.0.0", + "·http%3A§§127.0.0.1%3A4873§·@isaacs§string-locale-compare@1.0.0", + "·http%3A§§127.0.0.1%3A4873§·@isaacs§string-locale-compare@10.0.0", + "·http%3A§§127.0.0.1%3A4873§·@jsr§std__semver@1.0.0%2Bbuild.1", + "·http%3A§§127.0.0.1%3A4873§·@scope_x§bar@1.0.0-rc.1", + "·http%3A§§127.0.0.1%3A4873§·@scope§bar-baz@1.0.0%2Bbuild.1", + "·http%3A§§127.0.0.1%3A4873§·@scope§bar@2.1.3", + "·http%3A§§127.0.0.1%3A4873§·@sindresorhus§is@2.1.3", + "·http%3A§§127.0.0.1%3A4873§·0x@1.0.0", + "·http%3A§§127.0.0.1%3A4873§·0x@10.0.0", + "·http%3A§§127.0.0.1%3A4873§·a__b@1.0.0", + "·http%3A§§127.0.0.1%3A4873§·a__b@10.0.0", + "·http%3A§§127.0.0.1%3A4873§·a_b@1.0.0-rc.1", + "·http%3A§§127.0.0.1%3A4873§·a-b@1.0.0%2Bbuild.1", + "·http%3A§§127.0.0.1%3A4873§·a.b@2.1.3", + "·http%3A§§127.0.0.1%3A4873§·A@1.0.0%2Bbuild.1", + "·http%3A§§127.0.0.1%3A4873§·a@2.1.3", + "·http%3A§§127.0.0.1%3A4873§·a~b@1.0.0-rc.1", + "·http%3A§§127.0.0.1%3A4873§·a1@1.0.0%2Bbuild.1", + "·http%3A§§127.0.0.1%3A4873§·aB@1.0.0", + "·http%3A§§127.0.0.1%3A4873§·ab@1.0.0-rc.1", + "·http%3A§§127.0.0.1%3A4873§·aB@10.0.0", + "·http%3A§§127.0.0.1%3A4873§·Ab@2.1.3", + "·http%3A§§127.0.0.1%3A4873§·is-number@2.1.3", + "·http%3A§§127.0.0.1%3A4873§·JSONStream@2.1.3", + "·http%3A§§127.0.0.1%3A4873§·left-pad@1.0.0", + "·http%3A§§127.0.0.1%3A4873§·left-pad@10.0.0", + "·http%3A§§127.0.0.1%3A4873§·ms@1.0.0", + "·http%3A§§127.0.0.1%3A4873§·ms@10.0.0", + "·http%3A§§127.0.0.1%3A4873§·MS@2.1.3", + "·http%3A§§127.0.0.1%3A4873§·react-dom@1.0.0%2Bbuild.1", + "·http%3A§§127.0.0.1%3A4873§·react@1.0.0-rc.1", + "·http%3A§§127.0.0.1%3A4873§·use-sync-external-store@1.0.0%2Bbuild.1", + "·http%3A§§127.0.0.1%3A4873§·z@1.0.0%2Bbuild.1", + "·http%3A§§127.0.0.1%3A4873§·zz@1.0.0-rc.1", + "·npm·@a§b@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·@A§b@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·@A§b@1.0.0%2Bbuild.1", + "·npm·@a§b@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·@A§b@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·@a§b@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·@A§b@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·@a§b@2.1.3", + "·npm·@isaacs§string-locale-compare@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·@isaacs§string-locale-compare@1.0.0%2Bbuild.1", + "·npm·@isaacs§string-locale-compare@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·@isaacs§string-locale-compare@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·@jsr§std__semver@1.0.0", + "·npm·@jsr§std__semver@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·@jsr§std__semver@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·@jsr§std__semver@10.0.0", + "·npm·@jsr§std__semver@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·@scope_x§bar@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·@scope_x§bar@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·@scope_x§bar@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·@scope_x§bar@2.1.3", + "·npm·@scope§bar-baz@1.0.0", + "·npm·@scope§bar-baz@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·@scope§bar-baz@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·@scope§bar-baz@10.0.0", + "·npm·@scope§bar-baz@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·@scope§bar@1.0.0-rc.1", + "·npm·@scope§bar@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·@scope§bar@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·@scope§bar@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·@sindresorhus§is@1.0.0-rc.1", + "·npm·@sindresorhus§is@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·@sindresorhus§is@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·@sindresorhus§is@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·0x@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·0x@1.0.0%2Bbuild.1", + "·npm·0x@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·0x@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·a__b@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·a__b@1.0.0%2Bbuild.1", + "·npm·a__b@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·a__b@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·a_b@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·a_b@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·a_b@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·a_b@2.1.3", + "·npm·a-b@1.0.0", + "·npm·a-b@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·a-b@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·a-b@10.0.0", + "·npm·a-b@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·a.b@1.0.0-rc.1", + "·npm·a.b@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·a.b@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·a.b@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·A@1.0.0", + "·npm·a@1.0.0-rc.1", + "·npm·a@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·A@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·a@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·A@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·A@10.0.0", + "·npm·a@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·A@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·a~b@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·a~b@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·a~b@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·a~b@2.1.3", + "·npm·a1@1.0.0", + "·npm·a1@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·a1@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·a1@10.0.0", + "·npm·a1@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·Ab@1.0.0-rc.1", + "·npm·ab@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·aB@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·Ab@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·aB@1.0.0%2Bbuild.1", + "·npm·ab@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·aB@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·Ab@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·ab@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·aB@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·Ab@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·ab@2.1.3", + "·npm·is-number@1.0.0-rc.1", + "·npm·is-number@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·is-number@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·is-number@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·JSONStream@1.0.0-rc.1", + "·npm·JSONStream@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·JSONStream@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·JSONStream@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·left-pad@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·left-pad@1.0.0%2Bbuild.1", + "·npm·left-pad@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·left-pad@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·MS@1.0.0-rc.1", + "·npm·ms@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·MS@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·ms@1.0.0%2Bbuild.1", + "·npm·ms@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·MS@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·ms@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·MS@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·react-dom@1.0.0", + "·npm·react-dom@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·react-dom@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·react-dom@10.0.0", + "·npm·react-dom@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·react@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·react@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·react@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·react@2.1.3", + "·npm·use-sync-external-store@1.0.0", + "·npm·use-sync-external-store@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·use-sync-external-store@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·use-sync-external-store@10.0.0", + "·npm·use-sync-external-store@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·z@1.0.0", + "·npm·z@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·z@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·z@10.0.0", + "·npm·z@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·zz@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·zz@1.0.0%2Bbuild.1·%E1%B9%97%3A3", + "·npm·zz@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·zz@2.1.3", + "~acme~@A+b@1.0.0_pbuild.1", + "~acme~@A+b@1.0.0_pbuild.1~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~@a+b@1.0.0-rc.1~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~@A+b@1.0.0-rc.1~peer.dbd5ca8b03a66489", + "~acme~@A+b@1.0.0~peer.2", + "~acme~@a+b@1.0.0~peer.dbd5ca8b03a66489", + "~acme~@A+b@10.0.0~peer.2", + "~acme~@a+b@10.0.0~peer.dbd5ca8b03a66489", + "~acme~@a+b@2.1.3", + "~acme~@a+b@2.1.3~peer.2", + "~acme~@isaacs+string-locale-compare@1.0.0_pbuild.1", + "~acme~@isaacs+string-locale-compare@1.0.0_pbuild.1~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~@isaacs+string-locale-compare@1.0.0-rc.1~peer.dbd5ca8b03a66489", + "~acme~@isaacs+string-locale-compare@1.0.0~peer.2", + "~acme~@isaacs+string-locale-compare@10.0.0~peer.2", + "~acme~@jsr+std____semver@1.0.0", + "~acme~@jsr+std____semver@1.0.0_pbuild.1~peer.2", + "~acme~@jsr+std____semver@1.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~@jsr+std____semver@10.0.0", + "~acme~@jsr+std____semver@10.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~@jsr+std____semver@2.1.3~peer.dbd5ca8b03a66489", + "~acme~@scope__x+bar@1.0.0-rc.1~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~@scope__x+bar@1.0.0~peer.dbd5ca8b03a66489", + "~acme~@scope__x+bar@10.0.0~peer.dbd5ca8b03a66489", + "~acme~@scope__x+bar@2.1.3", + "~acme~@scope__x+bar@2.1.3~peer.2", + "~acme~@scope+bar-baz@1.0.0", + "~acme~@scope+bar-baz@1.0.0_pbuild.1~peer.2", + "~acme~@scope+bar-baz@1.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~@scope+bar-baz@10.0.0", + "~acme~@scope+bar-baz@10.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~@scope+bar-baz@2.1.3~peer.dbd5ca8b03a66489", + "~acme~@scope+bar@1.0.0_pbuild.1~peer.dbd5ca8b03a66489", + "~acme~@scope+bar@1.0.0-rc.1", + "~acme~@scope+bar@1.0.0-rc.1~peer.2", + "~acme~@scope+bar@2.1.3~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~@sindresorhus+is@1.0.0_pbuild.1~peer.dbd5ca8b03a66489", + "~acme~@sindresorhus+is@1.0.0-rc.1", + "~acme~@sindresorhus+is@1.0.0-rc.1~peer.2", + "~acme~@sindresorhus+is@2.1.3~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~0x@1.0.0_pbuild.1", + "~acme~0x@1.0.0_pbuild.1~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~0x@1.0.0-rc.1~peer.dbd5ca8b03a66489", + "~acme~0x@1.0.0~peer.2", + "~acme~0x@10.0.0~peer.2", + "~acme~a____b@1.0.0_pbuild.1", + "~acme~a____b@1.0.0_pbuild.1~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~a____b@1.0.0-rc.1~peer.dbd5ca8b03a66489", + "~acme~a____b@1.0.0~peer.2", + "~acme~a____b@10.0.0~peer.2", + "~acme~a__b@1.0.0-rc.1~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~a__b@1.0.0~peer.dbd5ca8b03a66489", + "~acme~a__b@10.0.0~peer.dbd5ca8b03a66489", + "~acme~a__b@2.1.3", + "~acme~a__b@2.1.3~peer.2", + "~acme~a_tb@1.0.0-rc.1~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~a_tb@1.0.0~peer.dbd5ca8b03a66489", + "~acme~a_tb@10.0.0~peer.dbd5ca8b03a66489", + "~acme~a_tb@2.1.3", + "~acme~a_tb@2.1.3~peer.2", + "~acme~a-b@1.0.0", + "~acme~a-b@1.0.0_pbuild.1~peer.2", + "~acme~a-b@1.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~a-b@10.0.0", + "~acme~a-b@10.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~a-b@2.1.3~peer.dbd5ca8b03a66489", + "~acme~a.b@1.0.0_pbuild.1~peer.dbd5ca8b03a66489", + "~acme~a.b@1.0.0-rc.1", + "~acme~a.b@1.0.0-rc.1~peer.2", + "~acme~a.b@2.1.3~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~A@1.0.0", + "~acme~A@1.0.0_pbuild.1~peer.2", + "~acme~a@1.0.0_pbuild.1~peer.dbd5ca8b03a66489", + "~acme~a@1.0.0-rc.1", + "~acme~a@1.0.0-rc.1~peer.2", + "~acme~A@1.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~A@10.0.0", + "~acme~A@10.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~a@2.1.3~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~A@2.1.3~peer.dbd5ca8b03a66489", + "~acme~a1@1.0.0", + "~acme~a1@1.0.0_pbuild.1~peer.2", + "~acme~a1@1.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~a1@10.0.0", + "~acme~a1@10.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~a1@2.1.3~peer.dbd5ca8b03a66489", + "~acme~aB@1.0.0_pbuild.1", + "~acme~aB@1.0.0_pbuild.1~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~Ab@1.0.0_pbuild.1~peer.dbd5ca8b03a66489", + "~acme~Ab@1.0.0-rc.1", + "~acme~ab@1.0.0-rc.1~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~Ab@1.0.0-rc.1~peer.2", + "~acme~aB@1.0.0-rc.1~peer.dbd5ca8b03a66489", + "~acme~aB@1.0.0~peer.2", + "~acme~ab@1.0.0~peer.dbd5ca8b03a66489", + "~acme~aB@10.0.0~peer.2", + "~acme~ab@10.0.0~peer.dbd5ca8b03a66489", + "~acme~ab@2.1.3", + "~acme~Ab@2.1.3~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~ab@2.1.3~peer.2", + "~acme~is-number@1.0.0_pbuild.1~peer.dbd5ca8b03a66489", + "~acme~is-number@1.0.0-rc.1", + "~acme~is-number@1.0.0-rc.1~peer.2", + "~acme~is-number@2.1.3~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~JSONStream@1.0.0_pbuild.1~peer.dbd5ca8b03a66489", + "~acme~JSONStream@1.0.0-rc.1", + "~acme~JSONStream@1.0.0-rc.1~peer.2", + "~acme~JSONStream@2.1.3~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~left-pad@1.0.0_pbuild.1", + "~acme~left-pad@1.0.0_pbuild.1~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~left-pad@1.0.0-rc.1~peer.dbd5ca8b03a66489", + "~acme~left-pad@1.0.0~peer.2", + "~acme~left-pad@10.0.0~peer.2", + "~acme~ms@1.0.0_pbuild.1", + "~acme~ms@1.0.0_pbuild.1~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~MS@1.0.0_pbuild.1~peer.dbd5ca8b03a66489", + "~acme~MS@1.0.0-rc.1", + "~acme~MS@1.0.0-rc.1~peer.2", + "~acme~ms@1.0.0-rc.1~peer.dbd5ca8b03a66489", + "~acme~ms@1.0.0~peer.2", + "~acme~ms@10.0.0~peer.2", + "~acme~MS@2.1.3~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~react-dom@1.0.0", + "~acme~react-dom@1.0.0_pbuild.1~peer.2", + "~acme~react-dom@1.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~react-dom@10.0.0", + "~acme~react-dom@10.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~react-dom@2.1.3~peer.dbd5ca8b03a66489", + "~acme~react@1.0.0-rc.1~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~react@1.0.0~peer.dbd5ca8b03a66489", + "~acme~react@10.0.0~peer.dbd5ca8b03a66489", + "~acme~react@2.1.3", + "~acme~react@2.1.3~peer.2", + "~acme~use-sync-external-store@1.0.0", + "~acme~use-sync-external-store@1.0.0_pbuild.1~peer.2", + "~acme~use-sync-external-store@1.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~use-sync-external-store@10.0.0", + "~acme~use-sync-external-store@10.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~use-sync-external-store@2.1.3~peer.dbd5ca8b03a66489", + "~acme~z@1.0.0", + "~acme~z@1.0.0_pbuild.1~peer.2", + "~acme~z@1.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~z@10.0.0", + "~acme~z@10.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~z@2.1.3~peer.dbd5ca8b03a66489", + "~acme~zz@1.0.0-rc.1~_croot_s_g_s#to-regex-range_s_g_s#is-number", + "~acme~zz@1.0.0~peer.dbd5ca8b03a66489", + "~acme~zz@10.0.0~peer.dbd5ca8b03a66489", + "~acme~zz@2.1.3", + "~acme~zz@2.1.3~peer.2", + "~https_c++registry.example.com+npm+~@A+b@1.0.0", + "~https_c++registry.example.com+npm+~@a+b@1.0.0-rc.1", + "~https_c++registry.example.com+npm+~@A+b@10.0.0", + "~https_c++registry.example.com+npm+~@isaacs+string-locale-compare@1.0.0", + "~https_c++registry.example.com+npm+~@isaacs+string-locale-compare@10.0.0", + "~https_c++registry.example.com+npm+~@jsr+std____semver@1.0.0_pbuild.1", + "~https_c++registry.example.com+npm+~@scope__x+bar@1.0.0-rc.1", + "~https_c++registry.example.com+npm+~@scope+bar-baz@1.0.0_pbuild.1", + "~https_c++registry.example.com+npm+~@scope+bar@2.1.3", + "~https_c++registry.example.com+npm+~@sindresorhus+is@2.1.3", + "~https_c++registry.example.com+npm+~0x@1.0.0", + "~https_c++registry.example.com+npm+~0x@10.0.0", + "~https_c++registry.example.com+npm+~a____b@1.0.0", + "~https_c++registry.example.com+npm+~a____b@10.0.0", + "~https_c++registry.example.com+npm+~a__b@1.0.0-rc.1", + "~https_c++registry.example.com+npm+~a_tb@1.0.0-rc.1", + "~https_c++registry.example.com+npm+~a-b@1.0.0_pbuild.1", + "~https_c++registry.example.com+npm+~a.b@2.1.3", + "~https_c++registry.example.com+npm+~A@1.0.0_pbuild.1", + "~https_c++registry.example.com+npm+~a@2.1.3", + "~https_c++registry.example.com+npm+~a1@1.0.0_pbuild.1", + "~https_c++registry.example.com+npm+~aB@1.0.0", + "~https_c++registry.example.com+npm+~ab@1.0.0-rc.1", + "~https_c++registry.example.com+npm+~aB@10.0.0", + "~https_c++registry.example.com+npm+~Ab@2.1.3", + "~https_c++registry.example.com+npm+~is-number@2.1.3", + "~https_c++registry.example.com+npm+~JSONStream@2.1.3", + "~https_c++registry.example.com+npm+~left-pad@1.0.0", + "~https_c++registry.example.com+npm+~left-pad@10.0.0", + "~https_c++registry.example.com+npm+~ms@1.0.0", + "~https_c++registry.example.com+npm+~ms@10.0.0", + "~https_c++registry.example.com+npm+~MS@2.1.3", + "~https_c++registry.example.com+npm+~react-dom@1.0.0_pbuild.1", + "~https_c++registry.example.com+npm+~react@1.0.0-rc.1", + "~https_c++registry.example.com+npm+~use-sync-external-store@1.0.0_pbuild.1", + "~https_c++registry.example.com+npm+~z@1.0.0_pbuild.1", + "~https_c++registry.example.com+npm+~zz@1.0.0-rc.1", + "~jsr~@a+b@1.0.0", + "~jsr~@a+b@10.0.0", + "~jsr~@A+b@2.1.3", + "~jsr~@isaacs+string-locale-compare@2.1.3", + "~jsr~@jsr+std____semver@1.0.0-rc.1", + "~jsr~@scope__x+bar@1.0.0", + "~jsr~@scope__x+bar@10.0.0", + "~jsr~@scope+bar-baz@1.0.0-rc.1", + "~jsr~@scope+bar@1.0.0_pbuild.1", + "~jsr~@sindresorhus+is@1.0.0_pbuild.1", + "~jsr~0x@2.1.3", + "~jsr~a____b@2.1.3", + "~jsr~a__b@1.0.0", + "~jsr~a__b@10.0.0", + "~jsr~a_tb@1.0.0", + "~jsr~a_tb@10.0.0", + "~jsr~a-b@1.0.0-rc.1", + "~jsr~a.b@1.0.0_pbuild.1", + "~jsr~a@1.0.0_pbuild.1", + "~jsr~A@1.0.0-rc.1", + "~jsr~a1@1.0.0-rc.1", + "~jsr~ab@1.0.0", + "~jsr~Ab@1.0.0_pbuild.1", + "~jsr~ab@10.0.0", + "~jsr~aB@2.1.3", + "~jsr~is-number@1.0.0_pbuild.1", + "~jsr~JSONStream@1.0.0_pbuild.1", + "~jsr~left-pad@2.1.3", + "~jsr~MS@1.0.0_pbuild.1", + "~jsr~ms@2.1.3", + "~jsr~react-dom@1.0.0-rc.1", + "~jsr~react@1.0.0", + "~jsr~react@10.0.0", + "~jsr~use-sync-external-store@1.0.0-rc.1", + "~jsr~z@1.0.0-rc.1", + "~jsr~zz@1.0.0", + "~jsr~zz@10.0.0", + "~npm~@a+b@1.0.0", + "~npm~@A+b@1.0.0", + "~npm~@a+b@1.0.0_pbuild.1", + "~npm~@A+b@1.0.0_pbuild.1", + "~npm~@a+b@1.0.0-rc.1", + "~npm~@A+b@1.0.0-rc.1", + "~npm~@a+b@10.0.0", + "~npm~@A+b@10.0.0", + "~npm~@a+b@2.1.3", + "~npm~@A+b@2.1.3", + "~npm~@isaacs+string-locale-compare@1.0.0", + "~npm~@isaacs+string-locale-compare@1.0.0_pbuild.1", + "~npm~@isaacs+string-locale-compare@1.0.0-rc.1", + "~npm~@isaacs+string-locale-compare@10.0.0", + "~npm~@isaacs+string-locale-compare@2.1.3", + "~npm~@jsr+std____semver@1.0.0", + "~npm~@jsr+std____semver@1.0.0_pbuild.1", + "~npm~@jsr+std____semver@1.0.0-rc.1", + "~npm~@jsr+std____semver@10.0.0", + "~npm~@jsr+std____semver@2.1.3", + "~npm~@scope__x+bar@1.0.0", + "~npm~@scope__x+bar@1.0.0_pbuild.1", + "~npm~@scope__x+bar@1.0.0-rc.1", + "~npm~@scope__x+bar@10.0.0", + "~npm~@scope__x+bar@2.1.3", + "~npm~@scope+bar-baz@1.0.0", + "~npm~@scope+bar-baz@1.0.0_pbuild.1", + "~npm~@scope+bar-baz@1.0.0-rc.1", + "~npm~@scope+bar-baz@10.0.0", + "~npm~@scope+bar-baz@2.1.3", + "~npm~@scope+bar@1.0.0", + "~npm~@scope+bar@1.0.0_pbuild.1", + "~npm~@scope+bar@1.0.0-rc.1", + "~npm~@scope+bar@10.0.0", + "~npm~@scope+bar@2.1.3", + "~npm~@sindresorhus+is@1.0.0", + "~npm~@sindresorhus+is@1.0.0_pbuild.1", + "~npm~@sindresorhus+is@1.0.0-rc.1", + "~npm~@sindresorhus+is@10.0.0", + "~npm~@sindresorhus+is@2.1.3", + "~npm~0x@1.0.0", + "~npm~0x@1.0.0_pbuild.1", + "~npm~0x@1.0.0-rc.1", + "~npm~0x@10.0.0", + "~npm~0x@2.1.3", + "~npm~a____b@1.0.0", + "~npm~a____b@1.0.0_pbuild.1", + "~npm~a____b@1.0.0-rc.1", + "~npm~a____b@10.0.0", + "~npm~a____b@2.1.3", + "~npm~a__b@1.0.0", + "~npm~a__b@1.0.0_pbuild.1", + "~npm~a__b@1.0.0-rc.1", + "~npm~a__b@10.0.0", + "~npm~a__b@2.1.3", + "~npm~a_tb@1.0.0", + "~npm~a_tb@1.0.0_pbuild.1", + "~npm~a_tb@1.0.0-rc.1", + "~npm~a_tb@10.0.0", + "~npm~a_tb@2.1.3", + "~npm~a-b@1.0.0", + "~npm~a-b@1.0.0_pbuild.1", + "~npm~a-b@1.0.0-rc.1", + "~npm~a-b@10.0.0", + "~npm~a-b@2.1.3", + "~npm~a.b@1.0.0", + "~npm~a.b@1.0.0_pbuild.1", + "~npm~a.b@1.0.0-rc.1", + "~npm~a.b@10.0.0", + "~npm~a.b@2.1.3", + "~npm~a@1.0.0", + "~npm~A@1.0.0", + "~npm~a@1.0.0_pbuild.1", + "~npm~A@1.0.0_pbuild.1", + "~npm~a@1.0.0-rc.1", + "~npm~A@1.0.0-rc.1", + "~npm~a@10.0.0", + "~npm~A@10.0.0", + "~npm~a@2.1.3", + "~npm~A@2.1.3", + "~npm~a1@1.0.0", + "~npm~a1@1.0.0_pbuild.1", + "~npm~a1@1.0.0-rc.1", + "~npm~a1@10.0.0", + "~npm~a1@2.1.3", + "~npm~ab@1.0.0", + "~npm~aB@1.0.0", + "~npm~Ab@1.0.0", + "~npm~ab@1.0.0_pbuild.1", + "~npm~aB@1.0.0_pbuild.1", + "~npm~Ab@1.0.0_pbuild.1", + "~npm~ab@1.0.0-rc.1", + "~npm~aB@1.0.0-rc.1", + "~npm~Ab@1.0.0-rc.1", + "~npm~ab@10.0.0", + "~npm~aB@10.0.0", + "~npm~Ab@10.0.0", + "~npm~ab@2.1.3", + "~npm~aB@2.1.3", + "~npm~Ab@2.1.3", + "~npm~is-number@1.0.0", + "~npm~is-number@1.0.0_pbuild.1", + "~npm~is-number@1.0.0-rc.1", + "~npm~is-number@10.0.0", + "~npm~is-number@2.1.3", + "~npm~JSONStream@1.0.0", + "~npm~JSONStream@1.0.0_pbuild.1", + "~npm~JSONStream@1.0.0-rc.1", + "~npm~JSONStream@10.0.0", + "~npm~JSONStream@2.1.3", + "~npm~left-pad@1.0.0", + "~npm~left-pad@1.0.0_pbuild.1", + "~npm~left-pad@1.0.0-rc.1", + "~npm~left-pad@10.0.0", + "~npm~left-pad@2.1.3", + "~npm~ms@1.0.0", + "~npm~MS@1.0.0", + "~npm~ms@1.0.0_pbuild.1", + "~npm~MS@1.0.0_pbuild.1", + "~npm~ms@1.0.0-rc.1", + "~npm~MS@1.0.0-rc.1", + "~npm~ms@10.0.0", + "~npm~MS@10.0.0", + "~npm~ms@2.1.3", + "~npm~MS@2.1.3", + "~npm~react-dom@1.0.0", + "~npm~react-dom@1.0.0_pbuild.1", + "~npm~react-dom@1.0.0-rc.1", + "~npm~react-dom@10.0.0", + "~npm~react-dom@2.1.3", + "~npm~react@1.0.0", + "~npm~react@1.0.0_pbuild.1", + "~npm~react@1.0.0-rc.1", + "~npm~react@10.0.0", + "~npm~react@2.1.3", + "~npm~use-sync-external-store@1.0.0", + "~npm~use-sync-external-store@1.0.0_pbuild.1", + "~npm~use-sync-external-store@1.0.0-rc.1", + "~npm~use-sync-external-store@10.0.0", + "~npm~use-sync-external-store@2.1.3", + "~npm~z@1.0.0", + "~npm~z@1.0.0_pbuild.1", + "~npm~z@1.0.0-rc.1", + "~npm~z@10.0.0", + "~npm~z@2.1.3", + "~npm~zz@1.0.0", + "~npm~zz@1.0.0_pbuild.1", + "~npm~zz@1.0.0-rc.1", + "~npm~zz@10.0.0", + "~npm~zz@2.1.3", + "file·.", + "file·..", + "file·..§x", + "file·.§packages§a", + "file·.socket§vendor§npm§0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b§@isaacs§string-locale-compare-1.0.0%2Bbuild.1§node_modules§@isaacs§string-locale-compare", + "file·.socket§vendor§npm§0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b§@scope§bar-10.0.0§node_modules§@scope§bar", + "file·.socket§vendor§npm§0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b§0x-1.0.0§node_modules§0x", + "file·.socket§vendor§npm§0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b§a_b-1.0.0-rc.1§node_modules§a_b", + "file·.socket§vendor§npm§0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b§a-1.0.0§node_modules§a", + "file·.socket§vendor§npm§0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b§a1-10.0.0§node_modules§a1", + "file·.socket§vendor§npm§0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b§aB-2.1.3§node_modules§aB", + "file·.socket§vendor§npm§0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b§MS-1.0.0%2Bbuild.1§node_modules§MS", + "file·.socket§vendor§npm§0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b§react-2.1.3§node_modules§react", + "file·.socket§vendor§npm§0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b§use-sync-external-store-1.0.0-rc.1§node_modules§use-sync-external-store", + "file·.socket§vendor§npm§80630680-4da6-45f9-bba8-b888e0ffd58c§@a§b-1.0.0%2Bbuild.1§node_modules§@a§b", + "file·.socket§vendor§npm§80630680-4da6-45f9-bba8-b888e0ffd58c§@scope§bar-baz-1.0.0§node_modules§@scope§bar-baz", + "file·.socket§vendor§npm§80630680-4da6-45f9-bba8-b888e0ffd58c§@sindresorhus§is-2.1.3§node_modules§@sindresorhus§is", + "file·.socket§vendor§npm§80630680-4da6-45f9-bba8-b888e0ffd58c§a__b-1.0.0%2Bbuild.1§node_modules§a__b", + "file·.socket§vendor§npm§80630680-4da6-45f9-bba8-b888e0ffd58c§A-1.0.0-rc.1§node_modules§A", + "file·.socket§vendor§npm§80630680-4da6-45f9-bba8-b888e0ffd58c§a~b-1.0.0§node_modules§a~b", + "file·.socket§vendor§npm§80630680-4da6-45f9-bba8-b888e0ffd58c§Ab-10.0.0§node_modules§Ab", + "file·.socket§vendor§npm§80630680-4da6-45f9-bba8-b888e0ffd58c§is-number-1.0.0-rc.1§node_modules§is-number", + "file·.socket§vendor§npm§80630680-4da6-45f9-bba8-b888e0ffd58c§left-pad-10.0.0§node_modules§left-pad", + "file·.socket§vendor§npm§80630680-4da6-45f9-bba8-b888e0ffd58c§z-2.1.3§node_modules§z", + "file·.socket§vendor§npm§ffffffff-2222-4333-8444-555555555555§@A§b-2.1.3§node_modules§@A§b", + "file·.socket§vendor§npm§ffffffff-2222-4333-8444-555555555555§@jsr§std__semver-10.0.0§node_modules§@jsr§std__semver", + "file·.socket§vendor§npm§ffffffff-2222-4333-8444-555555555555§@scope_x§bar-1.0.0-rc.1§node_modules§@scope_x§bar", + "file·.socket§vendor§npm§ffffffff-2222-4333-8444-555555555555§a-b-1.0.0§node_modules§a-b", + "file·.socket§vendor§npm§ffffffff-2222-4333-8444-555555555555§a.b-2.1.3§node_modules§a.b", + "file·.socket§vendor§npm§ffffffff-2222-4333-8444-555555555555§ab-1.0.0%2Bbuild.1§node_modules§ab", + "file·.socket§vendor§npm§ffffffff-2222-4333-8444-555555555555§JSONStream-1.0.0§node_modules§JSONStream", + "file·.socket§vendor§npm§ffffffff-2222-4333-8444-555555555555§ms-1.0.0-rc.1§node_modules§ms", + "file·.socket§vendor§npm§ffffffff-2222-4333-8444-555555555555§react-dom-1.0.0%2Bbuild.1§node_modules§react-dom", + "file·.socket§vendor§npm§ffffffff-2222-4333-8444-555555555555§zz-10.0.0§node_modules§zz", + "file·a%20b§c", + "file·vendor§x", + "file·vendor§x.tgz", + "file~_d", + "file~._d", + "file~..+x", + "file~.+packages+a", + "file~.socket+vendor+npm+0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b+@isaacs+string-locale-compare-1.0.0_pbuild.1+node__modules+@isaacs+string-locale-compare", + "file~.socket+vendor+npm+0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b+@scope+bar-10.0.0+node__modules+@scope+bar", + "file~.socket+vendor+npm+0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b+0x-1.0.0+node__modules+0x", + "file~.socket+vendor+npm+0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b+a__b-1.0.0-rc.1+node__modules+a__b", + "file~.socket+vendor+npm+0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b+a-1.0.0+node__modules+a", + "file~.socket+vendor+npm+0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b+a1-10.0.0+node__modules+a1", + "file~.socket+vendor+npm+0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b+aB-2.1.3+node__modules+aB", + "file~.socket+vendor+npm+0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b+MS-1.0.0_pbuild.1+node__modules+MS", + "file~.socket+vendor+npm+0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b+react-2.1.3+node__modules+react", + "file~.socket+vendor+npm+0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b+use-sync-external-store-1.0.0-rc.1+node__modules+use-sync-external-store", + "file~.socket+vendor+npm+80630680-4da6-45f9-bba8-b888e0ffd58c+@a+b-1.0.0_pbuild.1+node__modules+@a+b", + "file~.socket+vendor+npm+80630680-4da6-45f9-bba8-b888e0ffd58c+@scope+bar-baz-1.0.0+node__modules+@scope+bar-baz", + "file~.socket+vendor+npm+80630680-4da6-45f9-bba8-b888e0ffd58c+@sindresorhus+is-2.1.3+node__modules+@sindresorhus+is", + "file~.socket+vendor+npm+80630680-4da6-45f9-bba8-b888e0ffd58c+a____b-1.0.0_pbuild.1+node__modules+a____b", + "file~.socket+vendor+npm+80630680-4da6-45f9-bba8-b888e0ffd58c+a_tb-1.0.0+node__modules+a_tb", + "file~.socket+vendor+npm+80630680-4da6-45f9-bba8-b888e0ffd58c+A-1.0.0-rc.1+node__modules+A", + "file~.socket+vendor+npm+80630680-4da6-45f9-bba8-b888e0ffd58c+Ab-10.0.0+node__modules+Ab", + "file~.socket+vendor+npm+80630680-4da6-45f9-bba8-b888e0ffd58c+is-number-1.0.0-rc.1+node__modules+is-number", + "file~.socket+vendor+npm+80630680-4da6-45f9-bba8-b888e0ffd58c+left-pad-10.0.0+node__modules+left-pad", + "file~.socket+vendor+npm+80630680-4da6-45f9-bba8-b888e0ffd58c+z-2.1.3+node__modules+z", + "file~.socket+vendor+npm+ffffffff-2222-4333-8444-555555555555+@A+b-2.1.3+node__modules+@A+b", + "file~.socket+vendor+npm+ffffffff-2222-4333-8444-555555555555+@jsr+std____semver-10.0.0+node__modules+@jsr+std____semver", + "file~.socket+vendor+npm+ffffffff-2222-4333-8444-555555555555+@scope__x+bar-1.0.0-rc.1+node__modules+@scope__x+bar", + "file~.socket+vendor+npm+ffffffff-2222-4333-8444-555555555555+a-b-1.0.0+node__modules+a-b", + "file~.socket+vendor+npm+ffffffff-2222-4333-8444-555555555555+a.b-2.1.3+node__modules+a.b", + "file~.socket+vendor+npm+ffffffff-2222-4333-8444-555555555555+ab-1.0.0_pbuild.1+node__modules+ab", + "file~.socket+vendor+npm+ffffffff-2222-4333-8444-555555555555+JSONStream-1.0.0+node__modules+JSONStream", + "file~.socket+vendor+npm+ffffffff-2222-4333-8444-555555555555+ms-1.0.0-rc.1+node__modules+ms", + "file~.socket+vendor+npm+ffffffff-2222-4333-8444-555555555555+react-dom-1.0.0_pbuild.1+node__modules+react-dom", + "file~.socket+vendor+npm+ffffffff-2222-4333-8444-555555555555+zz-10.0.0+node__modules+zz", + "file~a_sb+c", + "file~vendor+x", + "file~vendor+x.tgz", + "git·git%2Bssh%3A§§git@host§x.git·main", + "git·github%3Auser§proj·semver%3A%5E1", + "git·github%3Auser§proj·v1.0.0", + "git~git_pssh_c++git@host+x.git~main", + "git~github_cuser+proj~semver_c^1", + "git~github_cuser+proj~v1.0.0", + "remote·http%3A§§h§x.tgz", + "remote·https%3A§§e.com§r-1.0.0.tgz", + "remote·https%3A§§e.com§R-1.0.0.tgz", + "remote~http_c++h+x.tgz", + "remote~https_c++e.com+r-1.0.0.tgz", + "remote~https_c++e.com+R-1.0.0.tgz", + "workspace·apps§web-1", + "workspace·packages§a", + "workspace·packages§b", + "workspace·packages§my_lib", + "workspace~apps+web-1", + "workspace~packages+a", + "workspace~packages+b", + "workspace~packages+my__lib" + ], + "edges": [ + [ + "file·. dep-0-1", + "dev ^1.0.0 || ^2 ··@isaacs§string-locale-compare@10.0.0" + ], + [ + "file·. dep-0-2", + "optional 1.0.0 ··@scope§bar-baz@1.0.0-rc.1" + ], + [ + "file·. dep-0-3", + "peer ^1.0.0 || ^2 ··@sindresorhus§is@2.1.3" + ], + [ + "file·. dep-0-0", + "prod 1.0.0 MISSING" + ], + [ + "file~_d dep-1-0", + "dev 1.0.0 ··@A§b@10.0.0" + ], + [ + "file~_d dep-1-1", + "optional ^1.0.0 || ^2 ··@scope_x§bar@1.0.0" + ], + [ + "file~_d dep-1-2", + "peer 1.0.0 ··@scope§bar@10.0.0" + ], + [ + "file~_d dep-1-3", + "peerOptional ^1.0.0 || ^2 ··a__b@1.0.0-rc.1" + ], + [ + "workspace·apps§web-1 dep-2-0", + "optional 1.0.0 ··@isaacs§string-locale-compare@2.1.3" + ], + [ + "workspace·apps§web-1 dep-2-1", + "peer ^1.0.0 || ^2 ··@scope§bar-baz@1.0.0%2Bbuild.1" + ], + [ + "workspace·apps§web-1 dep-2-2", + "peerOptional 1.0.0 ··0x@1.0.0" + ], + [ + "workspace·apps§web-1 dep-2-3", + "prod ^1.0.0 || ^2 ··a_b@10.0.0" + ], + [ + "workspace·packages§a dep-3-3", + "dev ^1.0.0 || ^2 ··a.b@1.0.0" + ], + [ + "workspace·packages§a dep-3-0", + "peer 1.0.0 ··@scope_x§bar@1.0.0-rc.1" + ], + [ + "workspace·packages§a dep-3-1", + "peerOptional ^1.0.0 || ^2 ··@scope§bar@2.1.3" + ], + [ + "workspace·packages§a dep-3-2", + "prod 1.0.0 ··a__b@1.0.0%2Bbuild.1" + ], + [ + "workspace·packages§b dep-4-2", + "dev 1.0.0 ··a_b@2.1.3" + ], + [ + "workspace·packages§b dep-4-3", + "optional ^1.0.0 || ^2 ··a@1.0.0-rc.1" + ], + [ + "workspace·packages§b dep-4-0", + "peerOptional 1.0.0 ··@scope§bar-baz@10.0.0" + ], + [ + "workspace·packages§b dep-4-1", + "prod ^1.0.0 || ^2 ··0x@1.0.0-rc.1" + ], + [ + "workspace·packages§my_lib dep-5-1", + "dev ^1.0.0 || ^2 ··a__b@10.0.0" + ], + [ + "workspace·packages§my_lib dep-5-2", + "optional 1.0.0 ··a.b@1.0.0-rc.1" + ], + [ + "workspace·packages§my_lib dep-5-3", + "peer ^1.0.0 || ^2 ··A@2.1.3" + ], + [ + "workspace·packages§my_lib dep-5-0", + "prod 1.0.0 ··@sindresorhus§is@1.0.0" + ], + [ + "workspace~apps+web-1 dep-6-0", + "dev 1.0.0 ··0x@1.0.0%2Bbuild.1" + ], + [ + "workspace~apps+web-1 dep-6-1", + "optional ^1.0.0 || ^2 ··a-b@1.0.0" + ], + [ + "workspace~apps+web-1 dep-6-2", + "peer 1.0.0 ··A@1.0.0-rc.1" + ], + [ + "workspace~apps+web-1 dep-6-3", + "peerOptional ^1.0.0 || ^2 ··a1@1.0.0-rc.1" + ], + [ + "workspace~packages+a dep-7-0", + "optional 1.0.0 ··a__b@2.1.3" + ], + [ + "workspace~packages+a dep-7-1", + "peer ^1.0.0 || ^2 ··a.b@1.0.0%2Bbuild.1" + ], + [ + "workspace~packages+a dep-7-2", + "peerOptional 1.0.0 ··a~b@1.0.0" + ], + [ + "workspace~packages+a dep-7-3", + "prod ^1.0.0 || ^2 ··ab@1.0.0-rc.1" + ], + [ + "workspace~packages+b dep-8-3", + "dev ^1.0.0 || ^2 MISSING" + ], + [ + "workspace~packages+b dep-8-0", + "peer 1.0.0 ··a-b@1.0.0-rc.1" + ], + [ + "workspace~packages+b dep-8-1", + "peerOptional ^1.0.0 || ^2 ··a@1.0.0%2Bbuild.1" + ], + [ + "workspace~packages+b dep-8-2", + "prod 1.0.0 ··a1@1.0.0%2Bbuild.1" + ], + [ + "workspace~packages+my__lib dep-9-2", + "dev 1.0.0 MISSING" + ], + [ + "workspace~packages+my__lib dep-9-3", + "optional ^1.0.0 || ^2 ··is-number@1.0.0%2Bbuild.1" + ], + [ + "workspace~packages+my__lib dep-9-0", + "peerOptional 1.0.0 ··a.b@10.0.0" + ], + [ + "workspace~packages+my__lib dep-9-1", + "prod ^1.0.0 || ^2 ··a~b@1.0.0-rc.1" + ], + [ + "··@a§b@1.0.0 dep-10-1", + "dev ^1.0.0 || ^2 MISSING" + ], + [ + "··@a§b@1.0.0 dep-10-2", + "optional 1.0.0 ··Ab@10.0.0" + ], + [ + "··@a§b@1.0.0 dep-10-3", + "peer ^1.0.0 || ^2 ··JSONStream@2.1.3" + ], + [ + "··@a§b@1.0.0 dep-10-0", + "prod 1.0.0 ··A@1.0.0%2Bbuild.1" + ], + [ + "··@jsr§std__semver@1.0.0%2Bbuild.1 dep-11-0", + "dev 1.0.0 MISSING" + ], + [ + "··@jsr§std__semver@1.0.0%2Bbuild.1 dep-11-1", + "optional ^1.0.0 || ^2 ··Ab@1.0.0-rc.1" + ], + [ + "··@jsr§std__semver@1.0.0%2Bbuild.1 dep-11-2", + "peer 1.0.0 ··is-number@10.0.0" + ], + [ + "··@jsr§std__semver@1.0.0%2Bbuild.1 dep-11-3", + "peerOptional ^1.0.0 || ^2 ··MS@1.0.0" + ], + [ + "··@scope§bar@2.1.3 dep-12-0", + "optional 1.0.0 ··a1@2.1.3" + ], + [ + "··@scope§bar@2.1.3 dep-12-1", + "peer ^1.0.0 || ^2 ··ab@2.1.3" + ], + [ + "··@scope§bar@2.1.3 dep-12-2", + "peerOptional 1.0.0 ··left-pad@1.0.0" + ], + [ + "··@scope§bar@2.1.3 dep-12-3", + "prod ^1.0.0 || ^2 ··ms@2.1.3" + ], + [ + "··a_b@1.0.0-rc.1 dep-13-3", + "dev ^1.0.0 || ^2 ··react@1.0.0" + ], + [ + "··a_b@1.0.0-rc.1 dep-13-0", + "peer 1.0.0 ··ab@1.0.0%2Bbuild.1" + ], + [ + "··a_b@1.0.0-rc.1 dep-13-1", + "peerOptional ^1.0.0 || ^2 ··is-number@2.1.3" + ], + [ + "··a_b@1.0.0-rc.1 dep-13-2", + "prod 1.0.0 ··ms@1.0.0-rc.1" + ], + [ + "··A@1.0.0-rc.1 dep-14-2", + "dev 1.0.0 ··MS@2.1.3" + ], + [ + "··A@1.0.0-rc.1 dep-14-3", + "optional ^1.0.0 || ^2 ··use-sync-external-store@1.0.0%2Bbuild.1" + ], + [ + "··A@1.0.0-rc.1 dep-14-0", + "peerOptional 1.0.0 ··aB@2.1.3" + ], + [ + "··A@1.0.0-rc.1 dep-14-1", + "prod ^1.0.0 || ^2 ··left-pad@1.0.0-rc.1" + ], + [ + "··ab@1.0.0 dep-15-1", + "dev ^1.0.0 || ^2 ··MS@1.0.0-rc.1" + ], + [ + "··ab@1.0.0 dep-15-2", + "optional 1.0.0 ··react@1.0.0-rc.1" + ], + [ + "··ab@1.0.0 dep-15-3", + "peer ^1.0.0 || ^2 ··z@2.1.3" + ], + [ + "··ab@1.0.0 dep-15-0", + "prod 1.0.0 ··JSONStream@1.0.0" + ], + [ + "··is-number@1.0.0%2Bbuild.1 dep-16-0", + "dev 1.0.0 ··left-pad@1.0.0%2Bbuild.1" + ], + [ + "··is-number@1.0.0%2Bbuild.1 dep-16-1", + "optional ^1.0.0 || ^2 ··react-dom@1.0.0" + ], + [ + "··is-number@1.0.0%2Bbuild.1 dep-16-2", + "peer 1.0.0 ··use-sync-external-store@10.0.0" + ], + [ + "··is-number@1.0.0%2Bbuild.1 dep-16-3", + "peerOptional ^1.0.0 || ^2 ·acme·@a§b@10.0.0" + ], + [ + "··ms@1.0.0%2Bbuild.1 dep-17-0", + "optional 1.0.0 ··ms@1.0.0%2Bbuild.1" + ], + [ + "··ms@1.0.0%2Bbuild.1 dep-17-1", + "peer ^1.0.0 || ^2 ··react@1.0.0%2Bbuild.1" + ], + [ + "··ms@1.0.0%2Bbuild.1 dep-17-2", + "peerOptional 1.0.0 ··zz@1.0.0" + ], + [ + "··ms@1.0.0%2Bbuild.1 dep-17-3", + "prod ^1.0.0 || ^2 ·acme·@scope§bar@1.0.0%2Bbuild.1" + ], + [ + "··use-sync-external-store@1.0.0-rc.1 dep-18-3", + "dev ^1.0.0 || ^2 ·acme·a.b@1.0.0%2Bbuild.1" + ], + [ + "··use-sync-external-store@1.0.0-rc.1 dep-18-0", + "peer 1.0.0 ··react-dom@1.0.0-rc.1" + ], + [ + "··use-sync-external-store@1.0.0-rc.1 dep-18-1", + "peerOptional ^1.0.0 || ^2 ··use-sync-external-store@2.1.3" + ], + [ + "··use-sync-external-store@1.0.0-rc.1 dep-18-2", + "prod 1.0.0 ·acme·@A§b@2.1.3" + ], + [ + "·acme·@isaacs§string-locale-compare@2.1.3 dep-19-2", + "dev 1.0.0 ·acme·@sindresorhus§is@1.0.0%2Bbuild.1" + ], + [ + "·acme·@isaacs§string-locale-compare@2.1.3 dep-19-3", + "optional ^1.0.0 || ^2 MISSING" + ], + [ + "·acme·@isaacs§string-locale-compare@2.1.3 dep-19-0", + "peerOptional 1.0.0 ··react@10.0.0" + ], + [ + "·acme·@isaacs§string-locale-compare@2.1.3 dep-19-1", + "prod ^1.0.0 || ^2 ··zz@1.0.0-rc.1" + ], + [ + "·acme·a1@1.0.0-rc.1 dep-20-1", + "dev ^1.0.0 || ^2 ·acme·@isaacs§string-locale-compare@2.1.3" + ], + [ + "·acme·a1@1.0.0-rc.1 dep-20-2", + "optional 1.0.0 MISSING" + ], + [ + "·acme·a1@1.0.0-rc.1 dep-20-3", + "peer ^1.0.0 || ^2 ·acme·ms@2.1.3" + ], + [ + "·acme·a1@1.0.0-rc.1 dep-20-0", + "prod 1.0.0 ··z@1.0.0" + ], + [ + "·http%3A§§127.0.0.1%3A4873§·@A§b@1.0.0 dep-21-0", + "dev 1.0.0 ··zz@1.0.0%2Bbuild.1" + ], + [ + "·http%3A§§127.0.0.1%3A4873§·@A§b@1.0.0 dep-21-1", + "optional ^1.0.0 || ^2 MISSING" + ], + [ + "·http%3A§§127.0.0.1%3A4873§·@A§b@1.0.0 dep-21-2", + "peer 1.0.0 ·acme·ab@10.0.0" + ], + [ + "·http%3A§§127.0.0.1%3A4873§·@A§b@1.0.0 dep-21-3", + "peerOptional ^1.0.0 || ^2 ·acme·zz@10.0.0" + ], + [ + "·http%3A§§127.0.0.1%3A4873§·A@1.0.0%2Bbuild.1 dep-22-0", + "optional 1.0.0 MISSING" + ], + [ + "·http%3A§§127.0.0.1%3A4873§·A@1.0.0%2Bbuild.1 dep-22-1", + "peer ^1.0.0 || ^2 ·acme·a@1.0.0%2Bbuild.1" + ], + [ + "·http%3A§§127.0.0.1%3A4873§·A@1.0.0%2Bbuild.1 dep-22-2", + "peerOptional 1.0.0 ·acme·react-dom@1.0.0-rc.1" + ], + [ + "·http%3A§§127.0.0.1%3A4873§·A@1.0.0%2Bbuild.1 dep-22-3", + "prod ^1.0.0 || ^2 ·http%3A§§127.0.0.1%3A4873§·@scope_x§bar@1.0.0-rc.1" + ], + [ + "·http%3A§§127.0.0.1%3A4873§·use-sync-external-store@1.0.0%2Bbuild.1 dep-23-3", + "dev ^1.0.0 || ^2 ·http%3A§§127.0.0.1%3A4873§·a__b@10.0.0" + ], + [ + "·http%3A§§127.0.0.1%3A4873§·use-sync-external-store@1.0.0%2Bbuild.1 dep-23-0", + "peer 1.0.0 ·acme·a__b@2.1.3" + ], + [ + "·http%3A§§127.0.0.1%3A4873§·use-sync-external-store@1.0.0%2Bbuild.1 dep-23-1", + "peerOptional ^1.0.0 || ^2 ·acme·aB@2.1.3" + ], + [ + "·http%3A§§127.0.0.1%3A4873§·use-sync-external-store@1.0.0%2Bbuild.1 dep-23-2", + "prod 1.0.0 ·http%3A§§127.0.0.1%3A4873§·@A§b@1.0.0" + ], + [ + "·npm·@jsr§std__semver@1.0.0%2Bbuild.1·%E1%B9%97%3A3 dep-24-2", + "dev 1.0.0 ·http%3A§§127.0.0.1%3A4873§·@scope§bar-baz@1.0.0%2Bbuild.1" + ], + [ + "·npm·@jsr§std__semver@1.0.0%2Bbuild.1·%E1%B9%97%3A3 dep-24-3", + "optional ^1.0.0 || ^2 ·http%3A§§127.0.0.1%3A4873§·a1@1.0.0%2Bbuild.1" + ], + [ + "·npm·@jsr§std__semver@1.0.0%2Bbuild.1·%E1%B9%97%3A3 dep-24-0", + "peerOptional 1.0.0 ·acme·a~b@1.0.0" + ], + [ + "·npm·@jsr§std__semver@1.0.0%2Bbuild.1·%E1%B9%97%3A3 dep-24-1", + "prod ^1.0.0 || ^2 ·acme·react@1.0.0" + ], + [ + "·npm·@sindresorhus§is@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms dep-25-1", + "dev ^1.0.0 || ^2 ·http%3A§§127.0.0.1%3A4873§·@a§b@1.0.0-rc.1" + ], + [ + "·npm·@sindresorhus§is@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms dep-25-2", + "optional 1.0.0 ·http%3A§§127.0.0.1%3A4873§·a_b@1.0.0-rc.1" + ], + [ + "·npm·@sindresorhus§is@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms dep-25-3", + "peer ^1.0.0 || ^2 ·http%3A§§127.0.0.1%3A4873§·left-pad@1.0.0" + ], + [ + "·npm·@sindresorhus§is@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms dep-25-0", + "prod 1.0.0 ·acme·is-number@1.0.0%2Bbuild.1" + ], + [ + "·npm·a-b@1.0.0%2Bbuild.1·%E1%B9%97%3A3 dep-26-0", + "dev 1.0.0 ·acme·react@10.0.0" + ], + [ + "·npm·a-b@1.0.0%2Bbuild.1·%E1%B9%97%3A3 dep-26-1", + "optional ^1.0.0 || ^2 ·http%3A§§127.0.0.1%3A4873§·@scope§bar@2.1.3" + ], + [ + "·npm·a-b@1.0.0%2Bbuild.1·%E1%B9%97%3A3 dep-26-2", + "peer 1.0.0 ·http%3A§§127.0.0.1%3A4873§·aB@1.0.0" + ], + [ + "·npm·a-b@1.0.0%2Bbuild.1·%E1%B9%97%3A3 dep-26-3", + "peerOptional ^1.0.0 || ^2 ·http%3A§§127.0.0.1%3A4873§·use-sync-external-store@1.0.0%2Bbuild.1" + ], + [ + "·npm·a~b@1.0.0%2Bbuild.1·%E1%B9%97%3A3 dep-27-0", + "optional 1.0.0 ·http%3A§§127.0.0.1%3A4873§·@A§b@10.0.0" + ], + [ + "·npm·a~b@1.0.0%2Bbuild.1·%E1%B9%97%3A3 dep-27-1", + "peer ^1.0.0 || ^2 ·http%3A§§127.0.0.1%3A4873§·a-b@1.0.0%2Bbuild.1" + ], + [ + "·npm·a~b@1.0.0%2Bbuild.1·%E1%B9%97%3A3 dep-27-2", + "peerOptional 1.0.0 ·http%3A§§127.0.0.1%3A4873§·left-pad@10.0.0" + ], + [ + "·npm·a~b@1.0.0%2Bbuild.1·%E1%B9%97%3A3 dep-27-3", + "prod ^1.0.0 || ^2 ·npm·@A§b@1.0.0%2Bbuild.1·%E1%B9%97%3A3" + ], + [ + "·npm·aB@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms dep-28-3", + "dev ^1.0.0 || ^2 ·npm·@isaacs§string-locale-compare@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "·npm·aB@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms dep-28-0", + "peer 1.0.0 ·http%3A§§127.0.0.1%3A4873§·@sindresorhus§is@2.1.3" + ], + [ + "·npm·aB@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms dep-28-1", + "peerOptional ^1.0.0 || ^2 ·http%3A§§127.0.0.1%3A4873§·ab@1.0.0-rc.1" + ], + [ + "·npm·aB@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms dep-28-2", + "prod 1.0.0 ·http%3A§§127.0.0.1%3A4873§·z@1.0.0%2Bbuild.1" + ], + [ + "·npm·MS@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms dep-29-2", + "dev 1.0.0 ·npm·@a§b@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "·npm·MS@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms dep-29-3", + "optional ^1.0.0 || ^2 ·npm·@scope_x§bar@1.0.0%2Bbuild.1·%E1%B9%97%3A3" + ], + [ + "·npm·MS@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms dep-29-0", + "peerOptional 1.0.0 ·http%3A§§127.0.0.1%3A4873§·a.b@2.1.3" + ], + [ + "·npm·MS@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms dep-29-1", + "prod ^1.0.0 || ^2 ·http%3A§§127.0.0.1%3A4873§·ms@1.0.0" + ], + [ + "·npm·use-sync-external-store@1.0.0%2Bbuild.1·%E1%B9%97%3A3 dep-30-1", + "dev ^1.0.0 || ^2 ·http%3A§§127.0.0.1%3A4873§·zz@1.0.0-rc.1" + ], + [ + "·npm·use-sync-external-store@1.0.0%2Bbuild.1·%E1%B9%97%3A3 dep-30-2", + "optional 1.0.0 ·npm·@jsr§std__semver@1.0.0" + ], + [ + "·npm·use-sync-external-store@1.0.0%2Bbuild.1·%E1%B9%97%3A3 dep-30-3", + "peer ^1.0.0 || ^2 MISSING" + ], + [ + "·npm·use-sync-external-store@1.0.0%2Bbuild.1·%E1%B9%97%3A3 dep-30-0", + "prod 1.0.0 ·http%3A§§127.0.0.1%3A4873§·aB@10.0.0" + ], + [ + "~acme~@a+b@1.0.0~peer.dbd5ca8b03a66489 dep-31-0", + "dev 1.0.0 ·http%3A§§127.0.0.1%3A4873§·ms@10.0.0" + ], + [ + "~acme~@a+b@1.0.0~peer.dbd5ca8b03a66489 dep-31-1", + "optional ^1.0.0 || ^2 ·npm·@A§b@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~acme~@a+b@1.0.0~peer.dbd5ca8b03a66489 dep-31-2", + "peer 1.0.0 MISSING" + ], + [ + "~acme~@a+b@1.0.0~peer.dbd5ca8b03a66489 dep-31-3", + "peerOptional ^1.0.0 || ^2 ·npm·@sindresorhus§is@1.0.0%2Bbuild.1·%E1%B9%97%3A3" + ], + [ + "~acme~@scope__x+bar@1.0.0~peer.dbd5ca8b03a66489 dep-32-0", + "optional 1.0.0 ·npm·@a§b@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~acme~@scope__x+bar@1.0.0~peer.dbd5ca8b03a66489 dep-32-1", + "peer ^1.0.0 || ^2 MISSING" + ], + [ + "~acme~@scope__x+bar@1.0.0~peer.dbd5ca8b03a66489 dep-32-2", + "peerOptional 1.0.0 ·npm·@scope§bar@1.0.0-rc.1" + ], + [ + "~acme~@scope__x+bar@1.0.0~peer.dbd5ca8b03a66489 dep-32-3", + "prod ^1.0.0 || ^2 ·npm·a__b@1.0.0%2Bbuild.1" + ], + [ + "~acme~@sindresorhus+is@2.1.3~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-33-3", + "dev ^1.0.0 || ^2 ·npm·a-b@1.0.0" + ], + [ + "~acme~@sindresorhus+is@2.1.3~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-33-0", + "peer 1.0.0 MISSING" + ], + [ + "~acme~@sindresorhus+is@2.1.3~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-33-1", + "peerOptional ^1.0.0 || ^2 ·npm·@scope_x§bar@2.1.3" + ], + [ + "~acme~@sindresorhus+is@2.1.3~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-33-2", + "prod 1.0.0 ·npm·@sindresorhus§is@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~acme~a_tb@1.0.0~peer.dbd5ca8b03a66489 dep-34-2", + "dev 1.0.0 ·npm·a__b@1.0.0%2Bbuild.1·%E1%B9%97%3A3" + ], + [ + "~acme~a_tb@1.0.0~peer.dbd5ca8b03a66489 dep-34-3", + "optional ^1.0.0 || ^2 ·npm·a.b@1.0.0%2Bbuild.1·%E1%B9%97%3A3" + ], + [ + "~acme~a_tb@1.0.0~peer.dbd5ca8b03a66489 dep-34-0", + "peerOptional 1.0.0 ·npm·@jsr§std__semver@1.0.0%2Bbuild.1·%E1%B9%97%3A3" + ], + [ + "~acme~a_tb@1.0.0~peer.dbd5ca8b03a66489 dep-34-1", + "prod ^1.0.0 || ^2 ·npm·@scope§bar@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~acme~a@1.0.0-rc.1 dep-35-1", + "dev ^1.0.0 || ^2 ·npm·0x@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~acme~a@1.0.0-rc.1 dep-35-2", + "optional 1.0.0 ·npm·a-b@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~acme~a@1.0.0-rc.1 dep-35-3", + "peer ^1.0.0 || ^2 ·npm·A@1.0.0%2Bbuild.1·%E1%B9%97%3A3" + ], + [ + "~acme~a@1.0.0-rc.1 dep-35-0", + "prod 1.0.0 ·npm·@scope§bar-baz@1.0.0" + ], + [ + "~acme~ab@1.0.0-rc.1~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-36-0", + "dev 1.0.0 ·npm·@scope§bar@1.0.0%2Bbuild.1·%E1%B9%97%3A3" + ], + [ + "~acme~ab@1.0.0-rc.1~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-36-1", + "optional ^1.0.0 || ^2 ·npm·a__b@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~acme~ab@1.0.0-rc.1~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-36-2", + "peer 1.0.0 ·npm·a.b@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~acme~ab@1.0.0-rc.1~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-36-3", + "peerOptional ^1.0.0 || ^2 ·npm·a~b@2.1.3" + ], + [ + "~acme~JSONStream@2.1.3~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-37-0", + "optional 1.0.0 ·npm·0x@1.0.0%2Bbuild.1" + ], + [ + "~acme~JSONStream@2.1.3~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-37-1", + "peer ^1.0.0 || ^2 ·npm·a-b@1.0.0%2Bbuild.1·%E1%B9%97%3A3" + ], + [ + "~acme~JSONStream@2.1.3~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-37-2", + "peerOptional 1.0.0 ·npm·A@10.0.0" + ], + [ + "~acme~JSONStream@2.1.3~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-37-3", + "prod ^1.0.0 || ^2 ·npm·ab@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~acme~react-dom@1.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-38-3", + "dev ^1.0.0 || ^2 ·npm·ab@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~acme~react-dom@1.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-38-0", + "peer 1.0.0 ·npm·a_b@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~acme~react-dom@1.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-38-1", + "peerOptional ^1.0.0 || ^2 ·npm·A@1.0.0" + ], + [ + "~acme~react-dom@1.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-38-2", + "prod 1.0.0 ·npm·a1@1.0.0" + ], + [ + "~acme~z@1.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-39-2", + "dev 1.0.0 ·npm·aB@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~acme~z@1.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-39-3", + "optional ^1.0.0 || ^2 ·npm·is-number@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~acme~z@1.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-39-0", + "peerOptional 1.0.0 ·npm·a-b@10.0.0" + ], + [ + "~acme~z@1.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number dep-39-1", + "prod ^1.0.0 || ^2 ·npm·a@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~https_c++registry.example.com+npm+~@scope+bar@2.1.3 dep-40-1", + "dev ^1.0.0 || ^2 ·npm·a1@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~https_c++registry.example.com+npm+~@scope+bar@2.1.3 dep-40-2", + "optional 1.0.0 ·npm·aB@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~https_c++registry.example.com+npm+~@scope+bar@2.1.3 dep-40-3", + "peer ^1.0.0 || ^2 ·npm·left-pad@1.0.0%2Bbuild.1·%E1%B9%97%3A3" + ], + [ + "~https_c++registry.example.com+npm+~@scope+bar@2.1.3 dep-40-0", + "prod 1.0.0 ·npm·a@1.0.0-rc.1" + ], + [ + "~https_c++registry.example.com+npm+~is-number@2.1.3 dep-41-0", + "dev 1.0.0 ·npm·A@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~https_c++registry.example.com+npm+~is-number@2.1.3 dep-41-1", + "optional ^1.0.0 || ^2 ·npm·Ab@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~https_c++registry.example.com+npm+~is-number@2.1.3 dep-41-2", + "peer 1.0.0 ·npm·JSONStream@1.0.0-rc.1" + ], + [ + "~https_c++registry.example.com+npm+~is-number@2.1.3 dep-41-3", + "peerOptional ^1.0.0 || ^2 MISSING" + ], + [ + "~jsr~@scope__x+bar@1.0.0 dep-42-0", + "optional 1.0.0 ·npm·a1@1.0.0%2Bbuild.1·%E1%B9%97%3A3" + ], + [ + "~jsr~@scope__x+bar@1.0.0 dep-42-1", + "peer ^1.0.0 || ^2 ·npm·Ab@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~jsr~@scope__x+bar@1.0.0 dep-42-2", + "peerOptional 1.0.0 MISSING" + ], + [ + "~jsr~@scope__x+bar@1.0.0 dep-42-3", + "prod ^1.0.0 || ^2 ·npm·react-dom@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~jsr~Ab@1.0.0_pbuild.1 dep-43-3", + "dev ^1.0.0 || ^2 ·npm·use-sync-external-store@1.0.0%2Bbuild.1·%E1%B9%97%3A3" + ], + [ + "~jsr~Ab@1.0.0_pbuild.1 dep-43-0", + "peer 1.0.0 ·npm·aB@1.0.0%2Bbuild.1" + ], + [ + "~jsr~Ab@1.0.0_pbuild.1 dep-43-1", + "peerOptional ^1.0.0 || ^2 MISSING" + ], + [ + "~jsr~Ab@1.0.0_pbuild.1 dep-43-2", + "prod 1.0.0 ·npm·ms@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~npm~@a+b@1.0.0_pbuild.1 dep-44-2", + "dev 1.0.0 ·npm·react@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~npm~@a+b@1.0.0_pbuild.1 dep-44-3", + "optional ^1.0.0 || ^2 ·npm·z@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~npm~@a+b@1.0.0_pbuild.1 dep-44-0", + "peerOptional 1.0.0 MISSING" + ], + [ + "~npm~@a+b@1.0.0_pbuild.1 dep-44-1", + "prod ^1.0.0 || ^2 ·npm·MS@1.0.0-rc.1" + ], + [ + "~npm~@jsr+std____semver@2.1.3 dep-45-1", + "dev ^1.0.0 || ^2 ·npm·MS@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~npm~@jsr+std____semver@2.1.3 dep-45-2", + "optional 1.0.0 ·npm·use-sync-external-store@10.0.0" + ], + [ + "~npm~@jsr+std____semver@2.1.3 dep-45-3", + "peer ^1.0.0 || ^2 ~acme~@a+b@1.0.0-rc.1~_croot_s_g_s#to-regex-range_s_g_s#is-number" + ], + [ + "~npm~@jsr+std____semver@2.1.3 dep-45-0", + "prod 1.0.0 ·npm·JSONStream@1.0.0%2Bbuild.1·%E1%B9%97%3A3" + ], + [ + "~npm~@sindresorhus+is@1.0.0_pbuild.1 dep-46-0", + "dev 1.0.0 ·npm·ms@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~npm~@sindresorhus+is@1.0.0_pbuild.1 dep-46-1", + "optional ^1.0.0 || ^2 ·npm·react@1.0.0%2Bbuild.1·%E1%B9%97%3A3" + ], + [ + "~npm~@sindresorhus+is@1.0.0_pbuild.1 dep-46-2", + "peer 1.0.0 ·npm·zz@1.0.0-rc.1·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~npm~@sindresorhus+is@1.0.0_pbuild.1 dep-46-3", + "peerOptional ^1.0.0 || ^2 ~acme~@a+b@2.1.3~peer.2" + ], + [ + "~npm~a__b@10.0.0 dep-47-0", + "optional 1.0.0 ·npm·react-dom@1.0.0" + ], + [ + "~npm~a__b@10.0.0 dep-47-1", + "peer ^1.0.0 || ^2 ·npm·use-sync-external-store@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~npm~a__b@10.0.0 dep-47-2", + "peerOptional 1.0.0 ~acme~@A+b@1.0.0-rc.1~peer.dbd5ca8b03a66489" + ], + [ + "~npm~a__b@10.0.0 dep-47-3", + "prod ^1.0.0 || ^2 ~acme~@jsr+std____semver@1.0.0_pbuild.1~peer.2" + ], + [ + "~npm~a@1.0.0 dep-48-3", + "dev ^1.0.0 || ^2 ~acme~@scope__x+bar@10.0.0~peer.dbd5ca8b03a66489" + ], + [ + "~npm~a@1.0.0 dep-48-0", + "peer 1.0.0 ·npm·react@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~npm~a@1.0.0 dep-48-1", + "peerOptional ^1.0.0 || ^2 ·npm·zz@1.0.0%2Bbuild.1·%E1%B9%97%3A3" + ], + [ + "~npm~a@1.0.0 dep-48-2", + "prod 1.0.0 ~acme~@isaacs+string-locale-compare@1.0.0_pbuild.1" + ], + [ + "~npm~Ab@1.0.0 dep-49-2", + "dev 1.0.0 ~acme~@jsr+std____semver@1.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number" + ], + [ + "~npm~Ab@1.0.0 dep-49-3", + "optional ^1.0.0 || ^2 ~acme~@scope+bar-baz@10.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number" + ], + [ + "~npm~Ab@1.0.0 dep-49-0", + "peerOptional 1.0.0 ·npm·z@1.0.0" + ], + [ + "~npm~Ab@1.0.0 dep-49-1", + "prod ^1.0.0 || ^2 ~acme~@A+b@1.0.0~peer.2" + ], + [ + "~npm~is-number@2.1.3 dep-50-1", + "dev ^1.0.0 || ^2 ~acme~@isaacs+string-locale-compare@1.0.0_pbuild.1~_croot_s_g_s#to-regex-range_s_g_s#is-number" + ], + [ + "~npm~is-number@2.1.3 dep-50-2", + "optional 1.0.0 ~acme~@scope__x+bar@2.1.3" + ], + [ + "~npm~is-number@2.1.3 dep-50-3", + "peer ^1.0.0 || ^2 ~acme~@sindresorhus+is@1.0.0-rc.1" + ], + [ + "~npm~is-number@2.1.3 dep-50-0", + "prod 1.0.0 ·npm·zz@10.0.0·%3Aroot%20%3E%20%23debug%20%3E%20%23ms" + ], + [ + "~npm~ms@10.0.0 dep-51-0", + "dev 1.0.0 ~acme~@a+b@1.0.0~peer.dbd5ca8b03a66489" + ], + [ + "~npm~ms@10.0.0 dep-51-1", + "optional ^1.0.0 || ^2 ~acme~@jsr+std____semver@10.0.0" + ], + [ + "~npm~ms@10.0.0 dep-51-2", + "peer 1.0.0 ~acme~@scope+bar-baz@2.1.3~peer.dbd5ca8b03a66489" + ], + [ + "~npm~ms@10.0.0 dep-51-3", + "peerOptional ^1.0.0 || ^2 ~acme~0x@10.0.0~peer.2" + ], + [ + "~npm~use-sync-external-store@10.0.0 dep-52-0", + "optional 1.0.0 ~acme~@isaacs+string-locale-compare@1.0.0-rc.1~peer.dbd5ca8b03a66489" + ], + [ + "~npm~use-sync-external-store@10.0.0 dep-52-1", + "peer ^1.0.0 || ^2 ~acme~@scope__x+bar@2.1.3~peer.2" + ], + [ + "~npm~use-sync-external-store@10.0.0 dep-52-2", + "peerOptional 1.0.0 ~acme~@sindresorhus+is@1.0.0-rc.1~peer.2" + ], + [ + "~npm~use-sync-external-store@10.0.0 dep-52-3", + "prod ^1.0.0 || ^2 MISSING" + ] + ] +} diff --git a/scripts/gen-vlt-collation-golden.mjs b/scripts/gen-vlt-collation-golden.mjs new file mode 100644 index 00000000..948bf83d --- /dev/null +++ b/scripts/gen-vlt-collation-golden.mjs @@ -0,0 +1,244 @@ +#!/usr/bin/env node +// Prints crates/socket-patch-core/tests/fixtures/vlt/collation-golden.json: +// vlt's lockfile orders (graph/src/lockfile/save.ts formatNodes/formatEdges) +// over DepIDs of every era, computed by Node's ICU. Regenerate with Node +// 24.21 under LANG=C LC_ALL=C: +// +// node scripts/gen-vlt-collation-golden.mjs \ +// > crates/socket-patch-core/tests/fixtures/vlt/collation-golden.json + +const EXPECTED_NODE = '24.21.0' +if (process.versions.node !== EXPECTED_NODE) { + process.stderr.write( + `warning: generated with Node ${process.versions.node}; the golden is pinned to ${EXPECTED_NODE}\n`, + ) +} + +const collate = (a, b) => a.localeCompare(b, 'en') + +const alphabet = [] +for (let c = 0x20; c < 0x7f; c++) { + alphabet.push(String.fromCharCode(c)) +} +alphabet.push('·', '§') +alphabet.sort(collate) +const inTable = new Set(alphabet) + +const TILDE_ESCAPE = { + _: '__', + '+': '_p', + '\\': '_b', + ':': '_c', + '~': '_t', + '<': '_l', + '>': '_g', + '"': '_q', + '|': '_i', + '?': '_m', + '*': '_a', + ' ': '_s', +} + +const encodeTilde = s => { + let out = '' + for (const ch of s) { + if (ch === '/') { + out += '+' + } else if (TILDE_ESCAPE[ch]) { + out += TILDE_ESCAPE[ch] + } else if (ch.charCodeAt(0) <= 0x1f) { + out += '_' + ch.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0') + } else { + out += ch + } + } + return out.endsWith('.') ? `${out.slice(0, -1)}_d` : out +} + +const encodeLegacy = s => + encodeURIComponent(s).replaceAll('%40', '@').replaceAll('%2F', '§') + +const ERAS = { + legacy: { d: '·', enc: encodeLegacy, root: 'file·.' }, + tilde: { d: '~', enc: encodeTilde, root: 'file~_d' }, +} + +const registryId = (era, segment, nameVersion, extra) => { + const { d, enc } = ERAS[era] + const tail = extra === undefined ? '' : `${d}${enc(extra)}` + return `${d}${enc(segment)}${d}${enc(nameVersion)}${tail}` +} + +const typedId = (era, type, first, second) => { + const { d, enc } = ERAS[era] + const tail = second === undefined ? '' : `${d}${enc(second)}` + return `${type}${d}${enc(first)}${tail}` +} + +const NAMES = [ + 'a', + 'A', + 'ab', + 'aB', + 'Ab', + 'a-b', + 'a_b', + 'a__b', + 'a.b', + 'a1', + 'a~b', + 'ms', + 'MS', + 'z', + 'zz', + '0x', + 'is-number', + 'react-dom', + 'react', + 'left-pad', + 'JSONStream', + 'use-sync-external-store', + '@a/b', + '@A/b', + '@scope/bar', + '@scope/bar-baz', + '@scope_x/bar', + '@isaacs/string-locale-compare', + '@sindresorhus/is', + '@jsr/std__semver', +] +const VERSIONS = ['1.0.0', '1.0.0-rc.1', '1.0.0+build.1', '2.1.3', '10.0.0'] +const SEGMENTS = { + legacy: ['', 'npm', 'acme', 'http://127.0.0.1:4873/'], + tilde: ['npm', 'acme', 'jsr', 'https://registry.example.com/npm/'], +} +const EXTRAS = { + legacy: [undefined, ':root > #debug > #ms', 'ṗ:3'], + tilde: [ + undefined, + 'peer.2', + 'peer.dbd5ca8b03a66489', + ':root > #to-regex-range > #is-number', + ], +} +const UUIDS = [ + '0b1f6e2a-3c4d-4e5f-8a9b-0c1d2e3f4a5b', + '80630680-4da6-45f9-bba8-b888e0ffd58c', + 'ffffffff-2222-4333-8444-555555555555', +] + +const vendoredDir = (uuid, name, version) => { + const slash = name.indexOf('/') + const leaf = + slash < 0 ? `${name}-${version}` : `${name.slice(0, slash)}/${name.slice(slash + 1)}-${version}` + return `.socket/vendor/npm/${uuid}/${leaf}/node_modules/${name}` +} + +const ids = new Set() +for (const era of Object.keys(ERAS)) { + NAMES.forEach((name, n) => { + VERSIONS.forEach((version, v) => { + const segment = SEGMENTS[era][(n + v) % SEGMENTS[era].length] + ids.add(registryId(era, segment, `${name}@${version}`)) + ids.add(registryId(era, SEGMENTS[era][0], `${name}@${version}`)) + const extra = EXTRAS[era][(n * 3 + v) % EXTRAS[era].length] + if (extra !== undefined) { + ids.add(registryId(era, SEGMENTS[era][1], `${name}@${version}`, extra)) + } + }) + const uuid = UUIDS[n % UUIDS.length] + ids.add(typedId(era, 'file', vendoredDir(uuid, name, VERSIONS[n % VERSIONS.length]))) + }) + const { root } = ERAS[era] + ids.add(root) + for (const path of ['packages/a', 'packages/b', 'packages/my_lib', 'apps/web-1']) { + ids.add(typedId(era, 'workspace', path)) + } + for (const path of ['..', '../x', 'vendor/x', 'vendor/x.tgz', './packages/a', 'a b/c']) { + ids.add(typedId(era, 'file', path)) + } + for (const url of ['https://e.com/r-1.0.0.tgz', 'https://e.com/R-1.0.0.tgz', 'http://h/x.tgz']) { + ids.add(typedId(era, 'remote', url)) + } + for (const [remote, selector] of [ + ['github:user/proj', 'v1.0.0'], + ['github:user/proj', 'semver:^1'], + ['git+ssh://git@host/x.git', 'main'], + ]) { + ids.add(typedId(era, 'git', remote, selector)) + } +} + +const nodes = [...ids] +for (const id of nodes) { + for (const ch of id) { + if (!inTable.has(ch)) { + throw new Error(`${JSON.stringify(id)} holds ${JSON.stringify(ch)}, outside the table`) + } + } +} +if (nodes.length < 400) { + throw new Error(`only ${nodes.length} DepIDs`) +} +nodes.sort(collate) +for (let i = 1; i < nodes.length; i++) { + if (collate(nodes[i - 1], nodes[i]) >= 0) { + throw new Error(`tie between ${nodes[i - 1]} and ${nodes[i]}`) + } +} + +const isImporter = id => + id === 'file~_d' || + id === 'file·.' || + /^workspace[~·]./.test(id) + +const TYPES = ['prod', 'dev', 'optional', 'peer', 'peerOptional'] +const registryNodes = nodes.filter(id => id.startsWith('~') || id.startsWith('·')) +const importers = nodes.filter(isImporter) +const sources = [...importers, ...registryNodes.filter((_, i) => i % 17 === 0)] +const edges = [] +const seen = new Set() +sources.forEach((from, f) => { + for (let k = 0; k < 4; k++) { + const type = TYPES[(f + k) % TYPES.length] + const to = + (f + k) % 11 === 0 ? 'MISSING' : registryNodes[(f * 7 + k * 13) % registryNodes.length] + const tieKey = `${from} ${type} ${to}` + if (seen.has(tieKey)) { + continue + } + seen.add(tieKey) + edges.push({ from, type, to, name: `dep-${f}-${k}`, spec: k % 2 ? '^1.0.0 || ^2' : '1.0.0' }) + } +}) +const toId = e => (e.to === 'MISSING' ? '' : e.to) +edges.sort( + (a, b) => + Number(isImporter(b.from)) - Number(isImporter(a.from)) || + collate(a.from, b.from) || + collate(a.type, b.type) || + collate(toId(a), toId(b)), +) +for (let i = 1; i < edges.length; i++) { + const [a, b] = [edges[i - 1], edges[i]] + if ( + isImporter(a.from) === isImporter(b.from) && + collate(a.from, b.from) === 0 && + collate(a.type, b.type) === 0 && + collate(toId(a), toId(b)) === 0 + ) { + throw new Error(`edge tie at ${i}`) + } +} + +process.stdout.write( + `${JSON.stringify( + { + alphabet: alphabet.join(''), + nodes, + edges: edges.map(e => [`${e.from} ${e.name}`, `${e.type} ${e.spec} ${e.to}`]), + }, + null, + 2, + )}\n`, +) From 0575434d7cb397c5e605034ccfa56821be684646 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Fri, 25 Sep 2026 20:36:56 -0400 Subject: [PATCH 08/46] Match depscan's npm name rule in vlt ids vlt lock ids now give a package identity to every name npm still accepts for existing packages, the same rule depscan uses. Scoped names whose scope or name starts with "." or "_" (such as the published @_koii/web3.js) and names over 214 characters were refused before, so hosted redirects, VEX and vendored-copy checks would have missed them while depscan patched them. Names npm blocks (node_modules, favicon.ico) and versions too large for npm to publish no longer get an identity. A refused lockfileVersion is now reported exactly as it is written in the lockfile (1e0 rather than 1.0), and tests now pin the default-registry scheme check and the fixture-corpus skip of the vlt tables. Assisted-by: Claude Code:claude-opus-5-5 --- Cargo.toml | 2 +- .../src/vendor/vlt_lock_text.rs | 209 ++++++++++++++---- .../src/vex/discover/testing/golden.rs | 7 +- 3 files changed, 170 insertions(+), 48 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c1e95483..1a2b392f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ repository = "https://github.com/SocketDev/socket-patch" socket-patch-core = { path = "crates/socket-patch-core", version = "=4.0.0" } clap = { version = "=4.5.60", features = ["derive", "env"] } serde = { version = "=1.0.228", features = ["derive"] } -serde_json = { version = "=1.0.149", features = ["preserve_order"] } +serde_json = { version = "=1.0.149", features = ["preserve_order", "raw_value"] } sha2 = "=0.10.9" sha1 = "=0.10.6" hex = "=0.4.3" diff --git a/crates/socket-patch-core/src/vendor/vlt_lock_text.rs b/crates/socket-patch-core/src/vendor/vlt_lock_text.rs index fc929a55..187c66f1 100644 --- a/crates/socket-patch-core/src/vendor/vlt_lock_text.rs +++ b/crates/socket-patch-core/src/vendor/vlt_lock_text.rs @@ -9,9 +9,11 @@ //! in the tests are shared verbatim with depscan's `vlt-dep-id.test.ts`. use std::cmp::Ordering; +use std::collections::HashMap; use std::sync::LazyLock; use regex::Regex; +use serde_json::value::RawValue; use serde_json::{Map, Value}; use crate::patch::path_safety::is_canonical_uuid; @@ -282,41 +284,51 @@ static SEMVER_RE: LazyLock = LazyLock::new(|| { .expect("semver regex") }); -const NPM_NAME_MAX_LENGTH: usize = 214; +const NPM_SAFE_INTEGER_MAX: u64 = 9_007_199_254_740_991; + +const NPM_BLOCKED_NAMES: [&str; 2] = ["node_modules", "favicon.ico"]; /// The semver.org 2.0.0 grammar exactly (build metadata allowed; no `v`, -/// `=`, ranges or leading zeros), the same regex as the TS twin. -pub(crate) fn is_strict_semver(version: &str) -> bool { - SEMVER_RE.is_match(version) +/// `=`, ranges or leading zeros) with major, minor and patch at most +/// node-semver's `Number.MAX_SAFE_INTEGER`; the TS twin's `isNpmSemver`. +/// A numeric prerelease identifier stays unbounded. +pub(crate) fn is_npm_semver(version: &str) -> bool { + SEMVER_RE.captures(version).is_some_and(|caps| { + (1..=3).all(|i| { + caps[i] + .parse::() + .is_ok_and(|n| n <= NPM_SAFE_INTEGER_MAX) + }) + }) } -fn is_npm_name_part(part: &str) -> bool { - let unreserved = |c: char| c.is_ascii_alphanumeric() || "-~!*'()".contains(c); - let mut chars = part.chars(); - chars.next().is_some_and(unreserved) && chars.all(|c| unreserved(c) || c == '.' || c == '_') +fn is_npm_url_safe(part: &str) -> bool { + !part.is_empty() + && part + .chars() + .all(|c| c.is_ascii_alphanumeric() || "-_.!~*'()".contains(c)) } -/// npm's name rule for existing packages: an optional `@scope/` plus a name, -/// URL-safe characters only, no leading `.` or `_`, at most 214 chars. +/// validate-npm-package-name's `validForOldPackages`, the TS twin's +/// `isNpmPackageName`: `@/` with both parts URL-safe, or a +/// URL-safe name without a leading `.` or `_` that is not a blocked name. +/// No length cap, since published packages exceed 214 chars. pub(crate) fn is_registry_package_name(name: &str) -> bool { - if name.len() > NPM_NAME_MAX_LENGTH { - return false; - } - match name.strip_prefix('@') { - None => is_npm_name_part(name), - Some(scoped) => scoped - .split_once('/') - .is_some_and(|(scope, bare)| is_npm_name_part(scope) && is_npm_name_part(bare)), + if let Some((scope, bare)) = name.strip_prefix('@').and_then(|s| s.split_once('/')) { + return is_npm_url_safe(scope) && is_npm_url_safe(bare); } + is_npm_url_safe(name) + && !name.starts_with(['.', '_']) + && !NPM_BLOCKED_NAMES.contains(&name.to_ascii_lowercase().as_str()) } /// `(name, version)` from a registry id's decoded `name@version`, split at /// the last `@` past index 0. The DepID version is authoritative for -/// identity, so a non-semver version gives `None`. +/// identity, so a version npm could not have published gives `None`. pub(crate) fn registry_name_version(second: &str) -> Option<(&str, &str)> { let at = second.rfind('@').filter(|&i| i > 0)?; let (name, version) = (&second[..at], &second[at + 1..]); - (is_registry_package_name(name) && is_strict_semver(version)).then_some((name, version)) + (is_registry_package_name(name) && is_npm_semver(version)).then_some((name, version)) } /// The store decoder: `(full name, version)` of a `.vlt/` entry, @@ -413,10 +425,15 @@ pub(crate) enum LockSniff { /// which JS `JSON.parse` would accept. NotJsonObject, /// `lockfileVersion` is present but not the integer token `0` or `1` - /// (`1.0`, `1e0`, `"1"`, `2`, `null`, ...), rendered as JSON. + /// (`1.0`, `1e0`, `"1"`, `2`, `null`, ...), as its raw JSON token. UnsupportedVersion(String), } +fn raw_top_level_token(text: &str, key: &str) -> Option { + let members: HashMap = serde_json::from_str(text).ok()?; + members.get(key).map(|raw| raw.get().to_string()) +} + pub(crate) fn sniff_lock(text: &str) -> LockSniff { if text.starts_with('\u{feff}') { return LockSniff::Bom; @@ -428,7 +445,10 @@ pub(crate) fn sniff_lock(text: &str) -> LockSniff { None => None, Some(v) => match v.as_u64() { Some(n @ (0 | 1)) => Some(n), - _ => return LockSniff::UnsupportedVersion(v.to_string()), + _ => { + let raw = raw_top_level_token(text, "lockfileVersion"); + return LockSniff::UnsupportedVersion(raw.unwrap_or_else(|| v.to_string())); + } }, }; LockSniff::Readable(ParsedLock { version, json }) @@ -847,7 +867,7 @@ pub(crate) struct VendoredPath { fn leaf_version<'l>(bare: &str, leaf: &'l str) -> Option<&'l str> { leaf.strip_prefix(bare)? .strip_prefix('-') - .filter(|v| is_strict_semver(v)) + .filter(|v| is_npm_semver(v)) } /// A decoded `file` path of the vendored directory shape, with the name @@ -1177,6 +1197,36 @@ mod tests { row("~npm~a·b@1.0.0", reg(Tilde, "npm", "a·b@1.0.0"), None), row("~npm~a_@1.0.0", reg(Tilde, "npm", "a_@1.0.0"), Some(("a_", "1.0.0"))), row("~npm~_1f_0a_zz_@1.0.0", reg(Tilde, "npm", "\u{1f}\n_zz_@1.0.0"), None), + row( + "~npm~@__koii+web3.js@0.1.11", + reg(Tilde, "npm", "@_koii/web3.js@0.1.11"), + Some(("@_koii/web3.js", "0.1.11")), + ), + row( + "·npm·@_koii§web3.js@0.1.11", + reg(Legacy, "npm", "@_koii/web3.js@0.1.11"), + Some(("@_koii/web3.js", "0.1.11")), + ), + row( + "git~github_cu+p~v1~peer.1", + Some((Tilde, Git, "github:u/p", Some("v1"), Some("peer.1"))), + None, + ), + row( + "git·github%3Au§p·v1·peer.1", + Some((Legacy, Git, "github:u/p", Some("v1"), Some("peer.1"))), + None, + ), + row( + "remote~https_c++e.com+r.tgz~peer.1", + Some((Tilde, Remote, "https://e.com/r.tgz", None, Some("peer.1"))), + None, + ), + row( + "workspace~packages+a~peer.1", + Some((Tilde, Workspace, "packages/a", None, Some("peer.1"))), + None, + ), row("·npm·a@1.0.0%ZZ", None, None), row("·npm·a@1.0.0%4", None, None), row("·npm·a@1.0.0%C3", None, None), @@ -1184,6 +1234,9 @@ mod tests { row("·npm%zz·a@1.0.0", None, None), row("··ms@2.1.3·%ZZ", None, None), row("file·.socket%2", None, None), + row("file·a·%ZZ", None, None), + row("workspace·a·%ZZ", None, None), + row("remote·https%3A§§e.com§r.tgz·%ZZ", None, None), row("git·github%3Auser·v1%G0", None, None), row("npm~foo@1.0.0", None, None), row("foo@1.0.0", None, None), @@ -1248,7 +1301,8 @@ mod tests { #[test] fn recovers_name_and_version_from_a_registry_second() { - let long = format!("{}@1.0.0", "a".repeat(215)); + let long_name = "a".repeat(215); + let long = format!("{long_name}@1.0.0"); let rows: Vec<(&str, Option<(&str, &str)>)> = vec![ ("a@1.0.0", Some(("a", "1.0.0"))), ("@s/p@1.0.0-rc.1+b.2", Some(("@s/p", "1.0.0-rc.1+b.2"))), @@ -1266,22 +1320,46 @@ mod tests { ("a@1.0", None), ("a@1.0.0 ", None), ("a@^1.0.0", None), + ("a@1.0.0-01", None), + ( + "a@9007199254740991.0.0", + Some(("a", "9007199254740991.0.0")), + ), + ("a@9007199254740992.0.0", None), + ("a@99999999999999999999.0.0", None), + ("a@1.9007199254740992.0", None), + ("a@1.0.9007199254740992", None), + ( + "a@1.0.0-99999999999999999999", + Some(("a", "1.0.0-99999999999999999999")), + ), (".a@1.0.0", None), ("_a@1.0.0", None), - ("@s/.p@1.0.0", None), - ("@_s/p@1.0.0", None), + ("@s/.p@1.0.0", Some(("@s/.p", "1.0.0"))), + ("@_s/p@1.0.0", Some(("@_s/p", "1.0.0"))), + ("@s/_p@1.0.0", Some(("@s/_p", "1.0.0"))), + ("@.s/p@1.0.0", Some(("@.s/p", "1.0.0"))), + ("@_koii/web3.js@0.1.11", Some(("@_koii/web3.js", "0.1.11"))), + ("a~'!()*@1.0.0", Some(("a~'!()*", "1.0.0"))), + ("node_modules@1.0.0", None), + ("Node_Modules@1.0.0", None), + ("favicon.ico@1.0.0", None), + ("@s/node_modules@1.0.0", Some(("@s/node_modules", "1.0.0"))), + ("@/p@1.0.0", None), + ("@s/@1.0.0", None), + ("@s/p/q@1.0.0", None), + ("@s/p q@1.0.0", None), ("a b@1.0.0", None), ("a/b@1.0.0", None), - (long.as_str(), None), + (long.as_str(), Some((long_name.as_str(), "1.0.0"))), ]; for (second, expected) in rows { assert_eq!(registry_name_version(second), expected, "{second}"); } - assert!(is_strict_semver("99999999999999999999.0.0")); - assert!(is_strict_semver("1.0.0-0a.01b+001")); - assert!(!is_strict_semver("1.0.0-01")); - assert!(!is_strict_semver("1.0.0\n")); - assert!(!is_strict_semver("١.0.0")); + assert!(is_npm_semver("1.0.0-0a.01b+001")); + assert!(!is_npm_semver("1.0.0\n")); + assert!(!is_npm_semver("١.0.0")); + assert!(!is_npm_semver("18446744073709551616.0.0")); } #[test] @@ -1495,6 +1573,13 @@ mod tests { } let not_a_url = options(r#"{"registry":"corp"}"#); assert!(!is_default_registry("corp", Some(¬_a_url))); + for registry in ["file:///r/", "ftp://h/", "git+https://h/"] { + let not_http = options(&format!(r#"{{"registry":"{registry}"}}"#)); + assert!( + !is_default_registry(registry, Some(¬_http)), + "{registry}" + ); + } } fn readable(text: &str) -> ParsedLock { @@ -1515,22 +1600,37 @@ mod tests { assert_eq!(readable(r#"{"lockfileVersion":1}"#).new_id_era(), Tilde); assert_eq!(readable(r#"{"lockfileVersion":0}"#).new_id_era(), Legacy); assert_eq!(readable("{}").new_id_era(), Legacy); - for (token, rendered) in [ - ("1.0", "1.0"), - ("1e0", "1.0"), - ("1.0000000000000001", "1.0"), - ("\"1\"", "\"1\""), - ("2", "2"), - ("-1", "-1"), - ("-0", "-0.0"), - ("null", "null"), - ("true", "true"), + for token in [ + "1.0", + "1e0", + "1E0", + "1.0000000000000001", + "\"1\"", + "\"\\u0031\"", + "2", + "-1", + "-0", + "null", + "true", + "[1]", + "{\"v\": 1}", ] { - match sniff_lock(&format!("{{\"lockfileVersion\":{token}}}")) { - LockSniff::UnsupportedVersion(v) => assert_eq!(v, rendered, "{token}"), - other => panic!("{token} sniffed as {other:?}"), + for text in [ + format!("{{\"lockfileVersion\":{token}}}"), + format!("{{\n \"lockfileVersion\" : {token} ,\n \"nodes\": {{}}\n}}"), + format!("{{\"options\":{{\"lockfileVersion\":1}},\"lockfileVersion\":{token}}}"), + format!("{{\"lockfileVersion\":1,\"lockfileVersion\":{token}}}"), + ] { + match sniff_lock(&text) { + LockSniff::UnsupportedVersion(v) => assert_eq!(v, token, "{text}"), + other => panic!("{text} sniffed as {other:?}"), + } } } + assert_eq!( + readable(r#"{"lockfileVersion":2,"lockfileVersion":1}"#).version, + Some(1) + ); } #[test] @@ -2178,6 +2278,12 @@ mod tests { "1.0.0", VendoredShape::Dir, ); + expect( + &dir(".socket/vendor/npm//@_koii/web3.js-0.1.11/node_modules/@_koii/web3.js"), + "@_koii/web3.js", + "0.1.11", + VendoredShape::Dir, + ); expect( &dir(".socket/vendor/npm//left-pad-1.3.0.tgz"), "left-pad", @@ -2255,6 +2361,19 @@ mod tests { "left-pad", ), (".socket/vendor/npm//.a-1.0.0/node_modules/.a", ".a"), + ( + ".socket/vendor/npm//node_modules-1.0.0/node_modules/node_modules", + "node_modules", + ), + ( + ".socket/vendor/npm//favicon.ico-1.0.0.tgz", + "favicon.ico", + ), + ( + ".socket/vendor/npm//a-9007199254740992.0.0/node_modules/a", + "a", + ), + (".socket/vendor/npm//a-1.0.9007199254740992.tgz", "a"), ( "vendor/npm//left-pad-1.3.0/node_modules/left-pad", "left-pad", diff --git a/crates/socket-patch-core/src/vex/discover/testing/golden.rs b/crates/socket-patch-core/src/vex/discover/testing/golden.rs index 6aa760e3..8af57ab9 100644 --- a/crates/socket-patch-core/src/vex/discover/testing/golden.rs +++ b/crates/socket-patch-core/src/vex/discover/testing/golden.rs @@ -482,9 +482,12 @@ mod tests { #[test] fn table_fixture_dirs_are_not_corpus_projects() { let corpus = corpus(); - for skipped in ["vlt/", "vlt-trees/", "vendor/"] { + for skipped in ["vlt", "vlt-trees", "vendor"] { + let nested = format!("{skipped}/"); assert!( - corpus.iter().all(|(name, _, _)| !name.starts_with(skipped)), + corpus + .iter() + .all(|(name, _, _)| name != skipped && !name.starts_with(&nested)), "{skipped} fixtures joined the corpus" ); } From 56ee39071de3c9c3ae75c62b832ced7e1c229e3b Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Fri, 25 Sep 2026 21:15:20 -0400 Subject: [PATCH 09/46] Patch vlt installs in agent mode socket-patch now recognizes a project installed by vlt (the node_modules/.vlt store or node_modules/.vlt-lock.json) ahead of any sibling bun, pnpm, yarn or npm marker, and apply prints a vlt layout note in human mode. scan, get, apply, rollback and vex now find every package in vlt's store in every DepID era: transitive-only packages, aliases, git, remote and file: entries, and workspace members whose node_modules hold only links. apply and rollback reach every store copy of a patched package, including vlt's peer and modifier variants, and each write replaces the file instead of writing through it, so vlt's machine-wide store stays untouched. The store-copy failure note now reads "store copy failed to patch" for pnpm and vlt alike. In a vlt project, --update suggests vlt install @socketsecurity/socket-patch@latest, and in vlx's cache it suggests re-running vlx with @latest. The crawler tests stage real layouts captured from vlt 0.0.0-32, 1.0.0-rc.14, 1.0.10, 1.2.0 and a 1.0.0-rc.22 workspace. Assisted-by: Claude Code:claude-opus-5-5 --- CHANGELOG.md | 16 + crates/socket-patch-cli/CLI_CONTRACT.md | 2 +- crates/socket-patch-cli/src/commands/apply.rs | 19 +- .../src/commands/vex_consumed.rs | 62 +- .../src/ecosystem_dispatch.rs | 33 + .../tests/covgap_commands_apply.rs | 57 +- .../tests/in_process_npm_multicopy.rs | 120 +- .../src/crawlers/npm_crawler.rs | 608 ++- .../src/crawlers/pkg_managers.rs | 133 +- crates/socket-patch-core/src/patch/apply.rs | 23 +- .../socket-patch-core/src/patch/rollback.rs | 16 +- .../socket-patch-core/src/update/channel.rs | 112 +- crates/socket-patch-core/src/utils/fs.rs | 163 + .../tests/covgap_crawlers_npm_crawler.rs | 75 +- .../tests/covgap_patch_apply.rs | 116 +- .../tests/crawler_npm_e2e.rs | 718 +++ .../fixtures/vlt-trees/0.0.0-32/README.md | 21 + .../fixtures/vlt-trees/0.0.0-32/listing.json | 4588 ++++++++++++++++ .../fixtures/vlt-trees/1.0.0-rc.14/README.md | 21 + .../vlt-trees/1.0.0-rc.14/listing.json | 4596 +++++++++++++++++ .../vlt-trees/1.0.0-rc.22-workspace/README.md | 14 + .../1.0.0-rc.22-workspace/listing.json | 204 + .../tests/fixtures/vlt-trees/1.0.10/README.md | 21 + .../fixtures/vlt-trees/1.0.10/listing.json | 4596 +++++++++++++++++ .../tests/fixtures/vlt-trees/1.2.0/README.md | 21 + .../fixtures/vlt-trees/1.2.0/listing.json | 4596 +++++++++++++++++ scripts/capture-vlt-tree.mjs | 137 + 27 files changed, 20953 insertions(+), 135 deletions(-) create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-trees/0.0.0-32/README.md create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-trees/0.0.0-32/listing.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.0-rc.14/README.md create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.0-rc.14/listing.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.0-rc.22-workspace/README.md create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.0-rc.22-workspace/listing.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.10/README.md create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.10/listing.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-trees/1.2.0/README.md create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-trees/1.2.0/listing.json create mode 100644 scripts/capture-vlt-tree.mjs diff --git a/CHANGELOG.md b/CHANGELOG.md index c3a2ca45..6f192916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -263,6 +263,22 @@ into the new version's section — see docs/releasing.md. ### Added +- **`apply` and `rollback` patch vlt installs in place.** A project + installed by vlt (`node_modules/.vlt/` or `node_modules/.vlt-lock.json`) + is detected as vlt ahead of any sibling bun, pnpm, yarn or npm marker, + and `apply` prints `Note: vlt layout detected…` in human mode. `scan`, + `get`, `apply`, `rollback` and `vex` find every package in vlt's store + (`node_modules/.vlt//node_modules/`) in every DepID era, + including transitive-only packages, aliases, git/remote/`file:` entries + and workspace members' link-only trees. `apply` and `rollback` reach + every store copy of a patched `name@version` (vlt's `~peer.`, + hashed-peer and modifier variants, and the legacy `··` / `·npm·` pair), + and every write replaces the file rather than writing through it, so + vlt 1.2's machine-wide store (hardlinked on Linux) stays untouched. The + store-copy failure note is now `store copy failed to patch` / + `failed to roll back` for pnpm and vlt alike. `--update` in a vlt + project suggests `vlt install @socketsecurity/socket-patch@latest`, and + in vlx's cache `vlx -y -- @socketsecurity/socket-patch@latest …`. - **`redirect_yarn_berry_mixed_line_endings` and `vendor_yarn_berry_mixed_line_endings`.** A `yarn.lock` (or, vendored, a root `package.json`) that mixes CRLF and LF line endings — or holds a bare diff --git a/crates/socket-patch-cli/CLI_CONTRACT.md b/crates/socket-patch-cli/CLI_CONTRACT.md index bc130bdb..7e89f34f 100644 --- a/crates/socket-patch-cli/CLI_CONTRACT.md +++ b/crates/socket-patch-cli/CLI_CONTRACT.md @@ -967,7 +967,7 @@ Honored global flags: `--json`, `--silent` (errors only), `--yes` (skip the conf | Detected channel | Hint | |---|---| -| npm (`node_modules` path component) | project-local (the directory holding the outermost `node_modules` has a `package.json`, and it is not directly under `lib`/`npm` or below a yarn/pnpm `global` store): `npm install @socketsecurity/socket-patch@latest`; otherwise global (including version-manager prefixes such as nvm-windows and fnm): `npm update -g @socketsecurity/socket-patch` | +| npm (`node_modules` path component) | project-local (the directory holding the outermost `node_modules` has a `package.json`, and it is not directly under `lib`/`npm` or below a yarn/pnpm `global` store): `npm install @socketsecurity/socket-patch@latest`, or `vlt install @socketsecurity/socket-patch@latest` when that directory holds `vlt-lock.json`, or `vlx -y -- @socketsecurity/socket-patch@latest …` when its `package.json` is vlx's (`"name": "vlx"`, the vlx cache); otherwise global (including version-manager prefixes such as nvm-windows and fnm): `npm update -g @socketsecurity/socket-patch` | | PyPI wheel (`site-packages`/`dist-packages`) | `pip install --upgrade socket-patch` | | `cargo install` (`$CARGO_HOME/bin`, `~/.cargo/bin`) | `cargo install socket-patch-cli` | | gem launcher cache (`/socket-patch/bin/…`) | `gem update socket-patch` | diff --git a/crates/socket-patch-cli/src/commands/apply.rs b/crates/socket-patch-cli/src/commands/apply.rs index 3854401f..4b40c6f4 100644 --- a/crates/socket-patch-cli/src/commands/apply.rs +++ b/crates/socket-patch-cli/src/commands/apply.rs @@ -954,10 +954,10 @@ pub(crate) async fn run_locked( // `yarn patch` — but only when an npm patch is actually in scope: // a polyglot repo's pypi/gem/go patches apply fine under PnP, and a // global-tree or non-npm `--ecosystems` run never crawls this - // checkout's node_modules at all. pnpm gets an informational note; - // the substantive safety is core's rename-over write - // (`utils::fs::atomic_write_bytes` never touches the store's shared - // inode). + // checkout's node_modules at all. pnpm, bun and vlt get an + // informational note; the substantive safety is core's rename-over + // write (`utils::fs::atomic_write_bytes` never touches the store's + // shared inode). match detect_npm_pkg_manager(&args.common.cwd) { NpmPkgManager::YarnBerryPnP => { if eco_in_local_scope(&args.common, Ecosystem::Npm) && manifest_targets_npm(&manifest) { @@ -984,6 +984,17 @@ pub(crate) async fn run_locked( // install cache by default. The rename-over write handles the // safety; this is informational only. } + NpmPkgManager::Vlt => { + if !args.common.json && !args.common.silent { + eprintln!( + "Note: vlt layout detected. Copy-on-write keeps vlt's shared package store \ + (/store/v1) untouched." + ); + } + // vlt 1.2 hard-links store files from its machine-wide cache + // (the Linux default); the rename-over write gives every patched + // file a private inode, so this is informational only. + } // Exhaustive on purpose (no `_`): a new package-manager layout must // make an explicit appearance here — silence is a decision, not a // default. diff --git a/crates/socket-patch-cli/src/commands/vex_consumed.rs b/crates/socket-patch-cli/src/commands/vex_consumed.rs index b8319f4e..8b4dc668 100644 --- a/crates/socket-patch-cli/src/commands/vex_consumed.rs +++ b/crates/socket-patch-cli/src/commands/vex_consumed.rs @@ -23,7 +23,7 @@ //! | golang | the REPLACEMENT module `$GOMODCACHE/patch.socket.dev/gopatch/@` (the ref's `url`, else go.mod's hosted `replace`) | the original `M@v` | //! | cargo | `registry/src/-/-` for the lock source's host; several such registries (one per patch uuid) are narrowed to the one whose cached `.crate` has the lock's pinned checksum. A `vendor/` source tree or `--global-prefix` is taken as given | crates.io's / any other registry's extraction | //! | maven | `///-socket./` (the version the pom pins), its artifact files matched under the suffixed name | the `` version dir | -//! | npm | every `node_modules` copy the crawler finds, plus alias installs (`node_modules/` holding the package) in the root's and every workspace member's tree | — each serves some dependent: ALL must verify | +//! | npm | every `node_modules` copy the crawler finds (pnpm and vlt store copies included), plus alias installs (`node_modules/` holding the package) in the root's and every workspace member's tree | — each serves some dependent: ALL must verify | //! | pypi | every copy in the crawler's environment set (the project's venvs when it has any, else the interpreters) | — any may be the one that runs the project: ALL must verify | //! | gem | every copy in bundler's gem path | — bundler loads whichever `Gem.path` home it hits first: ALL must verify | //! @@ -153,11 +153,15 @@ const ALIAS_WALK_MAX_DIRS: usize = 200_000; /// (`packages/a/node_modules/lp`) unhashed whenever the root or a hoisted /// copy existed — the identity fallback only fills purls with NO copy — /// so a stale or tampered member alias attested from the good root copy. -/// Hidden entries (`.bin`, pnpm's `.pnpm` store — the crawler probes it) -/// and symlinks (pnpm's importer links, `npm link` targets) are not -/// traversed. A plain `--global` run is not walked: its roots come from -/// spawning every package manager again, and the identity fallback covers -/// an alias that is the only global copy. +/// Hidden entries (`.bin`, pnpm's `.pnpm` and vlt's `.vlt` stores — the +/// crawler probes them) and symlinks (pnpm's and vlt's importer links, +/// `npm link` targets) are not traversed. Under vlt EVERY importer entry, +/// an alias included, is a link into `.vlt//node_modules/`, +/// so this walk finds no vlt alias at all: the store copy is named after +/// the real package, and the crawler's store pass resolves it (the +/// identity fallback covers the rest). A plain `--global` run is not +/// walked: its roots come from spawning every package manager again, and +/// the identity fallback covers an alias that is the only global copy. async fn npm_alias_copies( options: &CrawlerOptions, purls: &[String], @@ -696,6 +700,52 @@ mod tests { assert!(npm_alias_copies(&global, &purls).await.is_empty()); } + /// vlt twin of the `.pnpm` case: every importer entry is a link into + /// the `.vlt` store (the alias `lp` too), so the alias walk yields + /// nothing, while the crawler resolves the alias's package from its + /// store copy, which is named after the real package. That store copy + /// is the consumed evidence. + #[cfg(unix)] + #[tokio::test] + async fn vlt_alias_is_consumed_through_its_store_copy() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let nm = root.join("node_modules"); + let store_copy = nm.join(".vlt/~npm~left-pad@1.1.3/node_modules/left-pad"); + pkg(&store_copy, "left-pad", "1.1.3"); + pkg( + &nm.join(".vlt/~npm~left-pad@1.3.0/node_modules/left-pad"), + "left-pad", + "1.3.0", + ); + std::os::unix::fs::symlink( + ".vlt/~npm~left-pad@1.1.3/node_modules/left-pad", + nm.join("lp"), + ) + .unwrap(); + std::os::unix::fs::symlink( + ".vlt/~npm~left-pad@1.3.0/node_modules/left-pad", + nm.join("left-pad"), + ) + .unwrap(); + + let purls = vec!["pkg:npm/left-pad@1.1.3".to_string()]; + assert!(npm_alias_copies(&local(root), &purls).await.is_empty()); + + let found = NpmCrawler::new().find_by_purls(&nm, &purls).await.unwrap(); + assert_eq!( + found["pkg:npm/left-pad@1.1.3"] + .iter() + .map(|p| p.path.clone()) + .collect::>(), + vec![store_copy.clone()] + ); + + let mut all: HashMap> = HashMap::new(); + npm_identity_fallback(Some(&purls), &local(root), &mut all, &HashMap::new()).await; + assert_eq!(all.get(&purls[0]), Some(&vec![nm.join("lp")])); + } + #[test] fn cargo_registry_dirs_are_matched_by_host_and_hash_shape() { assert!(is_registry_dir_for_host( diff --git a/crates/socket-patch-cli/src/ecosystem_dispatch.rs b/crates/socket-patch-cli/src/ecosystem_dispatch.rs index da32594c..9219b201 100644 --- a/crates/socket-patch-cli/src/ecosystem_dispatch.rs +++ b/crates/socket-patch-cli/src/ecosystem_dispatch.rs @@ -1116,6 +1116,39 @@ mod tests { assert_eq!(out.get("pkg:npm/foo@1.0.0"), Some(&pkg_dir)); } + /// The dispatch wiring over a vlt store: a direct dep resolves at its + /// importer link, a transitive-only dep at its `.vlt/` store + /// copy (here a legacy-era modifier-extra id), and both come back keyed + /// by the exact PURLs handed in. + #[tokio::test] + async fn find_packages_for_purls_maps_npm_purl_to_vlt_store_dir() { + let tmp = tempfile::tempdir().unwrap(); + let nm = tmp.path().join("node_modules"); + let store = nm.join(".vlt"); + let direct = write_npm_package(&store.join("~npm~foo@1.0.0"), "foo", "1.0.0"); + let transitive = write_npm_package( + &store.join("··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms"), + "ms", + "2.1.3", + ); + #[cfg(unix)] + std::os::unix::fs::symlink(&direct, nm.join("foo")).unwrap(); + + let purls = [ + "pkg:npm/foo@1.0.0".to_string(), + "pkg:npm/ms@2.1.3".to_string(), + ]; + let partitioned = partition_purls(&purls, None); + let out = + find_packages_for_purls(&partitioned, &local_options(tmp.path().to_path_buf()), true) + .await; + #[cfg(unix)] + assert_eq!(out.get("pkg:npm/foo@1.0.0"), Some(&nm.join("foo"))); + #[cfg(not(unix))] + assert_eq!(out.get("pkg:npm/foo@1.0.0"), Some(&direct)); + assert_eq!(out.get("pkg:npm/ms@2.1.3"), Some(&transitive)); + } + /// Multi-copy P0 at the dispatch layer: `find_all_packages_for_purls` /// must carry EVERY physical copy of a duplicated npm PURL (a root copy /// plus a nested duplicate), root-copy-first — the second path the old diff --git a/crates/socket-patch-cli/tests/covgap_commands_apply.rs b/crates/socket-patch-cli/tests/covgap_commands_apply.rs index 1ec297bd..4f57dacf 100644 --- a/crates/socket-patch-cli/tests/covgap_commands_apply.rs +++ b/crates/socket-patch-cli/tests/covgap_commands_apply.rs @@ -17,8 +17,9 @@ //! transient-stage failure warning; //! 5. human-mode output block: "No patches to apply.", the no-matching- //! packages warning, the npm per-package failure line, the dry-run -//! "already patched" count, `--verbose` per-file labels, the pnpm/bun -//! layout notes, and the corrupt-manifest-under-PnP fall-through; +//! "already patched" count, `--verbose` per-file labels, the +//! pnpm/bun/vlt layout notes, and the corrupt-manifest-under-PnP +//! fall-through; //! 6. gem fallback-home skip surfacing on human stderr; //! 7. apply-loop wiring: a vendored release-variant base with its //! installed tree PRESENT is skipped (not re-patched), and a qualified @@ -774,6 +775,58 @@ fn bun_layout_prints_informational_note_in_human_mode() { assert!(stdout.contains("No patches to apply."), "stdout={stdout}"); } +/// A vlt install (the `node_modules/.vlt` store) prints its informational +/// note in human mode and never refuses. vlt install state outranks a +/// sibling `bun.lock`, so the bun note does not print. +#[test] +fn vlt_layout_prints_informational_note_in_human_mode() { + let tmp = tempfile::tempdir().unwrap(); + write_manifest(tmp.path(), json!({})); + std::fs::create_dir_all(tmp.path().join("node_modules").join(".vlt")).unwrap(); + std::fs::write(tmp.path().join("bun.lock"), "{}\n").unwrap(); + + let (code, stdout, stderr) = run_apply(tmp.path(), &["--offline"], &[]); + assert_eq!( + code, 0, + "the vlt note is informational only; stderr={stderr}" + ); + assert!( + stderr.contains( + "Note: vlt layout detected. Copy-on-write keeps vlt's shared package store \ + (/store/v1) untouched." + ), + "the vlt layout note must print on human stderr; stderr={stderr}" + ); + assert!(!stderr.contains("bun layout detected"), "stderr={stderr}"); + assert!(stdout.contains("No patches to apply."), "stdout={stdout}"); +} + +/// The vlt note is human-only: `--json` and `--silent` runs (the install +/// hook runs `apply --silent`) stay quiet. The hidden lock alone is vlt +/// install state too. +#[test] +fn vlt_layout_note_is_muted_under_json_and_silent() { + let tmp = tempfile::tempdir().unwrap(); + write_manifest(tmp.path(), json!({})); + std::fs::create_dir_all(tmp.path().join("node_modules")).unwrap(); + std::fs::write(tmp.path().join("node_modules/.vlt-lock.json"), "{}").unwrap(); + + let (code, _stdout, stderr) = run_apply(tmp.path(), &["--offline"], &[]); + assert_eq!(code, 0, "stderr={stderr}"); + assert!( + stderr.contains("Note: vlt layout detected."), + "stderr={stderr}" + ); + for flag in ["--json", "--silent"] { + let (code, _stdout, stderr) = run_apply(tmp.path(), &["--offline", flag], &[]); + assert_eq!(code, 0, "{flag}: stderr={stderr}"); + assert!( + !stderr.contains("layout detected"), + "{flag}: stderr={stderr}" + ); + } +} + /// A corrupt manifest under a yarn-PnP layout must fall through to the /// ordinary manifest-unreadable error — NOT the misdirected /// `yarn_pnp_unsupported` refusal (`manifest_targets_npm`'s `_ => false` diff --git a/crates/socket-patch-cli/tests/in_process_npm_multicopy.rs b/crates/socket-patch-cli/tests/in_process_npm_multicopy.rs index 9efe6588..35ab91da 100644 --- a/crates/socket-patch-cli/tests/in_process_npm_multicopy.rs +++ b/crates/socket-patch-cli/tests/in_process_npm_multicopy.rs @@ -225,7 +225,11 @@ fn rollback_restores_every_on_disk_copy_of_a_duplicated_package() { let (code, _v) = run_apply(&root); assert_eq!(code, 0); for f in [&index_a, &index_b] { - assert_eq!(std::fs::read(f).unwrap(), patched, "precondition: patched {f:?}"); + assert_eq!( + std::fs::read(f).unwrap(), + patched, + "precondition: patched {f:?}" + ); } // Now roll back and assert EVERY copy is restored to pristine bytes. @@ -259,3 +263,117 @@ fn rollback_restores_every_on_disk_copy_of_a_duplicated_package() { // Guard against the fixture asserting nothing. assert_ne!(before_hash, after_hash); } + +/// vlt twin: rc.15–1.0.7 materialize one store entry per peer context +/// (`.vlt/~npm~dupvuln@1.0.0~peer.2/` and `~peer.3/`), both real and +/// runtime-loaded. The importer links ONE of them, so the resolver hands +/// apply one primary and the store fan-out must reach the other; rollback +/// restores both. Returns `(root, primary index.js, twin index.js)`. +fn build_vlt_peer_variant_tree(tmp: &Path, link_importer: bool) -> (PathBuf, PathBuf, PathBuf) { + let name = "dupvuln"; + let purl = "pkg:npm/dupvuln@1.0.0"; + let original = b"module.exports = function(){ return 'VULNERABLE'; };\n"; + let mut patched = original.to_vec(); + patched.extend_from_slice(b"// SOCKET-PATCHED-MULTICOPY\n"); + std::fs::write( + tmp.join("package.json"), + r#"{ "name": "vlt-root", "version": "0.0.0" }"#, + ) + .unwrap(); + std::fs::write( + tmp.join("vlt-lock.json"), + r#"{"lockfileVersion":1,"options":{},"nodes":{},"edges":{}}"#, + ) + .unwrap(); + let store = tmp.join("node_modules").join(".vlt"); + let entry = |id: &str| store.join(id).join("node_modules").join(name); + let primary = write_copy(&entry("~npm~dupvuln@1.0.0~peer.2"), name, "1.0.0", original); + let twin = write_copy(&entry("~npm~dupvuln@1.0.0~peer.3"), name, "1.0.0", original); + std::fs::write(tmp.join("node_modules").join(".vlt-lock.json"), "{}").unwrap(); + if link_importer { + #[cfg(unix)] + std::os::unix::fs::symlink( + ".vlt/~npm~dupvuln@1.0.0~peer.2/node_modules/dupvuln", + tmp.join("node_modules").join(name), + ) + .unwrap(); + } + stage_manifest_and_blob( + tmp, + purl, + &git_sha256(original), + &git_sha256(&patched), + &patched, + ); + std::fs::write( + tmp.join(".socket").join("blobs").join(git_sha256(original)), + original, + ) + .unwrap(); + (tmp.to_path_buf(), primary, twin) +} + +fn run_rollback(root: &Path) -> (i32, serde_json::Value) { + let out = Command::new(binary()) + .args([ + "rollback", + "--json", + "--offline", + "--yes", + "--ecosystems", + "npm", + "--cwd", + root.to_str().unwrap(), + ]) + .output() + .expect("run rollback"); + let stdout = String::from_utf8_lossy(&out.stdout).to_string(); + let v: serde_json::Value = serde_json::from_str(stdout.trim()) + .unwrap_or_else(|e| panic!("rollback must emit JSON: {e}; stdout={stdout}")); + (out.status.code().unwrap_or(-1), v) +} + +/// Each copy holds exactly the patched bytes (`patched`) or exactly the +/// original bytes. +fn assert_vlt_copies(copies: [&Path; 2], patched: bool, stage: &str) { + let mut want = b"module.exports = function(){ return 'VULNERABLE'; };\n".to_vec(); + if patched { + want.extend_from_slice(b"// SOCKET-PATCHED-MULTICOPY\n"); + } + for f in copies { + assert_eq!(std::fs::read(f).unwrap(), want, "{stage}: {f:?}"); + } +} + +#[cfg(unix)] +#[test] +fn apply_and_rollback_reach_both_vlt_peer_variant_copies_from_an_importer_link() { + let tmp = tempfile::tempdir().unwrap(); + let (root, primary, twin) = build_vlt_peer_variant_tree(tmp.path(), true); + + let (code, v) = run_apply(&root); + assert_eq!(code, 0, "apply must succeed; envelope={v}"); + assert_eq!(v["status"], "success", "envelope={v}"); + assert_vlt_copies([&primary, &twin], true, "after apply"); + + let (code, v) = run_rollback(&root); + assert_eq!(code, 0, "rollback must succeed; envelope={v}"); + assert_vlt_copies([&primary, &twin], false, "after rollback"); +} + +/// Without an importer link (a transitive-only dependency) both store +/// copies are found by the resolver itself; each is patched exactly once +/// and both are restored. +#[test] +fn apply_and_rollback_reach_both_transitive_only_vlt_store_copies() { + let tmp = tempfile::tempdir().unwrap(); + let (root, primary, twin) = build_vlt_peer_variant_tree(tmp.path(), false); + + let (code, v) = run_apply(&root); + assert_eq!(code, 0, "apply must succeed; envelope={v}"); + assert_vlt_copies([&primary, &twin], true, "after apply"); + + let (code, v) = run_rollback(&root); + assert_eq!(code, 0, "rollback must succeed; envelope={v}"); + assert_vlt_copies([&primary, &twin], false, "after rollback"); +} diff --git a/crates/socket-patch-core/src/crawlers/npm_crawler.rs b/crates/socket-patch-core/src/crawlers/npm_crawler.rs index cb186fbb..f5263ec9 100644 --- a/crates/socket-patch-core/src/crawlers/npm_crawler.rs +++ b/crates/socket-patch-core/src/crawlers/npm_crawler.rs @@ -1,4 +1,5 @@ use std::collections::{HashMap, HashSet, VecDeque}; +use std::ffi::{OsStr, OsString}; use std::path::{Path, PathBuf}; use serde::Deserialize; @@ -7,6 +8,7 @@ use super::types::{CrawledPackage, CrawlerOptions}; use crate::patch::path_safety; use crate::utils::fs::is_dir; use crate::utils::purl::{percent_decode_purl_component, strip_purl_qualifiers}; +use crate::vendor::vlt_lock_text::decode_vlt_dep_id; /// Directories to skip when searching for workspace node_modules. const SKIP_DIRS: &[&str] = &[ @@ -187,6 +189,49 @@ fn is_legacy_pnpm_store_dir_name(name: &str) -> bool { name.starts_with(".registry.") } +/// The `node_modules` child that is vlt's per-project package store. +const VLT_STORE_NAME: &str = ".vlt"; + +/// `(name, version)` a `.vlt/` entry name advertises: the vlt store +/// decoder over the lossless name, `None` for git/file/remote/workspace +/// ids and for anything undecodable (which stays probeable). The pnpm +/// decoder must never see these names: it reads `··foo@1.0.0` as a package +/// named `··foo`, so the pending-name filter would skip the real `foo`. +fn decode_vlt_store_entry_name(entry_name: &OsStr) -> Option<(String, String)> { + entry_name.to_str().and_then(decode_vlt_dep_id) +} + +/// One virtual-store entry (pnpm or vlt): the `node_modules` holding its +/// package, and the `(name, version)` its dir name advertises. The +/// advertisement is advisory (the package.json probe is the authority), and +/// `None` means "unknowable from the name", never "empty". +struct StoreEntry { + advertised: Option<(String, String)>, + node_modules: PathBuf, +} + +impl StoreEntry { + fn pnpm(entries: Vec<(String, PathBuf)>) -> Vec { + entries + .into_iter() + .map(|(name, node_modules)| StoreEntry { + advertised: decode_pnpm_store_entry_name(&name), + node_modules, + }) + .collect() + } + + fn vlt(entries: Vec<(OsString, PathBuf)>) -> Vec { + entries + .into_iter() + .map(|(name, node_modules)| StoreEntry { + advertised: decode_vlt_store_entry_name(&name), + node_modules, + }) + .collect() + } +} + // --------------------------------------------------------------------------- // Global prefix detection helpers // --------------------------------------------------------------------------- @@ -399,13 +444,14 @@ struct Target { #[derive(Clone, Copy)] enum ScanPolicy<'a> { /// An importer's or package's `node_modules`: symlinked entries are - /// recorded (pnpm links direct deps; `npm link` targets) but never - /// traversed into, and a `.pnpm` child is the virtual store, scanned - /// in a deferred pass. + /// recorded (pnpm and vlt link direct deps; `npm link` targets) but + /// never traversed into, and a `.pnpm` or `.vlt` child is the virtual + /// store, scanned in a deferred pass. Importer, - /// One pnpm virtual-store entry's `node_modules`: only REAL - /// directories are inventoried — a symlinked entry here is the - /// package's dependency pointing at a sibling `.pnpm` store entry, + /// One pnpm or vlt store entry's `node_modules`: only REAL + /// directories are inventoried — a symlinked (or, on Windows, + /// junctioned) entry here is the package's dependency pointing at a + /// sibling store entry, /// which is inventoried via that entry; following it would record the /// same package under a path owned by a different store entry. /// `identity_seen` optionally carries the entry's own package name @@ -476,10 +522,10 @@ impl NpmCrawler { /// that only need one representative (`vendor`, `vex`, `setup`) can take /// the first and preserve the old root-preference. /// - /// pnpm's virtual-store peer-variant copies are deliberately NOT + /// pnpm's and vlt's store peer-variant copies are deliberately NOT /// enumerated here for a copy already found in an importer tree (a /// symlinked direct dep): those are handled by the apply engine's - /// [`find_pnpm_peer_variant_copies`] fan-out. A transitive-only package + /// [`find_store_peer_variant_copies`] fan-out. A transitive-only package /// that lives ONLY in the store is still resolved (its store copies are /// probed because no importer-tree copy was found). pub async fn find_by_purls( @@ -666,7 +712,25 @@ impl NpmCrawler { } let store_path = nm_path.join(&name); let entries = Self::list_pnpm_store_entries(&store_path).await; - Self::enqueue_pending_store_entries(entries, pending_names, queue); + Self::enqueue_pending_store_entries( + StoreEntry::pnpm(entries), + pending_names, + queue, + ); + continue; + } + // vlt's store has the same transitive-only-home property: every + // package lives at `.vlt//node_modules/` and the + // importer holds only links into it. + if name_str == VLT_STORE_NAME { + let Some(file_type) = crate::utils::fs::entry_file_type(&entry).await else { + continue; + }; + if !file_type.is_dir() { + continue; + } + let entries = Self::list_vlt_store_entries(&nm_path.join(&name)).await; + Self::enqueue_pending_store_entries(StoreEntry::vlt(entries), pending_names, queue); continue; } // pnpm <=3: the virtual store is a hidden `.` dir @@ -684,7 +748,11 @@ impl NpmCrawler { } let mut entries = Vec::new(); Self::collect_nested_store_entries(&nm_path.join(&name), &mut entries).await; - Self::enqueue_pending_store_entries(entries, pending_names, queue); + Self::enqueue_pending_store_entries( + StoreEntry::pnpm(entries), + pending_names, + queue, + ); continue; } if name_str.starts_with('.') || name_str == "node_modules" { @@ -742,19 +810,18 @@ impl NpmCrawler { /// entry for exactly those. Both enumerators only yield entries whose /// `node_modules` exists, so no re-stat here. fn enqueue_pending_store_entries( - entries: Vec<(String, PathBuf)>, + entries: Vec, pending_names: Option<&HashSet<&str>>, queue: &mut VecDeque, ) { - for (entry_name, entry_nm) in entries { - if let Some(filter) = pending_names { - if let Some((entry_pkg, _version)) = decode_pnpm_store_entry_name(&entry_name) { - if !filter.contains(entry_pkg.as_str()) { - continue; - } + for entry in entries { + if let (Some(filter), Some((entry_pkg, _version))) = (pending_names, &entry.advertised) + { + if !filter.contains(entry_pkg.as_str()) { + continue; } } - queue.push_back(entry_nm); + queue.push_back(entry.node_modules); } } @@ -902,6 +969,7 @@ impl NpmCrawler { Box::pin(async move { let mut results = Vec::new(); let mut pnpm_store: Option = None; + let mut vlt_store: Option = None; let mut legacy_stores: Vec = Vec::new(); let (store_entry, identity_seen) = match policy { ScanPolicy::Importer => (false, None), @@ -932,6 +1000,18 @@ impl NpmCrawler { continue; } + // vlt's store, deferred for the same reason: importer links + // win the `seen` dedup at their importer-root paths. + if !store_entry && name_str == VLT_STORE_NAME { + let Some(file_type) = crate::utils::fs::entry_file_type(&entry).await else { + continue; + }; + if file_type.is_dir() { + vlt_store = Some(node_modules_path.join(&name_str)); + } + continue; + } + // pnpm <=3 virtual store (a hidden `.` dir; // no `.pnpm` exists on those layouts): same // transitive-only-home property, same deferred scan so @@ -1005,12 +1085,16 @@ impl NpmCrawler { if let Some(store_path) = pnpm_store { let entries = Self::list_pnpm_store_entries(&store_path).await; - results.extend(Self::scan_store_entries(entries, seen).await); + results.extend(Self::scan_store_entries(StoreEntry::pnpm(entries), seen).await); } for store_path in legacy_stores { let mut entries = Vec::new(); Self::collect_nested_store_entries(&store_path, &mut entries).await; - results.extend(Self::scan_store_entries(entries, seen).await); + results.extend(Self::scan_store_entries(StoreEntry::pnpm(entries), seen).await); + } + if let Some(store_path) = vlt_store { + let entries = Self::list_vlt_store_entries(&store_path).await; + results.extend(Self::scan_store_entries(StoreEntry::vlt(entries), seen).await); } results @@ -1057,6 +1141,38 @@ impl NpmCrawler { entries } + /// Enumerate vlt's store (`node_modules/.vlt`), yielding the lossless + /// entry name and `/node_modules` for every REAL entry dir whose + /// `node_modules` is a real dir. Skipped: dot-names (store metadata and + /// the `.VLT.DELETE..` rollback staging that lingers on + /// Windows), the `node_modules` child (vlt's internal hoist dir: links + /// plus real `@scope` dirs holding links), files (`vlt.json`) and links. + /// The store is always flat; every entry holds exactly one real package + /// dir named after the package (never the alias). + async fn list_vlt_store_entries(store_path: &Path) -> Vec<(OsString, PathBuf)> { + let mut entries = Vec::new(); + for entry in crate::utils::fs::list_dir_entries(store_path).await { + let name = entry.file_name(); + if name.as_encoded_bytes().starts_with(b".") || name == "node_modules" { + continue; + } + let Some(file_type) = crate::utils::fs::entry_file_type(&entry).await else { + continue; + }; + if !file_type.is_dir() { + continue; + } + let entry_nm = store_path.join(&name).join("node_modules"); + let real_nm = tokio::fs::symlink_metadata(&entry_nm) + .await + .is_ok_and(|m| m.is_dir()); + if real_nm { + entries.push((name, entry_nm)); + } + } + entries + } + /// Descend a *nested* virtual-store host dir, yielding /// `(name@version, /node_modules)` for each package home /// found. Covers the two pre-flat layouts (both confirmed against @@ -1128,21 +1244,26 @@ impl NpmCrawler { } /// Inventory the packages under each virtual-store entry's - /// `node_modules` (entries come from `list_pnpm_store_entries` or - /// `collect_nested_store_entries`). An entry whose name decodes to a + /// `node_modules` (entries come from `list_pnpm_store_entries`, + /// `collect_nested_store_entries` or `list_vlt_store_entries`). An entry + /// whose name decodes to a /// name@version the importer pass already inventoried (every /// root-linked direct dep) skips the redundant package.json re-read /// via `identity_seen` — the entry is still walked, because /// bundled/injected dependencies are real dirs that physically live /// only inside the store entry. async fn scan_store_entries( - entries: Vec<(String, PathBuf)>, + entries: Vec, seen: &mut HashSet, ) -> Vec { let mut results = Vec::new(); - for (entry_name, entry_nm) in entries { - let identity_seen = decode_pnpm_store_entry_name(&entry_name) + for StoreEntry { + advertised, + node_modules: entry_nm, + } in entries + { + let identity_seen = advertised .filter(|(full_name, version)| { let (ns, bare) = parse_package_name(full_name); seen.contains(&build_npm_purl(ns.as_deref(), &bare, version)) @@ -1306,15 +1427,24 @@ impl Default for NpmCrawler { } // --------------------------------------------------------------------------- -// pnpm peer-variant duplicate discovery (used by the apply engine) +// Store peer-variant duplicate discovery (used by the apply engine) // --------------------------------------------------------------------------- +/// Which store layout a candidate store directory uses. +#[derive(Clone, Copy, PartialEq, Eq)] +enum StoreLayout { + Pnpm, + Vlt, +} + /// Find every OTHER physical copy of the package installed at `pkg_path` -/// inside the pnpm virtual store(s) reachable from it. +/// inside the pnpm or vlt store(s) reachable from it. /// -/// pnpm materializes one store copy PER PEER COMBINATION: -/// `.pnpm/foo@1.0.0(react@17…)/` and `.pnpm/foo@1.0.0(react@18…)/` are -/// both real directories holding the same `foo@1.0.0`, and each is +/// Both managers materialize one store copy PER PEER (or modifier) +/// COMBINATION: `.pnpm/foo@1.0.0(react@17…)/` and `…(react@18…)/`, or +/// `.vlt/~npm~foo@1.0.0~peer.2/` and `~peer.3/` (plus vlt's modifier +/// `~_croot…` extras and the legacy `··foo@1.0.0` / `·npm·foo@1.0.0` pair), +/// are all real directories holding the same `foo@1.0.0`, each /// runtime-loaded by whichever importer resolves to it. The purl-keyed /// resolver hands apply exactly ONE primary path (root-install-wins), so /// the apply engine calls this to fan every write out to the remaining @@ -1323,48 +1453,62 @@ impl Default for NpmCrawler { /// /// Discovery, all bounded and read-only: /// 1. Candidate stores come from the ancestor chains of `pkg_path` AND of -/// its canonicalized form (the root-linked primary is a symlink into -/// the store, and in a workspace the store lives beside the ROOT +/// its canonicalized form (the root-linked primary is a link into the +/// store, and in a workspace the store lives beside the ROOT /// `node_modules`, on the canonical chain only): any ancestor named -/// `.pnpm`, plus any `node_modules` ancestor's `.pnpm` child. Non-pnpm -/// layouts (npm/yarn trees, cargo/go/vendor dirs) have neither and -/// return early — this is the cheap common case. pnpm <=3 legacy -/// stores are keyed by plain `name/version` and cannot hold +/// `.pnpm` or `.vlt`, plus any `node_modules` ancestor's `.pnpm` and +/// `.vlt` children. Other layouts (npm/yarn trees, cargo/go/vendor dirs) +/// have neither and return early — the cheap common case. pnpm <=3 +/// legacy stores are keyed by plain `name/version` and cannot hold /// peer-variant duplicates, so they are deliberately not probed. -/// 2. Store entries are enumerated with the shared layout walker +/// 2. pnpm entries come from the shared layout walker /// (`list_pnpm_store_entries`, flat + nested); an entry whose name -/// decodes to a DIFFERENT name@version is skipped, an undecodable name -/// stays probeable (decode is advisory), and the package.json probe is -/// the authority — exactly the resolver's contract. -/// 3. Only REAL directories count (a symlink inside a store entry is -/// another entry's copy, already yielded via that entry), the copy -/// `pkg_path` itself canonicalizes to is excluded, and results are -/// deduped by canonical path. +/// decodes to a DIFFERENT name@version is skipped and an undecodable +/// name stays probeable. vlt entries come from `list_vlt_store_entries` +/// and must decode to exactly the primary's name@version: an +/// undecodable vlt id is a git/file/remote artifact with its own bytes, +/// not a peer variant of the registry copy. The package.json probe is +/// the authority either way. +/// 3. Only REAL directories count (a link inside a store entry is another +/// entry's copy, already yielded via that entry), the copy `pkg_path` +/// itself canonicalizes to is excluded, and results are deduped by +/// canonical path (both sides canonicalized, so Windows `\\?\` paths +/// compare consistently). /// /// The returned paths are the copies' package roots (each in its own /// store entry). Callers write through the hardened per-file pipeline, /// which breaks content-store hardlinks per copy — CoW safety holds for /// every copy independently. -pub async fn find_pnpm_peer_variant_copies(pkg_path: &Path) -> Vec { +pub async fn find_store_peer_variant_copies(pkg_path: &Path) -> Vec { // 1. Candidate stores from both ancestor chains (cheap stats only — // no file reads until a store is actually found). let canonical_pkg = tokio::fs::canonicalize(pkg_path).await.ok(); - let mut stores: Vec = Vec::new(); + let mut stores: Vec<(StoreLayout, PathBuf)> = Vec::new(); let mut seen_stores: HashSet = HashSet::new(); let chains = [Some(pkg_path), canonical_pkg.as_deref()]; for start in chains.into_iter().flatten() { let mut cur = start.parent(); while let Some(dir) = cur { - match dir.file_name().map(|n| n.to_string_lossy()) { - Some(name) if name == ".pnpm" => { + match dir.file_name().and_then(OsStr::to_str) { + Some(".pnpm") => { if seen_stores.insert(dir.to_path_buf()) { - stores.push(dir.to_path_buf()); + stores.push((StoreLayout::Pnpm, dir.to_path_buf())); } } - Some(name) if name == "node_modules" => { - let store = dir.join(".pnpm"); - if is_dir(&store).await && seen_stores.insert(store.clone()) { - stores.push(store); + Some(VLT_STORE_NAME) => { + if seen_stores.insert(dir.to_path_buf()) { + stores.push((StoreLayout::Vlt, dir.to_path_buf())); + } + } + Some("node_modules") => { + for (child, layout) in [ + (".pnpm", StoreLayout::Pnpm), + (VLT_STORE_NAME, StoreLayout::Vlt), + ] { + let store = dir.join(child); + if is_dir(&store).await && seen_stores.insert(store.clone()) { + stores.push((layout, store)); + } } } _ => {} @@ -1386,18 +1530,29 @@ pub async fn find_pnpm_peer_variant_copies(pkg_path: &Path) -> Vec { let mut copies: Vec = Vec::new(); let mut seen_copies: HashSet = HashSet::new(); - for store in stores { - for (entry_name, entry_nm) in NpmCrawler::list_pnpm_store_entries(&store).await { - // Fast advertisement filter; undecodable names stay probeable. - if let Some((n, v)) = decode_pnpm_store_entry_name(&entry_name) { - if n != full_name || v != version { - continue; - } + for (layout, store) in stores { + let entries = match layout { + StoreLayout::Pnpm => { + StoreEntry::pnpm(NpmCrawler::list_pnpm_store_entries(&store).await) + } + StoreLayout::Vlt => StoreEntry::vlt(NpmCrawler::list_vlt_store_entries(&store).await), + }; + for StoreEntry { + advertised, + node_modules: entry_nm, + } in entries + { + // Fast advertisement filter; undecodable pnpm names stay + // probeable, undecodable vlt ids are never variants. + match (advertised, layout) { + (Some((n, v)), _) if n != full_name || v != version => continue, + (None, StoreLayout::Vlt) => continue, + _ => {} } // `full_name` may be scoped (`@s/n`) — Path::join handles the // two-segment relative form. let candidate = entry_nm.join(&full_name); - // Real dirs only: a symlink here is another entry's physical + // Real dirs only: a link here is another entry's physical // copy, reached via that entry. let Ok(meta) = tokio::fs::symlink_metadata(&candidate).await else { continue; @@ -2305,4 +2460,335 @@ mod tests { "fnm layout must be discovered under $HOME; got {paths:?}" ); } + + /// §5.2 store decoder table: `(full name, version)` from any registry + /// segment in both eras, extras ignored; git, file, remote, workspace + /// and undecodable ids are `None` (still probed by package.json). + #[test] + fn test_decode_vlt_dep_id_table() { + let some = |n: &str, v: &str| Some((n.to_string(), v.to_string())); + let rows: &[(&str, Option<(String, String)>)] = &[ + ("··ms@2.1.3", some("ms", "2.1.3")), + ( + "·npm·@isaacs§string-locale-compare@1.1.0", + some("@isaacs/string-locale-compare", "1.1.0"), + ), + ( + "··@sindresorhus§is@4.6.0", + some("@sindresorhus/is", "4.6.0"), + ), + ("·npm·u@1.0.0%2Bbuild.1", some("u", "1.0.0+build.1")), + ( + "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + some("ms", "2.1.3"), + ), + ("·npm·x@1.0.0·%E1%B9%97%3A3", some("x", "1.0.0")), + ("~npm~@a+b@1.0.0", some("@a/b", "1.0.0")), + ("~npm~a__b@1.0.0", some("a_b", "1.0.0")), + ("~npm~u@1.0.0_pbuild.1", some("u", "1.0.0+build.1")), + ( + "~npm~is-number@6.0.0~_croot_s_g_s#to-regex-range_s_g_s#is-number", + some("is-number", "6.0.0"), + ), + ( + "~npm~react-dom@18.2.0~peer.ace93b147498ef7a", + some("react-dom", "18.2.0"), + ), + ("~npm~x@1.0.0~peer.2", some("x", "1.0.0")), + ("~npm~x@1~peer.2", None), + ("~acme~left-pad@1.3.0", some("left-pad", "1.3.0")), + ("~http_c++127.0.0.1_c4873+~x@1.0.0", some("x", "1.0.0")), + ("·http%3A§§127.0.0.1%3A4873§·x@1.0.0", some("x", "1.0.0")), + ( + "~jsr~@jsr+std____semver@1.0.8", + some("@jsr/std__semver", "1.0.8"), + ), + ("git~github_cisaacs+string-locale-compare~v1.1.0", None), + ("git·github%3Aisaacs§string-locale-compare·v1.1.0", None), + ("file~vendor+ms-2.1.2.tgz", None), + ("file·vendor§ms-2.1.2.tgz", None), + ( + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + None, + ), + ("workspace~packages+a", None), + ("workspace·packages§a", None), + ("··m%ZZs@1.0.0", None), + ("·npm·ms@2.1.3·%4", None), + ("ms@2.1.3", None), + ("node_modules", None), + ]; + for (id, want) in rows { + assert_eq!(&decode_vlt_store_entry_name(OsStr::new(id)), want, "{id}"); + } + } + + /// The hazard the separate decoders exist for: the pnpm decoder reads + /// legacy vlt names as packages named `··foo` / `·npm·@s§p`, so a vlt + /// entry run through it would be skipped by the pending-name filter. + /// vlt entries are advertised through the vlt decoder only. + #[test] + fn test_vlt_store_entries_never_reach_the_pnpm_decoder() { + assert_eq!( + decode_pnpm_store_entry_name("··foo@1.0.0"), + Some(("··foo".to_string(), "1.0.0".to_string())) + ); + let entries = || { + vec![ + (OsString::from("··foo@1.0.0"), PathBuf::from("a")), + (OsString::from("·npm·@s§p@2.0.0"), PathBuf::from("b")), + (OsString::from("~npm~@s+p@2.0.0~peer.1"), PathBuf::from("c")), + ] + }; + let pending: HashSet<&str> = ["foo", "@s/p"].into_iter().collect(); + let mut queue = VecDeque::new(); + NpmCrawler::enqueue_pending_store_entries( + StoreEntry::vlt(entries()), + Some(&pending), + &mut queue, + ); + assert_eq!( + queue, + VecDeque::from([PathBuf::from("a"), PathBuf::from("b"), PathBuf::from("c")]) + ); + let mut queue = VecDeque::new(); + let as_pnpm = entries() + .into_iter() + .map(|(n, p)| (n.into_string().unwrap(), p)) + .collect(); + NpmCrawler::enqueue_pending_store_entries( + StoreEntry::pnpm(as_pnpm), + Some(&pending), + &mut queue, + ); + assert!( + !queue.contains(&PathBuf::from("a")), + "the pnpm decoder misreads the legacy name" + ); + } + + #[test] + fn test_vlt_store_is_not_a_legacy_pnpm_store() { + for name in [".vlt", ".vlt-lock.json", ".VLT.DELETE.1.~npm~a@1.0.0"] { + assert!(!is_legacy_pnpm_store_dir_name(name), "{name}"); + } + } + + /// `list_vlt_store_entries` yields only real entry dirs with a real + /// `node_modules`, keeping the raw (legacy `·`/`§`) names. + #[cfg(unix)] + #[tokio::test] + async fn test_list_vlt_store_entries_skips_hoist_meta_links_and_files() { + let tmp = tempfile::tempdir().unwrap(); + let store = tmp.path().join(".vlt"); + for dir in [ + "··ms@2.1.3/node_modules/ms", + "~npm~a@1.0.0/node_modules/a", + "node_modules/@scope", + ".VLT.DELETE.9.~npm~b@1.0.0/node_modules/b", + "~npm~no-nm@1.0.0/no-nm", + "elsewhere/node_modules", + "~npm~nm-link@1.0.0", + ] { + std::fs::create_dir_all(store.join(dir)).unwrap(); + } + std::fs::write(store.join("vlt.json"), "{}").unwrap(); + std::os::unix::fs::symlink(store.join("~npm~a@1.0.0"), store.join("~npm~linked@1.0.0")) + .unwrap(); + std::os::unix::fs::symlink( + store.join("elsewhere/node_modules"), + store.join("~npm~nm-link@1.0.0/node_modules"), + ) + .unwrap(); + + let mut got: Vec<(OsString, PathBuf)> = NpmCrawler::list_vlt_store_entries(&store).await; + got.sort(); + assert_eq!( + got, + vec![ + ( + OsString::from("elsewhere"), + store.join("elsewhere/node_modules") + ), + ( + OsString::from("~npm~a@1.0.0"), + store.join("~npm~a@1.0.0/node_modules") + ), + ( + OsString::from("··ms@2.1.3"), + store.join("··ms@2.1.3/node_modules") + ), + ] + ); + } + + fn write_pkg(dir: &Path, name: &str, version: &str) { + std::fs::create_dir_all(dir).unwrap(); + std::fs::write( + dir.join("package.json"), + format!(r#"{{"name":"{name}","version":"{version}"}}"#), + ) + .unwrap(); + } + + /// A directory link the way vlt writes it: a symlink on Unix, an + /// absolute-target NTFS junction on Windows (vlt >= 1.0.0-rc.22). + fn link_dir(target: &Path, link: &Path) { + #[cfg(unix)] + std::os::unix::fs::symlink(target, link).unwrap(); + #[cfg(windows)] + { + let status = std::process::Command::new("cmd") + .args(["/C", "mklink", "/J"]) + .arg(link) + .arg(target) + .status() + .unwrap(); + assert!(status.success(), "mklink /J failed"); + } + } + + /// A small vlt tree on the host's own link kind (junctions on Windows): + /// the importer link resolves at the importer root, a dependency link + /// inside a store entry is an edge (never a second inventory entry), + /// the legacy `·npm·@scope§bar@2.0.0` name round-trips through the + /// filesystem, and the fan-out finds the peer twin from the link. + #[tokio::test] + async fn test_vlt_store_links_are_edges_on_every_platform() { + let tmp = tempfile::tempdir().unwrap(); + let root: PathBuf = tmp.path().components().collect(); + let nm = root.join("node_modules"); + let store = nm.join(".vlt"); + let bar = store + .join("·npm·@scope§bar@2.0.0") + .join("node_modules") + .join("@scope") + .join("bar"); + write_pkg(&bar, "@scope/bar", "2.0.0"); + let foo_entry = store.join("~npm~foo@1.0.0~peer.2").join("node_modules"); + write_pkg(&foo_entry.join("foo"), "foo", "1.0.0"); + std::fs::create_dir_all(foo_entry.join("@scope")).unwrap(); + link_dir(&bar, &foo_entry.join("@scope").join("bar")); + let twin = store + .join("~npm~foo@1.0.0~peer.3") + .join("node_modules") + .join("foo"); + write_pkg(&twin, "foo", "1.0.0"); + link_dir(&foo_entry.join("foo"), &nm.join("foo")); + + let options = CrawlerOptions { + cwd: root.clone(), + global: false, + global_prefix: None, + }; + let mut scanned: Vec<(String, PathBuf)> = NpmCrawler::new() + .crawl_all(&options) + .await + .into_iter() + .map(|p| (p.purl, p.path)) + .collect(); + scanned.sort(); + assert_eq!( + scanned, + vec![ + ("pkg:npm/@scope/bar@2.0.0".to_string(), bar.clone()), + ("pkg:npm/foo@1.0.0".to_string(), nm.join("foo")), + ] + ); + + let found = NpmCrawler::new() + .find_by_purls(&nm, &["pkg:npm/foo@1.0.0".to_string()]) + .await + .unwrap(); + let primary = &found["pkg:npm/foo@1.0.0"][0].path; + assert_eq!(primary, &nm.join("foo")); + assert_eq!(find_store_peer_variant_copies(primary).await, vec![twin]); + } + + /// vlt fan-out gates: every real copy whose DepID decodes to the + /// primary's `name@version` (peer counters, hashed peers, modifier + /// extras, the legacy `··`/`·npm·` pair) is returned once; the primary, + /// dependency links, the hoist dir, rollback staging, an imposter + /// package.json and git/remote/file ids (distinct artifacts, not + /// variants) are not. + #[cfg(unix)] + #[tokio::test] + async fn test_find_store_peer_variant_copies_vlt_gates() { + let tmp = tempfile::tempdir().unwrap(); + let nm = tmp.path().join("node_modules"); + let store = nm.join(".vlt"); + let copy = |id: &str| store.join(id).join("node_modules").join("foo"); + let primary = copy("~npm~foo@1.0.0~peer.2"); + let twins = [ + copy("~npm~foo@1.0.0~peer.3"), + copy("~npm~foo@1.0.0~peer.dbd5ca8b03a66489"), + copy("~npm~foo@1.0.0~_croot_s_g_s#foo"), + copy("··foo@1.0.0"), + copy("·npm·foo@1.0.0"), + ]; + for dir in std::iter::once(&primary).chain(&twins) { + write_pkg(dir, "foo", "1.0.0"); + } + for id in [ + "git~github_cu+foo~v1.0.0", + "remote~https_c++e.com+foo-1.0.0.tgz", + "file~vendor+foo-1.0.0.tgz", + ".VLT.DELETE.1.~npm~foo@1.0.0", + ] { + write_pkg(©(id), "foo", "1.0.0"); + } + write_pkg(©("~npm~foo@1.0.0~peer.9"), "foo", "1.0.1"); + write_pkg(&store.join("node_modules/foo"), "foo", "1.0.0"); + let dependent = store.join("~npm~dep@1.0.0/node_modules"); + write_pkg(&dependent.join("dep"), "dep", "1.0.0"); + std::os::unix::fs::symlink(&twins[0], dependent.join("foo")).unwrap(); + std::os::unix::fs::symlink(&primary, nm.join("foo")).unwrap(); + + for start in [primary.clone(), nm.join("foo")] { + let mut got = find_store_peer_variant_copies(&start).await; + got.sort(); + let mut want = twins.to_vec(); + want.sort(); + assert_eq!(got, want, "from {}", start.display()); + } + } + + /// D19: a vendored copy's `.socket/vendor/npm///node_modules` + /// is never a crawl root (hidden dirs are skipped), so the only + /// inventory entry is the importer link that points at it. + #[cfg(unix)] + #[tokio::test] + async fn test_socket_vendor_node_modules_is_never_crawled() { + let tmp = tempfile::tempdir().unwrap(); + let vendored = tmp.path().join( + ".socket/vendor/npm/0b6f8a1e-2c3d-4e5f-8a9b-0c1d2e3f4a5b/left-pad-1.3.0/node_modules/left-pad", + ); + write_pkg(&vendored, "left-pad", "1.3.0"); + write_pkg(&vendored.join("node_modules/inner"), "inner", "1.0.0"); + let nm = tmp.path().join("node_modules"); + std::fs::create_dir_all(nm.join(".vlt")).unwrap(); + std::os::unix::fs::symlink(&vendored, nm.join("left-pad")).unwrap(); + let options = CrawlerOptions { + cwd: tmp.path().to_path_buf(), + global: false, + global_prefix: None, + }; + assert_eq!( + NpmCrawler::new() + .get_node_modules_paths(&options) + .await + .unwrap(), + vec![nm.clone()] + ); + let scanned: Vec<(String, PathBuf)> = NpmCrawler::new() + .crawl_all(&options) + .await + .into_iter() + .map(|p| (p.purl, p.path)) + .collect(); + assert_eq!( + scanned, + vec![("pkg:npm/left-pad@1.3.0".to_string(), nm.join("left-pad"))] + ); + } } diff --git a/crates/socket-patch-core/src/crawlers/pkg_managers.rs b/crates/socket-patch-core/src/crawlers/pkg_managers.rs index 7498a56c..f2528a5b 100644 --- a/crates/socket-patch-core/src/crawlers/pkg_managers.rs +++ b/crates/socket-patch-core/src/crawlers/pkg_managers.rs @@ -1,5 +1,6 @@ //! Detect which Node.js package manager produced the layout in a -//! project root (`npm`, `pnpm`, `yarn` classic, or yarn-berry PnP). +//! project root (`npm`, `pnpm`, `vlt`, `bun`, `yarn` classic, or yarn-berry +//! PnP). //! //! The apply pipeline cares about this for two reasons: //! @@ -21,6 +22,12 @@ //! move is to refuse with a clear error and point the user at //! `yarn patch `. //! +//! vlt keeps every installed package in a per-project store +//! (`node_modules/.vlt//node_modules/`, hardlinked from vlt's +//! machine-wide cache on Linux) and links importers into it, which is the +//! pnpm situation again: the rename-over write keeps the shared cache +//! untouched and the detector only drives the CLI's notice. +//! //! Classic yarn (`yarn.lock` + a real `node_modules/`) behaves like //! npm at the filesystem level, so no special handling is needed. @@ -51,6 +58,11 @@ pub enum NpmPkgManager { /// The operator gets a heads-up event so it's clear which package /// manager the patch landed against. Bun, + /// vlt install state: the `node_modules/.vlt/` store directory or the + /// hidden `node_modules/.vlt-lock.json`. Every package lives in the + /// store, one real copy per DepID; a committed `vlt-lock.json` alone + /// does not count, since another manager may have installed the tree. + Vlt, /// No discernible package manager — empty or non-Node project. Unknown, } @@ -65,13 +77,17 @@ pub enum NpmPkgManager { /// unless the tree is pnpm's own `node-linker=pnp` layout (see /// [`pnpm_pnp_layout`]), which also writes a `.pnp.cjs` but keeps /// real package dirs in the pnpm virtual store → pnpm. -/// 2. `bun.lock` or `bun.lockb` (+ `node_modules/`) → bun. -/// 3. `node_modules/.modules.yaml` or `node_modules/.pnpm/` → pnpm. -/// 4. `yarn.lock` (without PnP markers) + `node_modules/` → yarn classic. -/// 5. `node_modules/` exists → npm. -/// 6. Otherwise → unknown. +/// 2. `node_modules/.vlt/` is a directory, or `node_modules/.vlt-lock.json` +/// is a file → vlt. +/// 3. `bun.lock` or `bun.lockb` (+ `node_modules/`) → bun. +/// 4. `node_modules/.modules.yaml` or `node_modules/.pnpm/` → pnpm. +/// 5. `yarn.lock` (without PnP markers) + `node_modules/` → yarn classic. +/// 6. `node_modules/` exists → npm. +/// 7. Otherwise → unknown. /// -/// Bun comes before pnpm in the precedence because bun's isolated +/// vlt wins over every other lockfile or store marker: its install state +/// only exists after a vlt install, while a sibling `bun.lock`, +/// `pnpm-lock.yaml` or `yarn.lock` may be stale. Bun comes before pnpm in the precedence because bun's isolated /// linker (v1.3.2+ default) populates `node_modules/.bun/` which /// superficially resembles pnpm's `.pnpm/` content store. The /// lockfile filename disambiguates cleanly. @@ -101,7 +117,17 @@ pub fn detect_npm_pkg_manager(project_root: &Path) -> NpmPkgManager { return NpmPkgManager::YarnBerryPnP; } - // 2. bun — `bun.lock` (text, current default in v1.2+) or + if project_root + .join(crate::constants::npm_family::VLT_STORE_DIR) + .is_dir() + || project_root + .join(crate::constants::npm_family::VLT_HIDDEN_LOCK_REL) + .is_file() + { + return NpmPkgManager::Vlt; + } + + // 3. bun — `bun.lock` (text, current default in v1.2+) or // `bun.lockb` (binary, legacy). Like the yarn-classic check // below, we require `node_modules/` to actually exist — // a bare lockfile without an install is a fresh checkout. @@ -112,12 +138,12 @@ pub fn detect_npm_pkg_manager(project_root: &Path) -> NpmPkgManager { return NpmPkgManager::Bun; } - // 3. pnpm — markers live inside node_modules/. + // 4. pnpm — markers live inside node_modules/. if node_modules.join(".modules.yaml").is_file() || node_modules.join(".pnpm").is_dir() { return NpmPkgManager::Pnpm; } - // 4. yarn classic — yarn.lock + node_modules. We only return + // 5. yarn classic — yarn.lock + node_modules. We only return // YarnClassic if node_modules actually exists, because a bare // yarn.lock without node_modules is a fresh checkout where // nothing has been installed yet. @@ -125,7 +151,7 @@ pub fn detect_npm_pkg_manager(project_root: &Path) -> NpmPkgManager { return NpmPkgManager::YarnClassic; } - // 5. npm — any node_modules/ at all. + // 6. npm — any node_modules/ at all. if node_modules.is_dir() { return NpmPkgManager::Npm; } @@ -521,4 +547,89 @@ mod tests { std::fs::write(d.path().join("yarn.lock"), "").unwrap(); assert_eq!(detect_npm_pkg_manager(d.path()), NpmPkgManager::YarnClassic); } + + #[test] + fn vlt_via_store_dir() { + let d = tempfile::tempdir().unwrap(); + std::fs::create_dir_all(d.path().join("node_modules/.vlt")).unwrap(); + assert_eq!(detect_npm_pkg_manager(d.path()), NpmPkgManager::Vlt); + } + + #[test] + fn vlt_via_hidden_lock() { + let d = tempfile::tempdir().unwrap(); + std::fs::create_dir_all(d.path().join("node_modules")).unwrap(); + std::fs::write(d.path().join("node_modules/.vlt-lock.json"), "{}").unwrap(); + assert_eq!(detect_npm_pkg_manager(d.path()), NpmPkgManager::Vlt); + } + + /// A committed `vlt-lock.json` is not install state: the tree beside it + /// was installed by something else (here npm), or not at all. + #[test] + fn vlt_lockfile_without_vlt_install_state_falls_through() { + let d = tempfile::tempdir().unwrap(); + std::fs::write(d.path().join("vlt-lock.json"), "{}").unwrap(); + assert_eq!(detect_npm_pkg_manager(d.path()), NpmPkgManager::Unknown); + + std::fs::create_dir_all(d.path().join("node_modules")).unwrap(); + assert_eq!(detect_npm_pkg_manager(d.path()), NpmPkgManager::Npm); + + std::fs::create_dir_all(d.path().join("node_modules/.pnpm")).unwrap(); + assert_eq!(detect_npm_pkg_manager(d.path()), NpmPkgManager::Pnpm); + } + + #[test] + fn yarn_berry_pnp_priority_over_vlt() { + let d = tempfile::tempdir().unwrap(); + std::fs::write(d.path().join(".pnp.cjs"), "").unwrap(); + std::fs::create_dir_all(d.path().join("node_modules/.vlt")).unwrap(); + std::fs::write(d.path().join("node_modules/.vlt-lock.json"), "{}").unwrap(); + assert_eq!( + detect_npm_pkg_manager(d.path()), + NpmPkgManager::YarnBerryPnP + ); + } + + /// vlt install state outranks every sibling lockfile and store marker, + /// each of which may be left over from another manager. + #[test] + fn vlt_priority_over_bun_pnpm_yarn_npm() { + for marker in ["node_modules/.vlt", "node_modules/.vlt-lock.json"] { + let d = tempfile::tempdir().unwrap(); + std::fs::create_dir_all(d.path().join("node_modules/.pnpm")).unwrap(); + std::fs::write(d.path().join("node_modules/.modules.yaml"), "").unwrap(); + for lock in ["bun.lock", "bun.lockb", "yarn.lock", "package-lock.json"] { + std::fs::write(d.path().join(lock), "").unwrap(); + } + if marker.ends_with(".json") { + std::fs::write(d.path().join(marker), "{}").unwrap(); + } else { + std::fs::create_dir_all(d.path().join(marker)).unwrap(); + } + assert_eq!( + detect_npm_pkg_manager(d.path()), + NpmPkgManager::Vlt, + "{marker}" + ); + } + } + + #[test] + fn node_modules_as_file_is_not_misclassified_vlt() { + let d = tempfile::tempdir().unwrap(); + std::fs::write(d.path().join("node_modules"), "not a dir").unwrap(); + std::fs::write(d.path().join("vlt-lock.json"), "{}").unwrap(); + std::fs::write(d.path().join("vlt.json"), "{}").unwrap(); + assert_eq!(detect_npm_pkg_manager(d.path()), NpmPkgManager::Unknown); + } + + /// Robustness: `.vlt` as a regular file, and the hidden lock as a + /// directory, are not vlt install state. + #[test] + fn store_dir_as_file_is_not_vlt() { + let d = tempfile::tempdir().unwrap(); + std::fs::create_dir_all(d.path().join("node_modules/.vlt-lock.json")).unwrap(); + std::fs::write(d.path().join("node_modules/.vlt"), "not a dir").unwrap(); + assert_eq!(detect_npm_pkg_manager(d.path()), NpmPkgManager::Npm); + } } diff --git a/crates/socket-patch-core/src/patch/apply.rs b/crates/socket-patch-core/src/patch/apply.rs index 5072f4a0..72a81f3f 100644 --- a/crates/socket-patch-core/src/patch/apply.rs +++ b/crates/socket-patch-core/src/patch/apply.rs @@ -725,9 +725,10 @@ async fn chown_blocking( /// pipeline to per-file blobs only — equivalent to pre-2.2 behavior. /// /// For npm packages, one on-disk `pkg_path` is not necessarily the only -/// physical home of `package@version`: pnpm materializes a separate -/// virtual-store copy per peer-dependency combination -/// (`.pnpm/foo@1.0.0(react@17…)/` and `…(react@18…)/` are both real, +/// physical home of `package@version`: pnpm and vlt materialize a separate +/// store copy per peer-dependency (or vlt modifier) combination +/// (`.pnpm/foo@1.0.0(react@17…)/` and `…(react@18…)/`, or +/// `.vlt/~npm~foo@1.0.0~peer.2/` and `~peer.3/`, are all real, /// runtime-loaded dirs), and the purl-keyed resolver hands apply exactly /// one primary path. After the primary succeeds, the same verify+patch /// pipeline is re-run against every other physical copy — including when @@ -749,10 +750,10 @@ pub async fn apply_package_patch( ) -> ApplyResult { let mut result = apply_package_patch_at(package_key, pkg_path, files, sources, uuid, dry_run, policy).await; - // Only npm purls can name pnpm store copies; everything else skips the + // Only npm purls can name pnpm or vlt store copies; everything else skips the // (already cheap) discovery outright. if result.success && package_key.starts_with("pkg:npm/") { - for copy in crate::crawlers::npm_crawler::find_pnpm_peer_variant_copies(pkg_path).await { + for copy in crate::crawlers::npm_crawler::find_store_peer_variant_copies(pkg_path).await { let copy_result = apply_package_patch_at(package_key, ©, files, sources, uuid, dry_run, policy) .await; @@ -762,8 +763,8 @@ pub async fn apply_package_patch( result } -/// Merge one pnpm store copy's result into the primary's. A failed copy -/// fails the whole result with a `pnpm store copy failed to patch: …` +/// Merge one pnpm or vlt store copy's result into the primary's. A failed +/// copy fails the whole result with a `store copy failed to patch: …` /// note. A copy that patched fine but could not put file ownership back /// (`success: true, error: Some(": patched, but ownership could not /// be restored…")`) keeps `success` and appends that advisory verbatim (it @@ -780,7 +781,7 @@ fn fold_copy_result(result: &mut ApplyResult, copy: &Path, copy_result: ApplyRes } else { result.success = false; format!( - "pnpm store copy {} failed to patch: {}", + "store copy {} failed to patch: {}", copy.display(), copy_result .error @@ -3573,8 +3574,8 @@ mod tests { ); } - /// The pnpm fan-out merge: a failed copy fails the whole result with the - /// `pnpm store copy … failed to patch` note; a copy that patched fine but + /// The store fan-out merge: a failed copy fails the whole result with the + /// `store copy … failed to patch` note; a copy that patched fine but /// could not restore ownership keeps `success` and appends the advisory /// verbatim (it already names the copy's file path); a copy's `--force` /// all-skipped note is NOT carried (it describes the copy alone). @@ -3638,7 +3639,7 @@ mod tests { assert!(!primary.success); assert_eq!( primary.error.as_deref(), - Some("pnpm store copy /store/pkg@1.0.0_peer failed to patch: boom") + Some("store copy /store/pkg@1.0.0_peer failed to patch: boom") ); } } diff --git a/crates/socket-patch-core/src/patch/rollback.rs b/crates/socket-patch-core/src/patch/rollback.rs index 31526e2e..d2eb720e 100644 --- a/crates/socket-patch-core/src/patch/rollback.rs +++ b/crates/socket-patch-core/src/patch/rollback.rs @@ -351,10 +351,10 @@ pub async fn rollback_package_patch( ) -> RollbackResult { let mut result = rollback_package_patch_at(package_key, pkg_path, files, blobs_path, dry_run).await; - // Only npm purls can name pnpm store copies; everything else skips the + // Only npm purls can name pnpm or vlt store copies; everything else skips the // (already cheap) discovery outright. if result.success && package_key.starts_with("pkg:npm/") { - for copy in crate::crawlers::npm_crawler::find_pnpm_peer_variant_copies(pkg_path).await { + for copy in crate::crawlers::npm_crawler::find_store_peer_variant_copies(pkg_path).await { let copy_result = rollback_package_patch_at(package_key, ©, files, blobs_path, dry_run).await; fold_copy_result(&mut result, ©, copy_result); @@ -363,8 +363,8 @@ pub async fn rollback_package_patch( result } -/// Merge one pnpm store copy's result into the primary's. A failed copy -/// fails the whole result with a `pnpm store copy failed to roll +/// Merge one pnpm or vlt store copy's result into the primary's. A failed +/// copy fails the whole result with a `store copy failed to roll /// back: …` note; a copy that restored fine but carries an advisory /// (`success: true, error: Some(…)` — e.g. "…ownership could not be /// restored…") keeps `success` and appends the advisory verbatim, so the @@ -379,7 +379,7 @@ fn fold_copy_result(result: &mut RollbackResult, copy: &Path, copy_result: Rollb } else { result.success = false; format!( - "pnpm store copy {} failed to roll back: {}", + "store copy {} failed to roll back: {}", copy.display(), copy_result .error @@ -1996,8 +1996,8 @@ mod tests { ); } - /// The pnpm fan-out merge: a failed copy fails the whole result with the - /// `pnpm store copy … failed to roll back` note; a copy that restored fine + /// The store fan-out merge: a failed copy fails the whole result with the + /// `store copy … failed to roll back` note; a copy that restored fine /// but carries an ownership advisory keeps `success` and appends the /// advisory verbatim (it already names the copy's file path); a clean copy /// changes nothing. @@ -2046,7 +2046,7 @@ mod tests { assert!(!primary.success); assert_eq!( primary.error.as_deref(), - Some("first; pnpm store copy /store/pkg@1.0.0_peer failed to roll back: boom") + Some("first; store copy /store/pkg@1.0.0_peer failed to roll back: boom") ); } diff --git a/crates/socket-patch-core/src/update/channel.rs b/crates/socket-patch-core/src/update/channel.rs index 7e823da8..18f768dc 100644 --- a/crates/socket-patch-core/src/update/channel.rs +++ b/crates/socket-patch-core/src/update/channel.rs @@ -118,14 +118,46 @@ pub fn upgrade_hint(channel: InstallChannel) -> &'static str { /// nvm-windows' `%APPDATA%\nvm\v20.11.0\node_modules`), where /// `npm update -g` is right, or a project dependency /// (`/node_modules`), where `-g` would update some other copy and -/// leave this one alone. +/// leave this one alone. A project that vlt installed (its root holds +/// `vlt-lock.json`) upgrades through vlt, and vlx's cache dir (a project +/// whose `package.json` is named `vlx`, under `$XDG_DATA_HOME/vlt/vlx/`) +/// is refreshed by running vlx with `@latest`. pub fn upgrade_hint_for(channel: InstallChannel, canonical_exe: &Path) -> &'static str { if channel == InstallChannel::Npm && !is_global_npm_install(canonical_exe) { + let holder = outermost_node_modules_holder(canonical_exe); + if holder.is_some_and(is_vlx_cache_dir) { + return "vlx -y -- @socketsecurity/socket-patch@latest …"; + } + if holder.is_some_and(|dir| dir.join(crate::constants::npm_family::VLT_LOCK).is_file()) { + return "vlt install @socketsecurity/socket-patch@latest"; + } return "npm install @socketsecurity/socket-patch@latest"; } upgrade_hint(channel) } +/// vlx installs each package into its own project dir whose generated +/// `package.json` is named `vlx`. +fn is_vlx_cache_dir(dir: &Path) -> bool { + crate::utils::fs::read_regular_to_string_sync(&dir.join("package.json")) + .ok() + .and_then(|text| { + serde_json::from_str::(crate::package_json::detect::strip_bom(&text)) + .ok() + }) + .is_some_and(|pkg| pkg.get("name").and_then(|n| n.as_str()) == Some("vlx")) +} + +/// The directory holding the outermost `node_modules` of `path`, keeping +/// the path's own prefix (drive, root). `ancestors` walks innermost-first, +/// so the LAST `node_modules` is the outermost one. +fn outermost_node_modules_holder(path: &Path) -> Option<&Path> { + path.ancestors() + .filter(|a| a.file_name().is_some_and(|n| n == "node_modules")) + .last() + .and_then(Path::parent) +} + /// Whether the outermost `node_modules` of `path` belongs to a global /// install. The well-known global layouts (directly under `lib` on a Unix /// prefix or `npm` on Windows, or anywhere below a yarn/pnpm `global` @@ -153,14 +185,7 @@ fn is_global_npm_install(path: &Path) -> bool { { return true; } - // `ancestors` walks innermost-first, so the LAST `node_modules` is the - // outermost one; its parent keeps the path's own prefix (drive, root). - let holder = path - .ancestors() - .filter(|a| a.file_name().is_some_and(|n| n == "node_modules")) - .last() - .and_then(Path::parent); - match holder { + match outermost_node_modules_holder(path) { Some(dir) => !dir.join("package.json").is_file(), None => true, } @@ -546,6 +571,75 @@ mod tests { ); } + /// A vlt project install lives in the project's own store + /// (`node_modules/.vlt//node_modules/…`), so the outermost + /// `node_modules` holder is the project root and its `vlt-lock.json` + /// routes the hint to vlt. vlx's cache dir is also a vlt project (it + /// carries a lock too) whose generated `package.json` is named `vlx`; + /// re-running vlx with `@latest` is what refreshes it. + #[test] + fn vlt_project_and_vlx_cache_hints() { + let bin = "node_modules/.vlt/~npm~@socketsecurity+socket-patch-linux-x64@2.0.0/\ + node_modules/@socketsecurity/socket-patch-linux-x64/bin/socket-patch"; + + let project = tempfile::tempdir().unwrap(); + std::fs::write(project.path().join("package.json"), r#"{"name":"app"}"#).unwrap(); + let exe = project.path().join(bin); + assert_eq!( + upgrade_hint_for(InstallChannel::Npm, &exe), + "npm install @socketsecurity/socket-patch@latest", + "no vlt-lock.json: an npm project" + ); + std::fs::write(project.path().join("vlt-lock.json"), "{}").unwrap(); + assert_eq!( + upgrade_hint_for(InstallChannel::Npm, &exe), + "vlt install @socketsecurity/socket-patch@latest" + ); + // A lock in a nested dir between the holder and the binary is not + // the holder's. + let nested = tempfile::tempdir().unwrap(); + std::fs::write(nested.path().join("package.json"), "{}").unwrap(); + std::fs::create_dir_all(nested.path().join("node_modules/.vlt")).unwrap(); + std::fs::write(nested.path().join("node_modules/.vlt/vlt-lock.json"), "{}").unwrap(); + assert_eq!( + upgrade_hint_for(InstallChannel::Npm, &nested.path().join(bin)), + "npm install @socketsecurity/socket-patch@latest" + ); + + let data = tempfile::tempdir().unwrap(); + let vlx = data + .path() + .join("vlt/vlx/@socketsecurity+socket-patch-b040b66d"); + std::fs::create_dir_all(&vlx).unwrap(); + std::fs::write( + vlx.join("package.json"), + "\u{feff}{\"name\":\"vlx\",\"dependencies\":{}}", + ) + .unwrap(); + std::fs::write(vlx.join("vlt-lock.json"), "{}").unwrap(); + assert_eq!( + upgrade_hint_for(InstallChannel::Npm, &vlx.join(bin)), + "vlx -y -- @socketsecurity/socket-patch@latest …" + ); + // A project merely named like vlx's dir but with another name is a + // vlt project, not the vlx cache. + std::fs::write(vlx.join("package.json"), r#"{"name":"vlxx"}"#).unwrap(); + assert_eq!( + upgrade_hint_for(InstallChannel::Npm, &vlx.join(bin)), + "vlt install @socketsecurity/socket-patch@latest" + ); + // Global installs keep the global hint whatever vlt files exist. + assert_eq!( + upgrade_hint_for( + InstallChannel::Npm, + Path::new( + "/usr/local/lib/node_modules/@socketsecurity/socket-patch/bin/socket-patch" + ) + ), + "npm update -g @socketsecurity/socket-patch" + ); + } + #[cfg(windows)] #[test] fn npm_hint_windows_global_prefix() { diff --git a/crates/socket-patch-core/src/utils/fs.rs b/crates/socket-patch-core/src/utils/fs.rs index b2ef02d0..6b10102e 100644 --- a/crates/socket-patch-core/src/utils/fs.rs +++ b/crates/socket-patch-core/src/utils/fs.rs @@ -252,6 +252,32 @@ pub async fn first_symlink<'a>( None } +/// Remove the link at `path` itself — never its target. A symlink (file or +/// directory) on Unix, and a directory symlink or NTFS junction (which +/// `FileType` reports as `is_symlink()`; vlt and pnpm link packages with +/// junctions) on Windows, where the directory flavors need `remove_dir` +/// (`RemoveDirectoryW` deletes the reparse point and leaves the target) and +/// `remove_file` fails. Anything that is not a link fails with +/// `InvalidInput` and nothing is removed, so a caller that expected a link +/// never deletes a real file or directory in its place. +pub async fn remove_link(path: &Path) -> std::io::Result<()> { + let file_type = tokio::fs::symlink_metadata(path).await?.file_type(); + if !file_type.is_symlink() { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!("{} is not a link", path.display()), + )); + } + #[cfg(windows)] + { + use std::os::windows::fs::FileTypeExt as _; + if file_type.is_symlink_dir() { + return tokio::fs::remove_dir(path).await; + } + } + tokio::fs::remove_file(path).await +} + /// Return the raw `FileType` for `entry`, swallowing stat errors. /// /// Use this instead of `entry_is_dir` when the caller needs to @@ -1116,4 +1142,141 @@ mod tests { "a dangling link is still a link the rename would replace" ); } + + /// `remove_link` deletes the link and never its target; a real file or + /// directory in its place is refused and kept. + #[cfg(unix)] + #[tokio::test] + async fn remove_link_removes_only_links() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().join("store/pkg"); + tokio::fs::create_dir_all(&dir).await.unwrap(); + tokio::fs::write(dir.join("index.js"), b"x").await.unwrap(); + let file = tmp.path().join("file.txt"); + tokio::fs::write(&file, b"y").await.unwrap(); + + let dir_link = tmp.path().join("dir_link"); + tokio::fs::symlink("store/pkg", &dir_link).await.unwrap(); + let file_link = tmp.path().join("file_link"); + tokio::fs::symlink(&file, &file_link).await.unwrap(); + let dangling = tmp.path().join("dangling"); + tokio::fs::symlink(tmp.path().join("absent"), &dangling) + .await + .unwrap(); + + for link in [&dir_link, &file_link, &dangling] { + remove_link(link).await.unwrap(); + assert!(tokio::fs::symlink_metadata(link).await.is_err(), "{link:?}"); + } + assert_eq!(tokio::fs::read(dir.join("index.js")).await.unwrap(), b"x"); + assert_eq!(tokio::fs::read(&file).await.unwrap(), b"y"); + + for real in [&dir, &file] { + let err = remove_link(real).await.unwrap_err(); + assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput, "{real:?}"); + assert!(tokio::fs::symlink_metadata(real).await.is_ok(), "{real:?}"); + } + let err = remove_link(&tmp.path().join("absent")).await.unwrap_err(); + assert_eq!(err.kind(), std::io::ErrorKind::NotFound); + } + + /// Windows link shapes vlt produces: junctions (absolute targets, vlt + /// >= 1.0.0-rc.22 and pnpm) and directory symlinks (older vlt). Both + /// read as links through `entry_file_type` and `symlink_metadata`, are + /// followed by `is_dir`, report their target through `read_link`, and /// `remove_link` deletes them while the target survives. + #[cfg(windows)] + async fn assert_windows_dir_link(tmp: &Path, link: &Path, target: &Path) { + let entry = list_dir_entries(tmp) + .await + .into_iter() + .find(|e| e.path() == link) + .expect("link entry listed"); + let ft = entry_file_type(&entry).await.expect("file_type available"); + assert!(ft.is_symlink() && !ft.is_dir(), "{link:?}"); + assert!(is_dir(link).await, "is_dir follows the link"); + let meta = tokio::fs::symlink_metadata(link).await.unwrap(); + assert!(meta.file_type().is_symlink() && !meta.is_dir()); + + let read = std::fs::read_link(link).unwrap(); + let resolved = if read.is_absolute() { + read + } else { + link.parent().unwrap().join(read) + }; + assert_eq!( + std::fs::canonicalize(resolved).unwrap(), + std::fs::canonicalize(target).unwrap() + ); + assert_eq!( + std::fs::canonicalize(link).unwrap(), + std::fs::canonicalize(target).unwrap() + ); + + remove_link(link).await.unwrap(); + assert!(tokio::fs::symlink_metadata(link).await.is_err()); + assert_eq!( + tokio::fs::read(target.join("package.json")).await.unwrap(), + b"{}" + ); + } + + #[cfg(windows)] + #[tokio::test] + async fn windows_junction_is_a_link_and_remove_link_keeps_target() { + let tmp = tempfile::tempdir().unwrap(); + let target = tmp.path().join("store").join("pkg"); + std::fs::create_dir_all(&target).unwrap(); + std::fs::write(target.join("package.json"), b"{}").unwrap(); + let link = tmp.path().join("junction"); + let status = std::process::Command::new("cmd") + .args(["/C", "mklink", "/J"]) + .arg(&link) + .arg(&target) + .status() + .unwrap(); + assert!(status.success(), "mklink /J failed"); + assert!( + std::fs::read_link(&link).unwrap().is_absolute(), + "junction targets are absolute" + ); + assert_windows_dir_link(tmp.path(), &link, &target).await; + + std::fs::remove_dir_all(tmp.path().join("store")).unwrap(); + assert!(!tmp.path().join("store").exists()); + } + + #[cfg(windows)] + #[tokio::test] + async fn windows_dir_symlink_is_a_link_and_remove_link_keeps_target() { + let tmp = tempfile::tempdir().unwrap(); + let target = tmp.path().join("store").join("pkg"); + std::fs::create_dir_all(&target).unwrap(); + std::fs::write(target.join("package.json"), b"{}").unwrap(); + let link = tmp.path().join("dir_symlink"); + std::os::windows::fs::symlink_dir(&target, &link).unwrap(); + assert_windows_dir_link(tmp.path(), &link, &target).await; + } + + /// `remove_dir_all` on a tree holding a junction removes the junction + /// without traversing into its target (what deleting a vlt store entry + /// relies on). + #[cfg(windows)] + #[test] + fn windows_remove_dir_all_does_not_follow_junctions() { + let tmp = tempfile::tempdir().unwrap(); + let target = tmp.path().join("sibling").join("pkg"); + std::fs::create_dir_all(&target).unwrap(); + std::fs::write(target.join("package.json"), b"{}").unwrap(); + let entry_nm = tmp.path().join("entry").join("node_modules"); + std::fs::create_dir_all(&entry_nm).unwrap(); + let status = std::process::Command::new("cmd") + .args(["/C", "mklink", "/J"]) + .arg(entry_nm.join("pkg")) + .arg(&target) + .status() + .unwrap(); + assert!(status.success(), "mklink /J failed"); + std::fs::remove_dir_all(tmp.path().join("entry")).unwrap(); + assert_eq!(std::fs::read(target.join("package.json")).unwrap(), b"{}"); + } } diff --git a/crates/socket-patch-core/tests/covgap_crawlers_npm_crawler.rs b/crates/socket-patch-core/tests/covgap_crawlers_npm_crawler.rs index 3c668444..8aadafc9 100644 --- a/crates/socket-patch-core/tests/covgap_crawlers_npm_crawler.rs +++ b/crates/socket-patch-core/tests/covgap_crawlers_npm_crawler.rs @@ -2,14 +2,14 @@ //! never-executed skip/fallback regions of the store walkers //! (`collect_nested_node_modules`, `collect_nested_store_entries`, //! `scan_scoped_packages`) and every reject/fallback gate of -//! `find_pnpm_peer_variant_copies`. Each test stages the real on-disk shape +//! `find_store_peer_variant_copies` (pnpm and vlt stores). Each test stages the real on-disk shape //! that reaches its region and asserts resolver/scan OUTPUT, not just //! survival. Companion to `crawler_npm_e2e.rs` (helpers mirrored from //! there). use std::path::Path; -use socket_patch_core::crawlers::npm_crawler::find_pnpm_peer_variant_copies; +use socket_patch_core::crawlers::npm_crawler::find_store_peer_variant_copies; use socket_patch_core::crawlers::types::CrawlerOptions; use socket_patch_core::crawlers::NpmCrawler; @@ -188,7 +188,7 @@ async fn crawl_all_dedups_scoped_root_linked_pnpm_direct_dep_and_skips_symlink_d ); } -// ── find_pnpm_peer_variant_copies: probe gates ───────────────── +// ── find_store_peer_variant_copies: pnpm probe gates ──────────── /// All four reject/fallback gates of the peer-variant store probe, in one /// staged store: @@ -203,7 +203,7 @@ async fn crawl_all_dedups_scoped_root_linked_pnpm_direct_dep_and_skips_symlink_d #[cfg(unix)] #[tokio::test] #[serial_test::parallel] -async fn find_pnpm_peer_variant_copies_probe_gates() { +async fn find_store_peer_variant_copies_probe_gates() { use std::os::unix::fs::symlink; let tmp = tempfile::tempdir().unwrap(); @@ -258,7 +258,7 @@ async fn find_pnpm_peer_variant_copies_probe_gates() { ) .await; - let copies = find_pnpm_peer_variant_copies(&primary).await; + let copies = find_store_peer_variant_copies(&primary).await; let got: std::collections::HashSet<_> = copies.iter().cloned().collect(); let want: std::collections::HashSet<_> = [twin.clone(), undecodable_copy.clone()] @@ -277,7 +277,7 @@ async fn find_pnpm_peer_variant_copies_probe_gates() { /// still handled by the caller). #[tokio::test] #[serial_test::parallel] -async fn find_pnpm_peer_variant_copies_unreadable_primary_returns_empty() { +async fn find_store_peer_variant_copies_unreadable_primary_returns_empty() { let tmp = tempfile::tempdir().unwrap(); let nm = tmp.path().join("node_modules"); let store = nm.join(".pnpm"); @@ -294,13 +294,74 @@ async fn find_pnpm_peer_variant_copies_unreadable_primary_returns_empty() { ) .await; - let copies = find_pnpm_peer_variant_copies(&primary).await; + let copies = find_store_peer_variant_copies(&primary).await; assert!( copies.is_empty(), "unreadable primary identity ⇒ no twins reported; got {copies:?}" ); } +// ── find_store_peer_variant_copies: vlt probe gates ───────────── + +/// vlt's fan-out from a workspace member's link: the store lives beside +/// the ROOT `node_modules`, reachable only on the link's canonical chain. +/// Real copies whose DepID decodes to the primary's `name@version` are +/// returned (a `~peer.` twin); a git dependency of the same +/// `name@version` is a different artifact (its own bytes), never a +/// variant, and a store entry reached through a link is not a store entry. +#[cfg(unix)] +#[tokio::test] +#[serial_test::parallel] +async fn find_store_peer_variant_copies_vlt_member_link_and_git_copy() { + use std::os::unix::fs::symlink; + + let tmp = tempfile::tempdir().unwrap(); + let store = tmp.path().join("node_modules").join(".vlt"); + let entry = |id: &str| store.join(id).join("node_modules"); + stage_npm_pkg(&entry("~npm~foo@1.0.0~peer.2"), "foo", "1.0.0").await; + stage_npm_pkg(&entry("~npm~foo@1.0.0~peer.3"), "foo", "1.0.0").await; + stage_npm_pkg(&entry("git~github_cu+foo~v1.0.0"), "foo", "1.0.0").await; + let elsewhere = tempfile::tempdir().unwrap(); + stage_npm_pkg(&elsewhere.path().join("node_modules"), "foo", "1.0.0").await; + symlink(elsewhere.path(), store.join("~npm~foo@1.0.0~peer.4")).unwrap(); + + let member_nm = tmp.path().join("packages/a/node_modules"); + tokio::fs::create_dir_all(&member_nm).await.unwrap(); + symlink( + "../../../node_modules/.vlt/~npm~foo@1.0.0~peer.2/node_modules/foo", + member_nm.join("foo"), + ) + .unwrap(); + + let copies = find_store_peer_variant_copies(&member_nm.join("foo")).await; + assert_eq!( + copies + .iter() + .map(|p| std::fs::canonicalize(p).unwrap()) + .collect::>(), + vec![std::fs::canonicalize(entry("~npm~foo@1.0.0~peer.3").join("foo")).unwrap()], + "exactly the peer twin; got {copies:?}" + ); +} + +/// Fail-safe gate on the vlt side too: an unreadable primary identity +/// reports no twins. +#[tokio::test] +#[serial_test::parallel] +async fn find_store_peer_variant_copies_vlt_unreadable_primary_returns_empty() { + let tmp = tempfile::tempdir().unwrap(); + let store = tmp.path().join("node_modules").join(".vlt"); + let primary = store.join("~npm~foo@1.0.0~peer.2/node_modules/foo"); + tokio::fs::create_dir_all(&primary).await.unwrap(); + stage_npm_pkg( + &store.join("~npm~foo@1.0.0~peer.3").join("node_modules"), + "foo", + "1.0.0", + ) + .await; + assert!(find_store_peer_variant_copies(&primary).await.is_empty()); +} + // ── pnpm<=3 legacy store: stray node_modules + depth-1 home ──── /// Two edge shapes of the nested legacy-store walk: diff --git a/crates/socket-patch-core/tests/covgap_patch_apply.rs b/crates/socket-patch-core/tests/covgap_patch_apply.rs index b4339c17..e04849ce 100644 --- a/crates/socket-patch-core/tests/covgap_patch_apply.rs +++ b/crates/socket-patch-core/tests/covgap_patch_apply.rs @@ -1,10 +1,11 @@ -//! Coverage-gap integration tests for `patch::apply`: the pnpm -//! peer-variant copy FAILURE aggregation in `apply_package_patch`. +//! Coverage-gap integration tests for `patch::apply`: the pnpm and vlt +//! peer-variant copy FAILURE aggregation in `apply_package_patch`, and the +//! copy-on-write guard for hardlinked store files. //! //! The success half (patching/healing every twin) lives in //! `crawler_npm_e2e.rs`; these exercise the fail-closed branch — a copy //! that cannot be patched must flip the whole result to failure with a -//! "pnpm store copy ... failed to patch: ..." note, never report the CVE +//! "store copy ... failed to patch: ..." note, never report the CVE //! fixed while a physical twin stays divergent. use std::collections::HashMap; @@ -17,7 +18,7 @@ use socket_patch_core::patch::apply::{apply_package_patch, MismatchPolicy, Patch const ORIGINAL: &[u8] = b"module.exports = 'vulnerable';\n"; const PATCHED: &[u8] = b"module.exports = 'fixed';\n"; -/// Stage one pnpm store entry `.pnpm//node_modules/foo` holding a +/// Stage one store entry `//node_modules/foo` holding a /// package.json and, when `index_content` is `Some`, an `index.js`. /// Returns the staged package root. async fn stage_store_entry(store: &Path, entry: &str, index_content: Option<&[u8]>) -> PathBuf { @@ -30,23 +31,24 @@ async fn stage_store_entry(store: &Path, entry: &str, index_content: Option<&[u8 .await .unwrap(); if let Some(content) = index_content { - tokio::fs::write(pkg.join("index.js"), content).await.unwrap(); + tokio::fs::write(pkg.join("index.js"), content) + .await + .unwrap(); } pkg } /// Shared apply invocation: blob-only sources staged under `root`, one /// patched file `package/index.js` (ORIGINAL → PATCHED), Warn policy. -async fn apply_foo( - root: &Path, - primary: &Path, -) -> socket_patch_core::patch::apply::ApplyResult { +async fn apply_foo(root: &Path, primary: &Path) -> socket_patch_core::patch::apply::ApplyResult { let before_hash = compute_git_sha256_from_bytes(ORIGINAL); let after_hash = compute_git_sha256_from_bytes(PATCHED); let blobs = root.join("blobs"); tokio::fs::create_dir_all(&blobs).await.unwrap(); - tokio::fs::write(blobs.join(&after_hash), PATCHED).await.unwrap(); + tokio::fs::write(blobs.join(&after_hash), PATCHED) + .await + .unwrap(); let mut files = HashMap::new(); files.insert( @@ -77,7 +79,7 @@ async fn apply_foo( /// Fail-closed invariant: after the primary store copy patches cleanly, a /// peer-variant twin whose pre-existing file is MISSING (a hard error /// under Warn) must flip the whole result to failure and surface the -/// aggregated "pnpm store copy ... failed to patch" note — never claim +/// aggregated "store copy ... failed to patch" note — never claim /// the CVE fixed with a divergent twin left behind. #[tokio::test] #[serial_test::parallel] @@ -99,7 +101,7 @@ async fn pnpm_twin_copy_failure_fails_whole_apply_fail_closed() { ); let err = result.error.as_deref().expect("aggregated error present"); assert!( - err.contains("pnpm store copy"), + err.contains("store copy"), "error must carry the store-copy note: {err}" ); assert!( @@ -128,7 +130,7 @@ async fn pnpm_twin_copy_failure_fails_whole_apply_fail_closed() { } /// Two failing twins: the second note must be CONCATENATED onto the first -/// (`"...; pnpm store copy ..."`), naming both twin paths. +/// (`"...; store copy ..."`), naming both twin paths. #[tokio::test] #[serial_test::parallel] async fn pnpm_multiple_twin_copy_failures_aggregate_with_semicolon() { @@ -144,7 +146,7 @@ async fn pnpm_multiple_twin_copy_failures_aggregate_with_semicolon() { assert!(!result.success, "two failed twins must fail the apply"); let err = result.error.as_deref().expect("aggregated error present"); assert!( - err.contains("; pnpm store copy"), + err.contains("; store copy"), "second failure must be joined onto the first with '; ': {err}" ); assert!( @@ -157,3 +159,89 @@ async fn pnpm_multiple_twin_copy_failures_aggregate_with_semicolon() { PATCHED ); } + +/// The vlt twin of the fail-closed invariant: a `~peer.3` copy that cannot +/// be patched fails the whole apply, naming that copy. +#[tokio::test] +#[serial_test::parallel] +async fn vlt_twin_copy_failure_fails_whole_apply_fail_closed() { + let tmp = tempfile::tempdir().unwrap(); + let store = tmp.path().join("node_modules").join(".vlt"); + + let primary = stage_store_entry(&store, "~npm~foo@1.0.0~peer.2", Some(ORIGINAL)).await; + let twin = stage_store_entry(&store, "~npm~foo@1.0.0~peer.3", None).await; + + let result = apply_foo(tmp.path(), &primary).await; + + assert!(!result.success, "error: {:?}", result.error); + let err = result.error.as_deref().expect("aggregated error present"); + assert!( + err.starts_with("store copy ") && err.contains("~peer.3"), + "the note names the failing vlt copy: {err}" + ); + assert_eq!( + tokio::fs::read(primary.join("index.js")).await.unwrap(), + PATCHED + ); + assert!(!twin.join("index.js").exists()); +} + +/// Copy-on-write security invariant (vlt 1.2.0 hardlinks store files from +/// `/store/v1//` on Linux): patching a target that shares its +/// inode with the machine-wide cache must replace the target's directory +/// entry and never write through the shared inode. The cache link keeps +/// its bytes and its inode; the target ends up on a private inode. Any +/// in-place write (open for write, truncate, append, chmod before the +/// rename) fails this test. +#[tokio::test] +#[serial_test::parallel] +async fn apply_breaks_hardlink_to_shared_store_file() { + let tmp = tempfile::tempdir().unwrap(); + let store = tmp.path().join("node_modules").join(".vlt"); + let primary = stage_store_entry(&store, "~npm~foo@1.0.0", None).await; + let cache = tmp.path().join("cache/store/v1/0a1b2c/index.js"); + tokio::fs::create_dir_all(cache.parent().unwrap()) + .await + .unwrap(); + tokio::fs::write(&cache, ORIGINAL).await.unwrap(); + std::fs::hard_link(&cache, primary.join("index.js")).unwrap(); + #[cfg(unix)] + let (cache_ino, cache_mode) = { + use std::os::unix::fs::MetadataExt as _; + let meta = std::fs::metadata(&cache).unwrap(); + assert_eq!(meta.nlink(), 2, "precondition: the target is a hardlink"); + (meta.ino(), meta.mode()) + }; + + let result = apply_foo(tmp.path(), &primary).await; + assert!(result.success, "{:?}", result.error); + + assert_eq!( + tokio::fs::read(primary.join("index.js")).await.unwrap(), + PATCHED + ); + assert_eq!( + tokio::fs::read(&cache).await.unwrap(), + ORIGINAL, + "the shared store file must never be written through" + ); + #[cfg(unix)] + { + use std::os::unix::fs::MetadataExt as _; + let cache_meta = std::fs::metadata(&cache).unwrap(); + let target_meta = std::fs::metadata(primary.join("index.js")).unwrap(); + assert_eq!(cache_meta.ino(), cache_ino, "the cache keeps its inode"); + assert_eq!(cache_meta.mode(), cache_mode, "the cache keeps its mode"); + assert_eq!( + cache_meta.nlink(), + 1, + "the target no longer links the cache" + ); + assert_ne!( + target_meta.ino(), + cache_ino, + "the target is a private inode" + ); + assert_eq!(target_meta.nlink(), 1); + } +} diff --git a/crates/socket-patch-core/tests/crawler_npm_e2e.rs b/crates/socket-patch-core/tests/crawler_npm_e2e.rs index baad09c2..d70475a9 100644 --- a/crates/socket-patch-core/tests/crawler_npm_e2e.rs +++ b/crates/socket-patch-core/tests/crawler_npm_e2e.rs @@ -2346,3 +2346,721 @@ async fn find_by_purls_resolves_bundled_only_target_via_fallback_pass() { host_nm.join("host").join("node_modules").join("leaf") ); } + +// ── vlt store (.vlt), staged from captured real layouts ──────── + +/// The eras with a captured `vlt install` layout under +/// `tests/fixtures/vlt-trees//listing.json`: legacy `··` (0.0.0-32), +/// legacy `·npm·` with `ṗ` peers (rc.14), and tilde with hashed peers +/// (1.0.10, 1.2.0). +#[cfg(unix)] +const VLT_TREES: [&str; 4] = ["0.0.0-32", "1.0.0-rc.14", "1.0.10", "1.2.0"]; + +#[cfg(unix)] +fn vlt_listing(tree: &str) -> serde_json::Value { + let path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/vlt-trees") + .join(tree) + .join("listing.json"); + serde_json::from_slice(&std::fs::read(&path).unwrap()).unwrap() +} + +/// `(path relative to the entry's node_modules, name, version)` of every +/// real package dir in a listed `node_modules`. +#[cfg(unix)] +fn listed_packages(modules: &serde_json::Value) -> Vec<(String, String, String)> { + modules + .as_object() + .unwrap() + .iter() + .filter_map(|(rel, desc)| { + let pkg = desc.get("pkg")?; + Some(( + rel.clone(), + pkg["name"].as_str().unwrap().to_string(), + pkg["version"].as_str().unwrap().to_string(), + )) + }) + .collect() +} + +/// Recreate one listed `node_modules`: real package dirs (a package.json +/// with the captured identity), other real dirs, and the captured relative +/// links, byte for byte. +#[cfg(unix)] +async fn stage_listed_modules(nm: &Path, modules: &serde_json::Value) { + tokio::fs::create_dir_all(nm).await.unwrap(); + for (rel, desc) in modules.as_object().unwrap() { + let path = nm.join(rel); + tokio::fs::create_dir_all(path.parent().unwrap()) + .await + .unwrap(); + if let Some(pkg) = desc.get("pkg") { + stage_pkg_dir( + &path, + pkg["name"].as_str().unwrap(), + pkg["version"].as_str().unwrap(), + ) + .await; + } else if let Some(target) = desc.get("link").and_then(|t| t.as_str()) { + std::os::unix::fs::symlink(target, &path).unwrap(); + } else if desc.get("dir").is_some() { + tokio::fs::create_dir_all(&path).await.unwrap(); + } else { + tokio::fs::write(&path, b"").await.unwrap(); + } + } +} + +#[cfg(unix)] +async fn stage_pkg_dir(dir: &Path, name: &str, version: &str) { + tokio::fs::create_dir_all(dir).await.unwrap(); + tokio::fs::write( + dir.join("package.json"), + format!(r#"{{"name":"{name}","version":"{version}"}}"#), + ) + .await + .unwrap(); +} + +/// Stage a captured vlt layout under `root` and return its root +/// `node_modules`: the `.vlt` store (entries, hoist dir, `vlt.json`), the +/// hidden lock, the importer links, workspace members' link-only +/// `node_modules`, and the non-store dirs importer links point at. +#[cfg(unix)] +async fn stage_vlt_tree(root: &Path, listing: &serde_json::Value) -> std::path::PathBuf { + let nm = root.join("node_modules"); + let store = nm.join(".vlt"); + tokio::fs::create_dir_all(&store).await.unwrap(); + tokio::fs::write(nm.join(".vlt-lock.json"), b"{}") + .await + .unwrap(); + for file in listing["storeFiles"].as_array().unwrap() { + tokio::fs::write(store.join(file.as_str().unwrap()), b"{}") + .await + .unwrap(); + } + stage_listed_modules(&store.join("node_modules"), &listing["hoist"]).await; + for entry in listing["store"].as_array().unwrap() { + let id = entry["id"].as_str().unwrap(); + stage_listed_modules(&store.join(id).join("node_modules"), &entry["node_modules"]).await; + } + stage_listed_modules(&nm, &listing["importers"]).await; + for (member, modules) in listing["members"].as_object().unwrap() { + stage_listed_modules(&root.join(member).join("node_modules"), modules).await; + } + for (dir, pkg) in listing["linkTargets"].as_object().unwrap() { + stage_pkg_dir( + &root.join(dir), + pkg["name"].as_str().unwrap(), + pkg["version"].as_str().unwrap(), + ) + .await; + } + nm +} + +#[cfg(unix)] +fn npm_purl(name: &str, version: &str) -> String { + format!("pkg:npm/{name}@{version}") +} + +/// Every real package dir of a staged listing, keyed by purl: the store +/// copies (`.vlt//node_modules/`) and the non-store link +/// targets (`file:` directory deps, workspace members). +#[cfg(unix)] +fn vlt_real_copies( + root: &Path, + listing: &serde_json::Value, +) -> std::collections::BTreeMap> { + let store = root.join("node_modules/.vlt"); + let mut out: std::collections::BTreeMap> = + std::collections::BTreeMap::new(); + for entry in listing["store"].as_array().unwrap() { + let entry_nm = store + .join(entry["id"].as_str().unwrap()) + .join("node_modules"); + for (rel, name, version) in listed_packages(&entry["node_modules"]) { + out.entry(npm_purl(&name, &version)) + .or_default() + .push(entry_nm.join(rel)); + } + } + for (dir, pkg) in listing["linkTargets"].as_object().unwrap() { + out.entry(npm_purl( + pkg["name"].as_str().unwrap(), + pkg["version"].as_str().unwrap(), + )) + .or_default() + .push(root.join(dir)); + } + out +} + +/// The importer-root key an importer link for `purl` is installed under, +/// when its key IS the package name (aliases like `lp-alias` are not). +#[cfg(unix)] +fn vlt_importer_key(listing: &serde_json::Value, root: &Path, purl: &str) -> Option { + let nm = root.join("node_modules"); + listing["importers"] + .as_object() + .unwrap() + .keys() + .find(|key| { + let Ok(text) = std::fs::read_to_string(nm.join(key).join("package.json")) else { + return false; + }; + let pkg: serde_json::Value = serde_json::from_str(&text).unwrap(); + let (name, version) = ( + pkg["name"].as_str().unwrap(), + pkg["version"].as_str().unwrap(), + ); + name == key.as_str() && npm_purl(name, version) == purl + }) + .cloned() +} + +#[cfg(unix)] +fn canonical(path: &Path) -> std::path::PathBuf { + std::fs::canonicalize(path).unwrap_or_else(|e| panic!("{}: {e}", path.display())) +} + +/// Every installed package of every captured era resolves: direct deps at +/// their importer-root link (BFS root-first), transitive-only packages +/// (`ms` under `debug`, the whole git dependency's dev tree) at their store +/// copy, aliases (`lp-alias` → `left-pad@1.1.3`) through the store entry +/// named after the real package, and each resolved path is a real copy of +/// exactly that `name@version`. +#[cfg(unix)] +#[tokio::test] +#[serial_test::parallel] +async fn find_by_purls_resolves_vlt_store_transitives() { + for tree in VLT_TREES { + let tmp = tempfile::tempdir().unwrap(); + let listing = vlt_listing(tree); + let nm = stage_vlt_tree(tmp.path(), &listing).await; + let copies = vlt_real_copies(tmp.path(), &listing); + let purls: Vec = copies.keys().cloned().collect(); + assert!(purls.len() > 200, "{tree}: the capture is the full tree"); + + let result = NpmCrawler.find_by_purls(&nm, &purls).await.unwrap(); + for purl in &purls { + let found = result + .get(purl) + .unwrap_or_else(|| panic!("{tree}: {purl} must resolve")); + let allowed: Vec<_> = copies[purl].iter().map(|p| canonical(p)).collect(); + for pkg in found { + assert!( + allowed.contains(&canonical(&pkg.path)), + "{tree}: {purl} resolved to {} which is no copy of it", + pkg.path.display() + ); + } + if let Some(key) = vlt_importer_key(&listing, tmp.path(), purl) { + assert_eq!( + found[0].path, + nm.join(&key), + "{tree}: the importer link wins for {purl}" + ); + } + } + let ms = &result["pkg:npm/ms@2.1.3"]; + assert!( + ms.iter().any(|p| p + .path + .strip_prefix(nm.join(".vlt")) + .is_ok_and(|rel| rel.to_string_lossy().contains("debug"))), + "{tree}: the modifier-extra ms entry must resolve; got {ms:?}" + ); + let alias = &result["pkg:npm/left-pad@1.1.3"]; + assert_eq!( + alias[0].path.parent().unwrap().parent().unwrap().parent(), + Some(nm.join(".vlt").as_path()), + "{tree}: the alias resolves through its own store entry" + ); + } +} + +/// Scan twin: `crawl_all` inventories every package of every captured era +/// exactly once. Importer links win the `seen` dedup at their importer +/// path, everything else is recorded at its REAL store dir (a dependency +/// link inside another entry never double-counts), and the internal hoist +/// dir never contributes a path. +#[cfg(unix)] +#[tokio::test] +#[serial_test::parallel] +async fn crawl_all_inventories_vlt_store_exactly_once() { + for tree in VLT_TREES { + let tmp = tempfile::tempdir().unwrap(); + let listing = vlt_listing(tree); + let nm = stage_vlt_tree(tmp.path(), &listing).await; + let copies = vlt_real_copies(tmp.path(), &listing); + + let result = NpmCrawler.crawl_all(&options_at(tmp.path())).await; + let mut purls: Vec<&str> = result.iter().map(|p| p.purl.as_str()).collect(); + purls.sort_unstable(); + let before = purls.len(); + purls.dedup(); + assert_eq!(before, purls.len(), "{tree}: every purl once"); + assert_eq!( + purls, + copies.keys().map(String::as_str).collect::>(), + "{tree}: exactly the installed packages" + ); + + let importer_links: Vec = listing["importers"] + .as_object() + .unwrap() + .keys() + .map(|key| nm.join(key)) + .collect(); + for pkg in &result { + assert!( + !pkg.path.starts_with(nm.join(".vlt/node_modules")), + "{tree}: the hoist dir is never scanned: {}", + pkg.path.display() + ); + if importer_links.contains(&pkg.path) { + continue; + } + assert!( + copies[&pkg.purl].contains(&pkg.path), + "{tree}: {} must be recorded at a real copy, got {}", + pkg.purl, + pkg.path.display() + ); + } + } +} + +/// Store entries whose DepID carries an extra (a modifier `%3Aroot…` / +/// `~_croot…`, a legacy `ṗ` peer `%E1%B9%97%3A`, a hashed `~peer.`) +/// decode to their package and resolve for a transitive-only target. +#[cfg(unix)] +#[tokio::test] +#[serial_test::parallel] +async fn find_by_purls_resolves_vlt_store_entry_with_peer_extra() { + for tree in VLT_TREES { + let tmp = tempfile::tempdir().unwrap(); + let listing = vlt_listing(tree); + let nm = stage_vlt_tree(tmp.path(), &listing).await; + let delimiter = if listing["lockfileVersion"] == 1 { + '~' + } else { + '·' + }; + + let mut checked = 0; + for entry in listing["store"].as_array().unwrap() { + let id = entry["id"].as_str().unwrap(); + if !id.starts_with(delimiter) || id.split(delimiter).count() != 4 { + continue; + } + for (rel, name, version) in listed_packages(&entry["node_modules"]) { + let purl = npm_purl(&name, &version); + if vlt_importer_key(&listing, tmp.path(), &purl).is_some() { + continue; + } + let result = NpmCrawler + .find_by_purls(&nm, std::slice::from_ref(&purl)) + .await + .unwrap(); + let want = nm.join(".vlt").join(id).join("node_modules").join(rel); + assert!( + result + .get(&purl) + .is_some_and(|found| found.iter().any(|p| p.path == want)), + "{tree}: {purl} must resolve to {}; got {result:?}", + want.display() + ); + checked += 1; + } + } + assert!( + checked > 0, + "{tree}: the capture holds extra-bearing entries" + ); + } +} + +/// git, remote and `file:` tarball store entries (`git~…`, `remote·…`, +/// `file~vendor+ms-2.1.2.tgz`) and a registry-shaped id that does not +/// decode (`%ZZ`) reveal no identity from their names, so they stay +/// probeable and the package inside resolves by its package.json. +#[cfg(unix)] +#[tokio::test] +#[serial_test::parallel] +async fn find_by_purls_probes_undecodable_vlt_store_entry() { + for tree in VLT_TREES { + let tmp = tempfile::tempdir().unwrap(); + let listing = vlt_listing(tree); + let nm = stage_vlt_tree(tmp.path(), &listing).await; + let store = nm.join(".vlt"); + let bad = store.join("·npm·wanted%ZZ@1.0.0").join("node_modules"); + stage_pkg_dir(&bad.join("wanted"), "wanted", "1.0.0").await; + + let find = |purl: &'static str| { + let nm = nm.clone(); + async move { + NpmCrawler + .find_by_purls(&nm, &[purl.to_string()]) + .await + .unwrap() + .remove(purl) + .unwrap_or_default() + } + }; + let remote = find("pkg:npm/left-pad@1.2.0").await; + assert!( + remote + .iter() + .any(|p| p.path.to_string_lossy().contains("remote")), + "{tree}: the remote tarball entry must be probed; got {remote:?}" + ); + let file = find("pkg:npm/ms@2.1.2").await; + assert!( + file.iter() + .any(|p| p.path.to_string_lossy().contains("ms-2.1.2.tgz")), + "{tree}: the file: tarball entry must be probed; got {file:?}" + ); + let wanted = find("pkg:npm/wanted@1.0.0").await; + assert_eq!( + wanted.iter().map(|p| p.path.clone()).collect::>(), + vec![bad.join("wanted")], + "{tree}: an undecodable id stays probeable" + ); + } +} + +/// What the traversal must never read inside `.vlt`: the hoist dir +/// `node_modules` (links plus real `@scope` dirs), the `vlt.json` file, +/// `.VLT.DELETE..` rollback staging, an entry dir without its +/// own `node_modules`, an entry reached through a link, and an entry's +/// `.bin`. Decoy packages sit in each; none may resolve or be scanned. +#[cfg(unix)] +#[tokio::test] +#[serial_test::parallel] +async fn find_by_purls_and_crawl_all_skip_vlt_hoist_dir_and_vlt_json_and_delete_staging() { + let tmp = tempfile::tempdir().unwrap(); + let listing = vlt_listing("1.2.0"); + let nm = stage_vlt_tree(tmp.path(), &listing).await; + let store = nm.join(".vlt"); + assert!(store.join("vlt.json").is_file()); + assert!(store.join("node_modules/@babel").is_dir()); + + stage_pkg_dir( + &store.join("node_modules/hoist-decoy"), + "hoist-decoy", + "9.9.9", + ) + .await; + stage_pkg_dir( + &store.join("node_modules/@babel/scoped-decoy"), + "@babel/scoped-decoy", + "9.9.9", + ) + .await; + stage_pkg_dir( + &store.join(".VLT.DELETE.4f2a.~npm~ghost@1.0.0/node_modules/ghost"), + "ghost", + "1.0.0", + ) + .await; + stage_pkg_dir(&store.join("~npm~bare@1.0.0/bare"), "bare", "1.0.0").await; + let outside = tempfile::tempdir().unwrap(); + stage_pkg_dir( + &outside.path().join("node_modules/linked"), + "linked", + "1.0.0", + ) + .await; + std::os::unix::fs::symlink(outside.path(), store.join("~npm~linked@1.0.0")).unwrap(); + stage_pkg_dir( + &store.join("~npm~left-pad@1.3.0/node_modules/.bin/bin-decoy"), + "bin-decoy", + "9.9.9", + ) + .await; + + let decoys = [ + "pkg:npm/hoist-decoy@9.9.9", + "pkg:npm/@babel/scoped-decoy@9.9.9", + "pkg:npm/ghost@1.0.0", + "pkg:npm/bare@1.0.0", + "pkg:npm/linked@1.0.0", + "pkg:npm/bin-decoy@9.9.9", + ]; + let purls: Vec = decoys.iter().map(|p| p.to_string()).collect(); + let result = NpmCrawler.find_by_purls(&nm, &purls).await.unwrap(); + assert!(result.is_empty(), "no decoy resolves; got {result:?}"); + + let scanned = NpmCrawler.crawl_all(&options_at(tmp.path())).await; + for decoy in decoys { + assert!( + !scanned.iter().any(|p| p.purl == decoy), + "{decoy} must not be scanned" + ); + } + assert_eq!( + scanned.len(), + vlt_real_copies(tmp.path(), &listing).len(), + "the real packages are still all scanned" + ); +} + +/// A vlt workspace (rc.22 capture): the root store is scanned once, and +/// the members' `node_modules` (links only, found by the unchanged +/// workspace walk) add only what the root does not link: the +/// workspace-to-workspace link `@scope/b`. +#[cfg(unix)] +#[tokio::test] +#[serial_test::parallel] +async fn crawl_all_inventories_vlt_workspace_root_store_once() { + let tmp = tempfile::tempdir().unwrap(); + let listing = vlt_listing("1.0.0-rc.22-workspace"); + let nm = stage_vlt_tree(tmp.path(), &listing).await; + + let mut roots = NpmCrawler + .get_node_modules_paths(&options_at(tmp.path())) + .await + .unwrap(); + roots.sort(); + assert_eq!( + roots, + vec![ + nm.clone(), + tmp.path().join("packages/a/node_modules"), + tmp.path().join("packages/b/node_modules"), + ] + ); + + let result = NpmCrawler.crawl_all(&options_at(tmp.path())).await; + let mut purls: Vec<&str> = result.iter().map(|p| p.purl.as_str()).collect(); + purls.sort_unstable(); + let copies = vlt_real_copies(tmp.path(), &listing); + assert_eq!(purls, copies.keys().map(String::as_str).collect::>()); + let b = result + .iter() + .find(|p| p.purl == "pkg:npm/@scope/b@1.0.0") + .unwrap(); + assert_eq!(b.path, tmp.path().join("packages/a/node_modules/@scope/b")); + let react17 = result + .iter() + .find(|p| p.purl == "pkg:npm/react@17.0.2") + .unwrap(); + assert_eq!( + react17.path, + nm.join(".vlt/~npm~react@17.0.2/node_modules/react"), + "store copies are scanned once, from the root store" + ); +} + +/// The apply fixture shared by the vlt fan-out tests: one patched file +/// `package/index.js` and the blobs to apply and roll it back. +#[cfg(unix)] +struct VltPatch { + files: std::collections::HashMap, + blobs: std::path::PathBuf, +} + +#[cfg(unix)] +const VLT_ORIGINAL: &[u8] = b"module.exports = 'vulnerable';\n"; +#[cfg(unix)] +const VLT_PATCHED: &[u8] = b"module.exports = 'fixed';\n"; + +#[cfg(unix)] +async fn vlt_patch(root: &Path) -> VltPatch { + use socket_patch_core::hash::git_sha256::compute_git_sha256_from_bytes; + use socket_patch_core::manifest::schema::PatchFileInfo; + + let before_hash = compute_git_sha256_from_bytes(VLT_ORIGINAL); + let after_hash = compute_git_sha256_from_bytes(VLT_PATCHED); + let blobs = root.join("blobs"); + tokio::fs::create_dir_all(&blobs).await.unwrap(); + tokio::fs::write(blobs.join(&after_hash), VLT_PATCHED) + .await + .unwrap(); + tokio::fs::write(blobs.join(&before_hash), VLT_ORIGINAL) + .await + .unwrap(); + let files = std::collections::HashMap::from([( + "package/index.js".to_string(), + PatchFileInfo { + before_hash, + after_hash, + }, + )]); + VltPatch { files, blobs } +} + +#[cfg(unix)] +async fn vlt_apply( + purl: &str, + primary: &Path, + patch: &VltPatch, +) -> socket_patch_core::patch::apply::ApplyResult { + use socket_patch_core::patch::apply::{apply_package_patch, MismatchPolicy, PatchSources}; + let sources = PatchSources { + blobs_path: &patch.blobs, + packages_path: None, + diffs_path: None, + mem_blobs: None, + }; + apply_package_patch( + purl, + primary, + &patch.files, + &sources, + None, + false, + MismatchPolicy::Warn, + ) + .await +} + +/// Every physical store copy of one `name@version` is patched and rolled +/// back from a single primary: the rc.22 capture's real `~peer.2` / +/// `~peer.3` entries of use-sync-external-store (primary = the member's +/// link), a legacy-era `··left-pad@1.3.0` / `·npm·left-pad@1.3.0` pair +/// (vlt #1328), and a modifier twin of `ms@2.1.3` beside the captured +/// `~_croot…` entry. Every copy hardlinks one "global store" file, which +/// must never change (copy-on-write per copy). +#[cfg(unix)] +#[tokio::test] +#[serial_test::parallel] +async fn apply_and_rollback_reach_every_vlt_variant_copy() { + use socket_patch_core::patch::rollback::rollback_package_patch; + + struct Case { + tree: &'static str, + purl: &'static str, + name: &'static str, + primary: &'static str, + twins: &'static [&'static str], + } + let cases = [ + Case { + tree: "1.0.0-rc.22-workspace", + purl: "pkg:npm/use-sync-external-store@1.2.0", + name: "use-sync-external-store", + primary: "packages/a/node_modules/use-sync-external-store", + twins: &[], + }, + Case { + tree: "0.0.0-32", + purl: "pkg:npm/left-pad@1.3.0", + name: "left-pad", + primary: "node_modules/left-pad", + twins: &["·npm·left-pad@1.3.0"], + }, + Case { + tree: "1.2.0", + purl: "pkg:npm/ms@2.1.3", + name: "ms", + primary: "node_modules/.vlt/~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms/node_modules/ms", + twins: &["~npm~ms@2.1.3"], + }, + ]; + for case in cases { + let tmp = tempfile::tempdir().unwrap(); + let listing = vlt_listing(case.tree); + let nm = stage_vlt_tree(tmp.path(), &listing).await; + let version = case.purl.rsplit_once('@').unwrap().1; + for twin in case.twins { + stage_pkg_dir( + &nm.join(".vlt") + .join(twin) + .join("node_modules") + .join(case.name), + case.name, + version, + ) + .await; + } + let copies = vlt_real_copies(tmp.path(), &listing)[case.purl].clone(); + let mut copies: Vec<_> = copies + .into_iter() + .chain( + case.twins + .iter() + .map(|t| nm.join(".vlt").join(t).join("node_modules").join(case.name)), + ) + .collect(); + copies.retain(|c| c.starts_with(nm.join(".vlt"))); + assert!(copies.len() >= 2, "{}: several copies", case.tree); + + let cas = tmp.path().join("cas-index.js"); + tokio::fs::write(&cas, VLT_ORIGINAL).await.unwrap(); + for copy in &copies { + std::fs::hard_link(&cas, copy.join("index.js")).unwrap(); + } + let patch = vlt_patch(tmp.path()).await; + let primary = tmp.path().join(case.primary); + assert!(primary.join("package.json").is_file(), "{}", case.tree); + + let result = vlt_apply(case.purl, &primary, &patch).await; + assert!(result.success, "{}: {:?}", case.tree, result.error); + for copy in &copies { + assert_eq!( + tokio::fs::read(copy.join("index.js")).await.unwrap(), + VLT_PATCHED, + "{}: every store copy is patched ({})", + case.tree, + copy.display() + ); + } + assert_eq!(tokio::fs::read(&cas).await.unwrap(), VLT_ORIGINAL); + + let rb = + rollback_package_patch(case.purl, &primary, &patch.files, &patch.blobs, false).await; + assert!(rb.success, "{}: {:?}", case.tree, rb.error); + for copy in &copies { + assert_eq!( + tokio::fs::read(copy.join("index.js")).await.unwrap(), + VLT_ORIGINAL, + "{}: every store copy is restored ({})", + case.tree, + copy.display() + ); + } + assert_eq!(tokio::fs::read(&cas).await.unwrap(), VLT_ORIGINAL); + } +} + +/// Healing half: an earlier single-copy apply left the `~peer.2` primary +/// patched and the `~peer.3` twin vulnerable. The rerun reports the primary +/// AlreadyPatched and must still patch the twin. +#[cfg(unix)] +#[tokio::test] +#[serial_test::parallel] +async fn apply_heals_unpatched_vlt_twin_when_primary_already_patched() { + let tmp = tempfile::tempdir().unwrap(); + let listing = vlt_listing("1.0.0-rc.22-workspace"); + let nm = stage_vlt_tree(tmp.path(), &listing).await; + let store = nm.join(".vlt"); + let primary_copy = store + .join("~npm~use-sync-external-store@1.2.0~peer.2/node_modules/use-sync-external-store"); + let twin = store + .join("~npm~use-sync-external-store@1.2.0~peer.3/node_modules/use-sync-external-store"); + tokio::fs::write(primary_copy.join("index.js"), VLT_PATCHED) + .await + .unwrap(); + tokio::fs::write(twin.join("index.js"), VLT_ORIGINAL) + .await + .unwrap(); + let patch = vlt_patch(tmp.path()).await; + + let result = vlt_apply( + "pkg:npm/use-sync-external-store@1.2.0", + &tmp.path() + .join("packages/a/node_modules/use-sync-external-store"), + &patch, + ) + .await; + assert!(result.success, "{:?}", result.error); + assert_eq!( + tokio::fs::read(twin.join("index.js")).await.unwrap(), + VLT_PATCHED, + "an AlreadyPatched primary must not mask a still-vulnerable twin" + ); +} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-trees/0.0.0-32/README.md b/crates/socket-patch-core/tests/fixtures/vlt-trees/0.0.0-32/README.md new file mode 100644 index 00000000..98718a53 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-trees/0.0.0-32/README.md @@ -0,0 +1,21 @@ +# vlt 0.0.0-32 installed layout + +`listing.json` is the on-disk layout real vlt 0.0.0-32 produced, captured with +`scripts/capture-vlt-tree.mjs` right after a cold `vlt install` (isolated +XDG dirs and VLT_CACHE, VLT_TELEMETRY=0, LANG=C, no lockfile). Store entry +names are byte-exact; the crawler tests in `crawler_npm_e2e.rs` stage the +listing as real directories, package.json files and relative symlinks. + +Project (`package.json` dependencies): + +- "left-pad": "1.3.0", "debug": "4.3.4", "@isaacs/string-locale-compare": "1.1.0" +- "react": "18.2.0", "use-sync-external-store": "1.2.0" +- "lp-alias": "npm:left-pad@1.1.3", "semver_x": "npm:semver@7.6.0" +- "slc-git": "github:isaacs/string-locale-compare#v1.1.0" +- "lp-remote": "https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz" +- "ms-tgz": "file:./vendor/ms-2.1.2.tgz" +- (no "localdir": 0.0.0-32 cannot link a file: directory dependency) + +`vlt.json`: `{"config": {"registries": {}}, "modifiers": {":root > #debug > #ms": "2.1.3"}}`. + +The git dependency's devDependencies account for most store entries. diff --git a/crates/socket-patch-core/tests/fixtures/vlt-trees/0.0.0-32/listing.json b/crates/socket-patch-core/tests/fixtures/vlt-trees/0.0.0-32/listing.json new file mode 100644 index 00000000..559c45a5 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-trees/0.0.0-32/listing.json @@ -0,0 +1,4588 @@ +{ + "vlt": "0.0.0-32", + "lockfileVersion": 0, + "storeFiles": [ + "vlt.json" + ], + "hoist": { + "@babel/code-frame": { + "link": "../../··@babel§code-frame@7.29.7/node_modules/@babel/code-frame" + }, + "@babel/compat-data": { + "link": "../../··@babel§compat-data@7.29.7/node_modules/@babel/compat-data" + }, + "@babel/core": { + "link": "../../··@babel§core@7.29.7/node_modules/@babel/core" + }, + "@babel/generator": { + "link": "../../··@babel§generator@7.29.8/node_modules/@babel/generator" + }, + "@babel/helper-compilation-targets": { + "link": "../../··@babel§helper-compilation-targets@7.29.7/node_modules/@babel/helper-compilation-targets" + }, + "@babel/helper-globals": { + "link": "../../··@babel§helper-globals@7.29.7/node_modules/@babel/helper-globals" + }, + "@babel/helper-module-imports": { + "link": "../../··@babel§helper-module-imports@7.29.7/node_modules/@babel/helper-module-imports" + }, + "@babel/helper-module-transforms": { + "link": "../../··@babel§helper-module-transforms@7.29.7/node_modules/@babel/helper-module-transforms" + }, + "@babel/helper-string-parser": { + "link": "../../··@babel§helper-string-parser@7.29.7/node_modules/@babel/helper-string-parser" + }, + "@babel/helper-validator-identifier": { + "link": "../../··@babel§helper-validator-identifier@7.29.7/node_modules/@babel/helper-validator-identifier" + }, + "@babel/helper-validator-option": { + "link": "../../··@babel§helper-validator-option@7.29.7/node_modules/@babel/helper-validator-option" + }, + "@babel/helpers": { + "link": "../../··@babel§helpers@7.29.7/node_modules/@babel/helpers" + }, + "@babel/parser": { + "link": "../../··@babel§parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/template": { + "link": "../../··@babel§template@7.29.7/node_modules/@babel/template" + }, + "@babel/traverse": { + "link": "../../··@babel§traverse@7.29.8/node_modules/@babel/traverse" + }, + "@babel/types": { + "link": "../../··@babel§types@7.29.8/node_modules/@babel/types" + }, + "@isaacs/string-locale-compare": { + "link": "../../··@isaacs§string-locale-compare@1.1.0/node_modules/@isaacs/string-locale-compare" + }, + "@istanbuljs/load-nyc-config": { + "link": "../../··@istanbuljs§load-nyc-config@1.1.0/node_modules/@istanbuljs/load-nyc-config" + }, + "@istanbuljs/schema": { + "link": "../../··@istanbuljs§schema@0.1.6/node_modules/@istanbuljs/schema" + }, + "@jridgewell/gen-mapping": { + "link": "../../··@jridgewell§gen-mapping@0.3.13/node_modules/@jridgewell/gen-mapping" + }, + "@jridgewell/remapping": { + "link": "../../··@jridgewell§remapping@2.3.5/node_modules/@jridgewell/remapping" + }, + "@jridgewell/resolve-uri": { + "link": "../../··@jridgewell§resolve-uri@3.1.2/node_modules/@jridgewell/resolve-uri" + }, + "@jridgewell/sourcemap-codec": { + "link": "../../··@jridgewell§sourcemap-codec@1.6.0/node_modules/@jridgewell/sourcemap-codec" + }, + "@jridgewell/trace-mapping": { + "link": "../../··@jridgewell§trace-mapping@0.3.31/node_modules/@jridgewell/trace-mapping" + }, + "aggregate-error": { + "link": "../··aggregate-error@3.1.0/node_modules/aggregate-error" + }, + "ajv": { + "link": "../··ajv@6.15.0/node_modules/ajv" + }, + "ansi-regex": { + "link": "../··ansi-regex@5.0.1/node_modules/ansi-regex" + }, + "ansi-styles": { + "link": "../··ansi-styles@4.3.0/node_modules/ansi-styles" + }, + "anymatch": { + "link": "../··anymatch@3.1.3/node_modules/anymatch" + }, + "append-transform": { + "link": "../··append-transform@2.0.0/node_modules/append-transform" + }, + "archy": { + "link": "../··archy@1.0.0/node_modules/archy" + }, + "argparse": { + "link": "../··argparse@1.0.10/node_modules/argparse" + }, + "asn1": { + "link": "../··asn1@0.2.6/node_modules/asn1" + }, + "assert-plus": { + "link": "../··assert-plus@1.0.0/node_modules/assert-plus" + }, + "async-hook-domain": { + "link": "../··async-hook-domain@2.0.4/node_modules/async-hook-domain" + }, + "asynckit": { + "link": "../··asynckit@0.4.0/node_modules/asynckit" + }, + "aws-sign2": { + "link": "../··aws-sign2@0.7.0/node_modules/aws-sign2" + }, + "aws4": { + "link": "../··aws4@1.13.2/node_modules/aws4" + }, + "balanced-match": { + "link": "../··balanced-match@1.0.2/node_modules/balanced-match" + }, + "baseline-browser-mapping": { + "link": "../··baseline-browser-mapping@2.11.26/node_modules/baseline-browser-mapping" + }, + "bcrypt-pbkdf": { + "link": "../··bcrypt-pbkdf@1.0.2/node_modules/bcrypt-pbkdf" + }, + "binary-extensions": { + "link": "../··binary-extensions@2.3.0/node_modules/binary-extensions" + }, + "bind-obj-methods": { + "link": "../··bind-obj-methods@3.0.0/node_modules/bind-obj-methods" + }, + "brace-expansion": { + "link": "../··brace-expansion@1.1.21/node_modules/brace-expansion" + }, + "braces": { + "link": "../··braces@3.0.3/node_modules/braces" + }, + "browserslist": { + "link": "../··browserslist@4.29.1/node_modules/browserslist" + }, + "buffer-from": { + "link": "../··buffer-from@1.1.2/node_modules/buffer-from" + }, + "caching-transform": { + "link": "../··caching-transform@4.0.0/node_modules/caching-transform" + }, + "camelcase": { + "link": "../··camelcase@5.3.1/node_modules/camelcase" + }, + "caniuse-lite": { + "link": "../··caniuse-lite@1.0.30001812/node_modules/caniuse-lite" + }, + "caseless": { + "link": "../··caseless@0.12.0/node_modules/caseless" + }, + "chokidar": { + "link": "../··chokidar@3.6.0/node_modules/chokidar" + }, + "clean-stack": { + "link": "../··clean-stack@2.2.0/node_modules/clean-stack" + }, + "cliui": { + "link": "../··cliui@7.0.4/node_modules/cliui" + }, + "color-convert": { + "link": "../··color-convert@2.0.1/node_modules/color-convert" + }, + "color-name": { + "link": "../··color-name@1.1.4/node_modules/color-name" + }, + "color-support": { + "link": "../··color-support@1.1.3/node_modules/color-support" + }, + "combined-stream": { + "link": "../··combined-stream@1.0.8/node_modules/combined-stream" + }, + "commondir": { + "link": "../··commondir@1.0.1/node_modules/commondir" + }, + "concat-map": { + "link": "../··concat-map@0.0.1/node_modules/concat-map" + }, + "convert-source-map": { + "link": "../··convert-source-map@2.0.0/node_modules/convert-source-map" + }, + "core-util-is": { + "link": "../··core-util-is@1.0.2/node_modules/core-util-is" + }, + "coveralls": { + "link": "../··coveralls@3.1.1/node_modules/coveralls" + }, + "cross-spawn": { + "link": "../··cross-spawn@7.0.6/node_modules/cross-spawn" + }, + "dashdash": { + "link": "../··dashdash@1.14.1/node_modules/dashdash" + }, + "debug": { + "link": "../··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms/node_modules/debug" + }, + "decamelize": { + "link": "../··decamelize@1.2.0/node_modules/decamelize" + }, + "default-require-extensions": { + "link": "../··default-require-extensions@3.0.1/node_modules/default-require-extensions" + }, + "delayed-stream": { + "link": "../··delayed-stream@1.0.0/node_modules/delayed-stream" + }, + "diff": { + "link": "../··diff@4.0.4/node_modules/diff" + }, + "ecc-jsbn": { + "link": "../··ecc-jsbn@0.1.2/node_modules/ecc-jsbn" + }, + "electron-to-chromium": { + "link": "../··electron-to-chromium@1.5.439/node_modules/electron-to-chromium" + }, + "emoji-regex": { + "link": "../··emoji-regex@8.0.0/node_modules/emoji-regex" + }, + "es6-error": { + "link": "../··es6-error@4.1.1/node_modules/es6-error" + }, + "escalade": { + "link": "../··escalade@3.2.0/node_modules/escalade" + }, + "escape-string-regexp": { + "link": "../··escape-string-regexp@2.0.0/node_modules/escape-string-regexp" + }, + "esprima": { + "link": "../··esprima@4.0.1/node_modules/esprima" + }, + "events-to-array": { + "link": "../··events-to-array@1.1.2/node_modules/events-to-array" + }, + "extend": { + "link": "../··extend@3.0.2/node_modules/extend" + }, + "extsprintf": { + "link": "../··extsprintf@1.3.0/node_modules/extsprintf" + }, + "fast-deep-equal": { + "link": "../··fast-deep-equal@3.1.3/node_modules/fast-deep-equal" + }, + "fast-json-stable-stringify": { + "link": "../··fast-json-stable-stringify@2.1.0/node_modules/fast-json-stable-stringify" + }, + "fill-range": { + "link": "../··fill-range@7.1.1/node_modules/fill-range" + }, + "find-cache-dir": { + "link": "../··find-cache-dir@3.3.2/node_modules/find-cache-dir" + }, + "find-up": { + "link": "../··find-up@4.1.0/node_modules/find-up" + }, + "findit": { + "link": "../··findit@2.0.0/node_modules/findit" + }, + "foreground-child": { + "link": "../··foreground-child@2.0.0/node_modules/foreground-child" + }, + "forever-agent": { + "link": "../··forever-agent@0.6.1/node_modules/forever-agent" + }, + "form-data": { + "link": "../··form-data@2.3.3/node_modules/form-data" + }, + "fromentries": { + "link": "../··fromentries@1.3.2/node_modules/fromentries" + }, + "fs-exists-cached": { + "link": "../··fs-exists-cached@1.0.0/node_modules/fs-exists-cached" + }, + "fs.realpath": { + "link": "../··fs.realpath@1.0.0/node_modules/fs.realpath" + }, + "fsevents": { + "link": "../··fsevents@2.3.3/node_modules/fsevents" + }, + "function-loop": { + "link": "../··function-loop@2.0.1/node_modules/function-loop" + }, + "gensync": { + "link": "../··gensync@1.0.0-beta.2/node_modules/gensync" + }, + "get-caller-file": { + "link": "../··get-caller-file@2.0.5/node_modules/get-caller-file" + }, + "get-package-type": { + "link": "../··get-package-type@0.1.0/node_modules/get-package-type" + }, + "getpass": { + "link": "../··getpass@0.1.7/node_modules/getpass" + }, + "glob": { + "link": "../··glob@7.2.3/node_modules/glob" + }, + "glob-parent": { + "link": "../··glob-parent@5.1.2/node_modules/glob-parent" + }, + "graceful-fs": { + "link": "../··graceful-fs@4.2.11/node_modules/graceful-fs" + }, + "har-schema": { + "link": "../··har-schema@2.0.0/node_modules/har-schema" + }, + "har-validator": { + "link": "../··har-validator@5.1.5/node_modules/har-validator" + }, + "has-flag": { + "link": "../··has-flag@4.0.0/node_modules/has-flag" + }, + "hasha": { + "link": "../··hasha@5.2.2/node_modules/hasha" + }, + "html-escaper": { + "link": "../··html-escaper@2.0.2/node_modules/html-escaper" + }, + "http-signature": { + "link": "../··http-signature@1.2.0/node_modules/http-signature" + }, + "imurmurhash": { + "link": "../··imurmurhash@0.1.4/node_modules/imurmurhash" + }, + "indent-string": { + "link": "../··indent-string@4.0.0/node_modules/indent-string" + }, + "inflight": { + "link": "../··inflight@1.0.6/node_modules/inflight" + }, + "inherits": { + "link": "../··inherits@2.0.4/node_modules/inherits" + }, + "is-binary-path": { + "link": "../··is-binary-path@2.1.0/node_modules/is-binary-path" + }, + "is-extglob": { + "link": "../··is-extglob@2.1.1/node_modules/is-extglob" + }, + "is-fullwidth-code-point": { + "link": "../··is-fullwidth-code-point@3.0.0/node_modules/is-fullwidth-code-point" + }, + "is-glob": { + "link": "../··is-glob@4.0.3/node_modules/is-glob" + }, + "is-number": { + "link": "../··is-number@7.0.0/node_modules/is-number" + }, + "is-stream": { + "link": "../··is-stream@2.0.1/node_modules/is-stream" + }, + "is-typedarray": { + "link": "../··is-typedarray@1.0.0/node_modules/is-typedarray" + }, + "is-windows": { + "link": "../··is-windows@1.0.2/node_modules/is-windows" + }, + "isexe": { + "link": "../··isexe@2.0.0/node_modules/isexe" + }, + "isstream": { + "link": "../··isstream@0.1.2/node_modules/isstream" + }, + "istanbul-lib-coverage": { + "link": "../··istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-hook": { + "link": "../··istanbul-lib-hook@3.0.0/node_modules/istanbul-lib-hook" + }, + "istanbul-lib-instrument": { + "link": "../··istanbul-lib-instrument@4.0.3/node_modules/istanbul-lib-instrument" + }, + "istanbul-lib-processinfo": { + "link": "../··istanbul-lib-processinfo@2.0.3/node_modules/istanbul-lib-processinfo" + }, + "istanbul-lib-report": { + "link": "../··istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report" + }, + "istanbul-lib-source-maps": { + "link": "../··istanbul-lib-source-maps@4.0.1/node_modules/istanbul-lib-source-maps" + }, + "istanbul-reports": { + "link": "../··istanbul-reports@3.2.0/node_modules/istanbul-reports" + }, + "jackspeak": { + "link": "../··jackspeak@1.4.2/node_modules/jackspeak" + }, + "js-tokens": { + "link": "../··js-tokens@4.0.0/node_modules/js-tokens" + }, + "js-yaml": { + "link": "../··js-yaml@3.15.2/node_modules/js-yaml" + }, + "jsbn": { + "link": "../··jsbn@0.1.1/node_modules/jsbn" + }, + "jsesc": { + "link": "../··jsesc@3.1.0/node_modules/jsesc" + }, + "json-schema": { + "link": "../··json-schema@0.4.0/node_modules/json-schema" + }, + "json-schema-traverse": { + "link": "../··json-schema-traverse@0.4.1/node_modules/json-schema-traverse" + }, + "json-stringify-safe": { + "link": "../··json-stringify-safe@5.0.1/node_modules/json-stringify-safe" + }, + "json5": { + "link": "../··json5@2.2.3/node_modules/json5" + }, + "jsprim": { + "link": "../··jsprim@1.4.2/node_modules/jsprim" + }, + "lcov-parse": { + "link": "../··lcov-parse@1.0.0/node_modules/lcov-parse" + }, + "left-pad": { + "link": "../··left-pad@1.3.0/node_modules/left-pad" + }, + "libtap": { + "link": "../··libtap@1.4.1/node_modules/libtap" + }, + "locate-path": { + "link": "../··locate-path@5.0.0/node_modules/locate-path" + }, + "lodash.flattendeep": { + "link": "../··lodash.flattendeep@4.4.0/node_modules/lodash.flattendeep" + }, + "log-driver": { + "link": "../··log-driver@1.2.7/node_modules/log-driver" + }, + "loose-envify": { + "link": "../··loose-envify@1.4.0/node_modules/loose-envify" + }, + "lru-cache": { + "link": "../··lru-cache@6.0.0/node_modules/lru-cache" + }, + "make-dir": { + "link": "../··make-dir@4.0.0/node_modules/make-dir" + }, + "mime-db": { + "link": "../··mime-db@1.52.0/node_modules/mime-db" + }, + "mime-types": { + "link": "../··mime-types@2.1.35/node_modules/mime-types" + }, + "minimatch": { + "link": "../··minimatch@3.1.5/node_modules/minimatch" + }, + "minimist": { + "link": "../··minimist@1.2.8/node_modules/minimist" + }, + "minipass": { + "link": "../··minipass@3.3.6/node_modules/minipass" + }, + "mkdirp": { + "link": "../··mkdirp@1.0.4/node_modules/mkdirp" + }, + "ms": { + "link": "../file·vendor§ms-2.1.2.tgz/node_modules/ms" + }, + "node-preload": { + "link": "../··node-preload@0.2.1/node_modules/node-preload" + }, + "node-releases": { + "link": "../··node-releases@2.0.57/node_modules/node-releases" + }, + "normalize-path": { + "link": "../··normalize-path@3.0.0/node_modules/normalize-path" + }, + "nyc": { + "link": "../··nyc@15.1.0/node_modules/nyc" + }, + "oauth-sign": { + "link": "../··oauth-sign@0.9.0/node_modules/oauth-sign" + }, + "once": { + "link": "../··once@1.4.0/node_modules/once" + }, + "opener": { + "link": "../··opener@1.5.2/node_modules/opener" + }, + "own-or": { + "link": "../··own-or@1.0.0/node_modules/own-or" + }, + "own-or-env": { + "link": "../··own-or-env@1.0.2/node_modules/own-or-env" + }, + "p-limit": { + "link": "../··p-limit@2.3.0/node_modules/p-limit" + }, + "p-locate": { + "link": "../··p-locate@4.1.0/node_modules/p-locate" + }, + "p-map": { + "link": "../··p-map@3.0.0/node_modules/p-map" + }, + "p-try": { + "link": "../··p-try@2.2.0/node_modules/p-try" + }, + "package-hash": { + "link": "../··package-hash@4.0.0/node_modules/package-hash" + }, + "path-exists": { + "link": "../··path-exists@4.0.0/node_modules/path-exists" + }, + "path-is-absolute": { + "link": "../··path-is-absolute@1.0.1/node_modules/path-is-absolute" + }, + "path-key": { + "link": "../··path-key@3.1.1/node_modules/path-key" + }, + "performance-now": { + "link": "../··performance-now@2.1.0/node_modules/performance-now" + }, + "picocolors": { + "link": "../··picocolors@1.1.1/node_modules/picocolors" + }, + "picomatch": { + "link": "../··picomatch@2.3.2/node_modules/picomatch" + }, + "pkg-dir": { + "link": "../··pkg-dir@4.2.0/node_modules/pkg-dir" + }, + "process-on-spawn": { + "link": "../··process-on-spawn@1.1.0/node_modules/process-on-spawn" + }, + "psl": { + "link": "../··psl@1.15.0/node_modules/psl" + }, + "punycode": { + "link": "../··punycode@2.3.1/node_modules/punycode" + }, + "qs": { + "link": "../··qs@6.5.5/node_modules/qs" + }, + "react": { + "link": "../··react@18.2.0/node_modules/react" + }, + "readdirp": { + "link": "../··readdirp@3.6.0/node_modules/readdirp" + }, + "release-zalgo": { + "link": "../··release-zalgo@1.0.0/node_modules/release-zalgo" + }, + "request": { + "link": "../··request@2.88.2/node_modules/request" + }, + "require-directory": { + "link": "../··require-directory@2.1.1/node_modules/require-directory" + }, + "require-main-filename": { + "link": "../··require-main-filename@2.0.0/node_modules/require-main-filename" + }, + "resolve-from": { + "link": "../··resolve-from@5.0.0/node_modules/resolve-from" + }, + "rimraf": { + "link": "../··rimraf@3.0.2/node_modules/rimraf" + }, + "safe-buffer": { + "link": "../··safe-buffer@5.2.1/node_modules/safe-buffer" + }, + "safer-buffer": { + "link": "../··safer-buffer@2.1.2/node_modules/safer-buffer" + }, + "semver": { + "link": "../·npm·semver@7.6.0/node_modules/semver" + }, + "set-blocking": { + "link": "../··set-blocking@2.0.0/node_modules/set-blocking" + }, + "shebang-command": { + "link": "../··shebang-command@2.0.0/node_modules/shebang-command" + }, + "shebang-regex": { + "link": "../··shebang-regex@3.0.0/node_modules/shebang-regex" + }, + "signal-exit": { + "link": "../··signal-exit@3.0.7/node_modules/signal-exit" + }, + "source-map": { + "link": "../··source-map@0.6.1/node_modules/source-map" + }, + "source-map-support": { + "link": "../··source-map-support@0.5.21/node_modules/source-map-support" + }, + "spawn-wrap": { + "link": "../··spawn-wrap@2.0.0/node_modules/spawn-wrap" + }, + "sprintf-js": { + "link": "../··sprintf-js@1.0.3/node_modules/sprintf-js" + }, + "sshpk": { + "link": "../··sshpk@1.18.0/node_modules/sshpk" + }, + "stack-utils": { + "link": "../··stack-utils@2.0.6/node_modules/stack-utils" + }, + "string-width": { + "link": "../··string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../··strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "strip-bom": { + "link": "../··strip-bom@4.0.0/node_modules/strip-bom" + }, + "supports-color": { + "link": "../··supports-color@7.2.0/node_modules/supports-color" + }, + "tap": { + "link": "../··tap@15.2.3/node_modules/tap" + }, + "tap-mocha-reporter": { + "link": "../··tap-mocha-reporter@5.0.4/node_modules/tap-mocha-reporter" + }, + "tap-parser": { + "link": "../··tap-parser@11.0.2/node_modules/tap-parser" + }, + "tap-yaml": { + "link": "../··tap-yaml@1.0.2/node_modules/tap-yaml" + }, + "tcompare": { + "link": "../··tcompare@5.0.7/node_modules/tcompare" + }, + "test-exclude": { + "link": "../··test-exclude@6.0.0/node_modules/test-exclude" + }, + "to-regex-range": { + "link": "../··to-regex-range@5.0.1/node_modules/to-regex-range" + }, + "tough-cookie": { + "link": "../··tough-cookie@2.5.0/node_modules/tough-cookie" + }, + "trivial-deferred": { + "link": "../··trivial-deferred@1.1.2/node_modules/trivial-deferred" + }, + "tunnel-agent": { + "link": "../··tunnel-agent@0.6.0/node_modules/tunnel-agent" + }, + "tweetnacl": { + "link": "../··tweetnacl@0.14.5/node_modules/tweetnacl" + }, + "type-fest": { + "link": "../··type-fest@0.8.1/node_modules/type-fest" + }, + "typedarray-to-buffer": { + "link": "../··typedarray-to-buffer@3.1.5/node_modules/typedarray-to-buffer" + }, + "unicode-length": { + "link": "../··unicode-length@2.1.0/node_modules/unicode-length" + }, + "update-browserslist-db": { + "link": "../··update-browserslist-db@1.3.3/node_modules/update-browserslist-db" + }, + "uri-js": { + "link": "../··uri-js@4.4.1/node_modules/uri-js" + }, + "use-sync-external-store": { + "link": "../··use-sync-external-store@1.2.0/node_modules/use-sync-external-store" + }, + "uuid": { + "link": "../··uuid@8.3.2/node_modules/uuid" + }, + "verror": { + "link": "../··verror@1.10.0/node_modules/verror" + }, + "which": { + "link": "../··which@2.0.2/node_modules/which" + }, + "which-module": { + "link": "../··which-module@2.0.1/node_modules/which-module" + }, + "wrap-ansi": { + "link": "../··wrap-ansi@7.0.0/node_modules/wrap-ansi" + }, + "wrappy": { + "link": "../··wrappy@1.0.2/node_modules/wrappy" + }, + "write-file-atomic": { + "link": "../··write-file-atomic@3.0.3/node_modules/write-file-atomic" + }, + "y18n": { + "link": "../··y18n@4.0.3/node_modules/y18n" + }, + "yallist": { + "link": "../··yallist@4.0.0/node_modules/yallist" + }, + "yaml": { + "link": "../··yaml@1.10.3/node_modules/yaml" + }, + "yargs": { + "link": "../··yargs@15.4.1/node_modules/yargs" + }, + "yargs-parser": { + "link": "../··yargs-parser@18.1.3/node_modules/yargs-parser" + } + }, + "store": [ + { + "id": "file·vendor§ms-2.1.2.tgz", + "node_modules": { + "ms": { + "pkg": { + "name": "ms", + "version": "2.1.2" + } + } + } + }, + { + "id": "git·github%3Aisaacs§string-locale-compare·v1.1.0", + "node_modules": { + ".bin": { + "dir": true + }, + "@isaacs/string-locale-compare": { + "pkg": { + "name": "@isaacs/string-locale-compare", + "version": "1.1.0" + } + }, + "tap": { + "link": "../../··tap@15.2.3/node_modules/tap" + } + } + }, + { + "id": "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "node_modules": { + "left-pad": { + "pkg": { + "name": "left-pad", + "version": "1.2.0" + } + } + } + }, + { + "id": "·npm·left-pad@1.1.3", + "node_modules": { + "left-pad": { + "pkg": { + "name": "left-pad", + "version": "1.1.3" + } + } + } + }, + { + "id": "·npm·semver@7.6.0", + "node_modules": { + "lru-cache": { + "link": "../../··lru-cache@6.0.0/node_modules/lru-cache" + }, + "semver": { + "pkg": { + "name": "semver", + "version": "7.6.0" + } + } + } + }, + { + "id": "··@babel§code-frame@7.29.7", + "node_modules": { + "@babel/code-frame": { + "pkg": { + "name": "@babel/code-frame", + "version": "7.29.7" + } + }, + "@babel/helper-validator-identifier": { + "link": "../../../··@babel§helper-validator-identifier@7.29.7/node_modules/@babel/helper-validator-identifier" + }, + "js-tokens": { + "link": "../../··js-tokens@4.0.0/node_modules/js-tokens" + }, + "picocolors": { + "link": "../../··picocolors@1.1.1/node_modules/picocolors" + } + } + }, + { + "id": "··@babel§compat-data@7.29.7", + "node_modules": { + "@babel/compat-data": { + "pkg": { + "name": "@babel/compat-data", + "version": "7.29.7" + } + } + } + }, + { + "id": "··@babel§core@7.29.7", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/code-frame": { + "link": "../../../··@babel§code-frame@7.29.7/node_modules/@babel/code-frame" + }, + "@babel/core": { + "pkg": { + "name": "@babel/core", + "version": "7.29.7" + } + }, + "@babel/generator": { + "link": "../../../··@babel§generator@7.29.8/node_modules/@babel/generator" + }, + "@babel/helper-compilation-targets": { + "link": "../../../··@babel§helper-compilation-targets@7.29.7/node_modules/@babel/helper-compilation-targets" + }, + "@babel/helper-module-transforms": { + "link": "../../../··@babel§helper-module-transforms@7.29.7/node_modules/@babel/helper-module-transforms" + }, + "@babel/helpers": { + "link": "../../../··@babel§helpers@7.29.7/node_modules/@babel/helpers" + }, + "@babel/parser": { + "link": "../../../··@babel§parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/template": { + "link": "../../../··@babel§template@7.29.7/node_modules/@babel/template" + }, + "@babel/traverse": { + "link": "../../../··@babel§traverse@7.29.8/node_modules/@babel/traverse" + }, + "@babel/types": { + "link": "../../../··@babel§types@7.29.8/node_modules/@babel/types" + }, + "@jridgewell/remapping": { + "link": "../../../··@jridgewell§remapping@2.3.5/node_modules/@jridgewell/remapping" + }, + "convert-source-map": { + "link": "../../··convert-source-map@2.0.0/node_modules/convert-source-map" + }, + "debug": { + "link": "../../··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms/node_modules/debug" + }, + "gensync": { + "link": "../../··gensync@1.0.0-beta.2/node_modules/gensync" + }, + "json5": { + "link": "../../··json5@2.2.3/node_modules/json5" + }, + "semver": { + "link": "../../··semver@6.3.1/node_modules/semver" + } + } + }, + { + "id": "··@babel§generator@7.29.8", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/generator": { + "pkg": { + "name": "@babel/generator", + "version": "7.29.8" + } + }, + "@babel/parser": { + "link": "../../../··@babel§parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/types": { + "link": "../../../··@babel§types@7.29.8/node_modules/@babel/types" + }, + "@jridgewell/gen-mapping": { + "link": "../../../··@jridgewell§gen-mapping@0.3.13/node_modules/@jridgewell/gen-mapping" + }, + "@jridgewell/trace-mapping": { + "link": "../../../··@jridgewell§trace-mapping@0.3.31/node_modules/@jridgewell/trace-mapping" + }, + "jsesc": { + "link": "../../··jsesc@3.1.0/node_modules/jsesc" + } + } + }, + { + "id": "··@babel§helper-compilation-targets@7.29.7", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/compat-data": { + "link": "../../../··@babel§compat-data@7.29.7/node_modules/@babel/compat-data" + }, + "@babel/helper-compilation-targets": { + "pkg": { + "name": "@babel/helper-compilation-targets", + "version": "7.29.7" + } + }, + "@babel/helper-validator-option": { + "link": "../../../··@babel§helper-validator-option@7.29.7/node_modules/@babel/helper-validator-option" + }, + "browserslist": { + "link": "../../··browserslist@4.29.1/node_modules/browserslist" + }, + "lru-cache": { + "link": "../../··lru-cache@5.1.1/node_modules/lru-cache" + }, + "semver": { + "link": "../../··semver@6.3.1/node_modules/semver" + } + } + }, + { + "id": "··@babel§helper-globals@7.29.7", + "node_modules": { + "@babel/helper-globals": { + "pkg": { + "name": "@babel/helper-globals", + "version": "7.29.7" + } + } + } + }, + { + "id": "··@babel§helper-module-imports@7.29.7", + "node_modules": { + "@babel/helper-module-imports": { + "pkg": { + "name": "@babel/helper-module-imports", + "version": "7.29.7" + } + }, + "@babel/traverse": { + "link": "../../../··@babel§traverse@7.29.8/node_modules/@babel/traverse" + }, + "@babel/types": { + "link": "../../../··@babel§types@7.29.8/node_modules/@babel/types" + } + } + }, + { + "id": "··@babel§helper-module-transforms@7.29.7", + "node_modules": { + "@babel/core": { + "link": "../../../··@babel§core@7.29.7/node_modules/@babel/core" + }, + "@babel/helper-module-imports": { + "link": "../../../··@babel§helper-module-imports@7.29.7/node_modules/@babel/helper-module-imports" + }, + "@babel/helper-module-transforms": { + "pkg": { + "name": "@babel/helper-module-transforms", + "version": "7.29.7" + } + }, + "@babel/helper-validator-identifier": { + "link": "../../../··@babel§helper-validator-identifier@7.29.7/node_modules/@babel/helper-validator-identifier" + }, + "@babel/traverse": { + "link": "../../../··@babel§traverse@7.29.8/node_modules/@babel/traverse" + } + } + }, + { + "id": "··@babel§helper-string-parser@7.29.7", + "node_modules": { + "@babel/helper-string-parser": { + "pkg": { + "name": "@babel/helper-string-parser", + "version": "7.29.7" + } + } + } + }, + { + "id": "··@babel§helper-validator-identifier@7.29.7", + "node_modules": { + "@babel/helper-validator-identifier": { + "pkg": { + "name": "@babel/helper-validator-identifier", + "version": "7.29.7" + } + } + } + }, + { + "id": "··@babel§helper-validator-option@7.29.7", + "node_modules": { + "@babel/helper-validator-option": { + "pkg": { + "name": "@babel/helper-validator-option", + "version": "7.29.7" + } + } + } + }, + { + "id": "··@babel§helpers@7.29.7", + "node_modules": { + "@babel/helpers": { + "pkg": { + "name": "@babel/helpers", + "version": "7.29.7" + } + }, + "@babel/template": { + "link": "../../../··@babel§template@7.29.7/node_modules/@babel/template" + }, + "@babel/types": { + "link": "../../../··@babel§types@7.29.8/node_modules/@babel/types" + } + } + }, + { + "id": "··@babel§parser@7.29.9", + "node_modules": { + "@babel/parser": { + "pkg": { + "name": "@babel/parser", + "version": "7.29.9" + } + }, + "@babel/types": { + "link": "../../../··@babel§types@7.29.8/node_modules/@babel/types" + } + } + }, + { + "id": "··@babel§template@7.29.7", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/code-frame": { + "link": "../../../··@babel§code-frame@7.29.7/node_modules/@babel/code-frame" + }, + "@babel/parser": { + "link": "../../../··@babel§parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/template": { + "pkg": { + "name": "@babel/template", + "version": "7.29.7" + } + }, + "@babel/types": { + "link": "../../../··@babel§types@7.29.8/node_modules/@babel/types" + } + } + }, + { + "id": "··@babel§traverse@7.29.8", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/code-frame": { + "link": "../../../··@babel§code-frame@7.29.7/node_modules/@babel/code-frame" + }, + "@babel/generator": { + "link": "../../../··@babel§generator@7.29.8/node_modules/@babel/generator" + }, + "@babel/helper-globals": { + "link": "../../../··@babel§helper-globals@7.29.7/node_modules/@babel/helper-globals" + }, + "@babel/parser": { + "link": "../../../··@babel§parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/template": { + "link": "../../../··@babel§template@7.29.7/node_modules/@babel/template" + }, + "@babel/traverse": { + "pkg": { + "name": "@babel/traverse", + "version": "7.29.8" + } + }, + "@babel/types": { + "link": "../../../··@babel§types@7.29.8/node_modules/@babel/types" + }, + "debug": { + "link": "../../··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms/node_modules/debug" + } + } + }, + { + "id": "··@babel§types@7.29.8", + "node_modules": { + "@babel/helper-string-parser": { + "link": "../../../··@babel§helper-string-parser@7.29.7/node_modules/@babel/helper-string-parser" + }, + "@babel/helper-validator-identifier": { + "link": "../../../··@babel§helper-validator-identifier@7.29.7/node_modules/@babel/helper-validator-identifier" + }, + "@babel/types": { + "pkg": { + "name": "@babel/types", + "version": "7.29.8" + } + } + } + }, + { + "id": "··@isaacs§string-locale-compare@1.1.0", + "node_modules": { + "@isaacs/string-locale-compare": { + "pkg": { + "name": "@isaacs/string-locale-compare", + "version": "1.1.0" + } + } + } + }, + { + "id": "··@istanbuljs§load-nyc-config@1.1.0", + "node_modules": { + ".bin": { + "dir": true + }, + "@istanbuljs/load-nyc-config": { + "pkg": { + "name": "@istanbuljs/load-nyc-config", + "version": "1.1.0" + } + }, + "camelcase": { + "link": "../../··camelcase@5.3.1/node_modules/camelcase" + }, + "find-up": { + "link": "../../··find-up@4.1.0/node_modules/find-up" + }, + "get-package-type": { + "link": "../../··get-package-type@0.1.0/node_modules/get-package-type" + }, + "js-yaml": { + "link": "../../··js-yaml@3.15.2/node_modules/js-yaml" + }, + "resolve-from": { + "link": "../../··resolve-from@5.0.0/node_modules/resolve-from" + } + } + }, + { + "id": "··@istanbuljs§schema@0.1.6", + "node_modules": { + "@istanbuljs/schema": { + "pkg": { + "name": "@istanbuljs/schema", + "version": "0.1.6" + } + } + } + }, + { + "id": "··@jridgewell§gen-mapping@0.3.13", + "node_modules": { + "@jridgewell/gen-mapping": { + "pkg": { + "name": "@jridgewell/gen-mapping", + "version": "0.3.13" + } + }, + "@jridgewell/sourcemap-codec": { + "link": "../../../··@jridgewell§sourcemap-codec@1.6.0/node_modules/@jridgewell/sourcemap-codec" + }, + "@jridgewell/trace-mapping": { + "link": "../../../··@jridgewell§trace-mapping@0.3.31/node_modules/@jridgewell/trace-mapping" + } + } + }, + { + "id": "··@jridgewell§remapping@2.3.5", + "node_modules": { + "@jridgewell/gen-mapping": { + "link": "../../../··@jridgewell§gen-mapping@0.3.13/node_modules/@jridgewell/gen-mapping" + }, + "@jridgewell/remapping": { + "pkg": { + "name": "@jridgewell/remapping", + "version": "2.3.5" + } + }, + "@jridgewell/trace-mapping": { + "link": "../../../··@jridgewell§trace-mapping@0.3.31/node_modules/@jridgewell/trace-mapping" + } + } + }, + { + "id": "··@jridgewell§resolve-uri@3.1.2", + "node_modules": { + "@jridgewell/resolve-uri": { + "pkg": { + "name": "@jridgewell/resolve-uri", + "version": "3.1.2" + } + } + } + }, + { + "id": "··@jridgewell§sourcemap-codec@1.6.0", + "node_modules": { + "@jridgewell/sourcemap-codec": { + "pkg": { + "name": "@jridgewell/sourcemap-codec", + "version": "1.6.0" + } + } + } + }, + { + "id": "··@jridgewell§trace-mapping@0.3.31", + "node_modules": { + "@jridgewell/resolve-uri": { + "link": "../../../··@jridgewell§resolve-uri@3.1.2/node_modules/@jridgewell/resolve-uri" + }, + "@jridgewell/sourcemap-codec": { + "link": "../../../··@jridgewell§sourcemap-codec@1.6.0/node_modules/@jridgewell/sourcemap-codec" + }, + "@jridgewell/trace-mapping": { + "pkg": { + "name": "@jridgewell/trace-mapping", + "version": "0.3.31" + } + } + } + }, + { + "id": "··aggregate-error@3.1.0", + "node_modules": { + "aggregate-error": { + "pkg": { + "name": "aggregate-error", + "version": "3.1.0" + } + }, + "clean-stack": { + "link": "../../··clean-stack@2.2.0/node_modules/clean-stack" + }, + "indent-string": { + "link": "../../··indent-string@4.0.0/node_modules/indent-string" + } + } + }, + { + "id": "··ajv@6.15.0", + "node_modules": { + "ajv": { + "pkg": { + "name": "ajv", + "version": "6.15.0" + } + }, + "fast-deep-equal": { + "link": "../../··fast-deep-equal@3.1.3/node_modules/fast-deep-equal" + }, + "fast-json-stable-stringify": { + "link": "../../··fast-json-stable-stringify@2.1.0/node_modules/fast-json-stable-stringify" + }, + "json-schema-traverse": { + "link": "../../··json-schema-traverse@0.4.1/node_modules/json-schema-traverse" + }, + "uri-js": { + "link": "../../··uri-js@4.4.1/node_modules/uri-js" + } + } + }, + { + "id": "··ansi-regex@5.0.1", + "node_modules": { + "ansi-regex": { + "pkg": { + "name": "ansi-regex", + "version": "5.0.1" + } + } + } + }, + { + "id": "··ansi-styles@4.3.0", + "node_modules": { + "ansi-styles": { + "pkg": { + "name": "ansi-styles", + "version": "4.3.0" + } + }, + "color-convert": { + "link": "../../··color-convert@2.0.1/node_modules/color-convert" + } + } + }, + { + "id": "··anymatch@3.1.3", + "node_modules": { + "anymatch": { + "pkg": { + "name": "anymatch", + "version": "3.1.3" + } + }, + "normalize-path": { + "link": "../../··normalize-path@3.0.0/node_modules/normalize-path" + }, + "picomatch": { + "link": "../../··picomatch@2.3.2/node_modules/picomatch" + } + } + }, + { + "id": "··append-transform@2.0.0", + "node_modules": { + "append-transform": { + "pkg": { + "name": "append-transform", + "version": "2.0.0" + } + }, + "default-require-extensions": { + "link": "../../··default-require-extensions@3.0.1/node_modules/default-require-extensions" + } + } + }, + { + "id": "··archy@1.0.0", + "node_modules": { + "archy": { + "pkg": { + "name": "archy", + "version": "1.0.0" + } + } + } + }, + { + "id": "··argparse@1.0.10", + "node_modules": { + "argparse": { + "pkg": { + "name": "argparse", + "version": "1.0.10" + } + }, + "sprintf-js": { + "link": "../../··sprintf-js@1.0.3/node_modules/sprintf-js" + } + } + }, + { + "id": "··asn1@0.2.6", + "node_modules": { + "asn1": { + "pkg": { + "name": "asn1", + "version": "0.2.6" + } + }, + "safer-buffer": { + "link": "../../··safer-buffer@2.1.2/node_modules/safer-buffer" + } + } + }, + { + "id": "··assert-plus@1.0.0", + "node_modules": { + "assert-plus": { + "pkg": { + "name": "assert-plus", + "version": "1.0.0" + } + } + } + }, + { + "id": "··async-hook-domain@2.0.4", + "node_modules": { + "async-hook-domain": { + "pkg": { + "name": "async-hook-domain", + "version": "2.0.4" + } + } + } + }, + { + "id": "··asynckit@0.4.0", + "node_modules": { + "asynckit": { + "pkg": { + "name": "asynckit", + "version": "0.4.0" + } + } + } + }, + { + "id": "··aws-sign2@0.7.0", + "node_modules": { + "aws-sign2": { + "pkg": { + "name": "aws-sign2", + "version": "0.7.0" + } + } + } + }, + { + "id": "··aws4@1.13.2", + "node_modules": { + "aws4": { + "pkg": { + "name": "aws4", + "version": "1.13.2" + } + } + } + }, + { + "id": "··balanced-match@1.0.2", + "node_modules": { + "balanced-match": { + "pkg": { + "name": "balanced-match", + "version": "1.0.2" + } + } + } + }, + { + "id": "··baseline-browser-mapping@2.11.26", + "node_modules": { + "baseline-browser-mapping": { + "pkg": { + "name": "baseline-browser-mapping", + "version": "2.11.26" + } + } + } + }, + { + "id": "··bcrypt-pbkdf@1.0.2", + "node_modules": { + "bcrypt-pbkdf": { + "pkg": { + "name": "bcrypt-pbkdf", + "version": "1.0.2" + } + }, + "tweetnacl": { + "link": "../../··tweetnacl@0.14.5/node_modules/tweetnacl" + } + } + }, + { + "id": "··binary-extensions@2.3.0", + "node_modules": { + "binary-extensions": { + "pkg": { + "name": "binary-extensions", + "version": "2.3.0" + } + } + } + }, + { + "id": "··bind-obj-methods@3.0.0", + "node_modules": { + "bind-obj-methods": { + "pkg": { + "name": "bind-obj-methods", + "version": "3.0.0" + } + } + } + }, + { + "id": "··brace-expansion@1.1.21", + "node_modules": { + "balanced-match": { + "link": "../../··balanced-match@1.0.2/node_modules/balanced-match" + }, + "brace-expansion": { + "pkg": { + "name": "brace-expansion", + "version": "1.1.21" + } + }, + "concat-map": { + "link": "../../··concat-map@0.0.1/node_modules/concat-map" + } + } + }, + { + "id": "··braces@3.0.3", + "node_modules": { + "braces": { + "pkg": { + "name": "braces", + "version": "3.0.3" + } + }, + "fill-range": { + "link": "../../··fill-range@7.1.1/node_modules/fill-range" + } + } + }, + { + "id": "··browserslist@4.29.1", + "node_modules": { + ".bin": { + "dir": true + }, + "baseline-browser-mapping": { + "link": "../../··baseline-browser-mapping@2.11.26/node_modules/baseline-browser-mapping" + }, + "browserslist": { + "pkg": { + "name": "browserslist", + "version": "4.29.1" + } + }, + "caniuse-lite": { + "link": "../../··caniuse-lite@1.0.30001812/node_modules/caniuse-lite" + }, + "electron-to-chromium": { + "link": "../../··electron-to-chromium@1.5.439/node_modules/electron-to-chromium" + }, + "node-releases": { + "link": "../../··node-releases@2.0.57/node_modules/node-releases" + }, + "update-browserslist-db": { + "link": "../../··update-browserslist-db@1.3.3/node_modules/update-browserslist-db" + } + } + }, + { + "id": "··buffer-from@1.1.2", + "node_modules": { + "buffer-from": { + "pkg": { + "name": "buffer-from", + "version": "1.1.2" + } + } + } + }, + { + "id": "··caching-transform@4.0.0", + "node_modules": { + "caching-transform": { + "pkg": { + "name": "caching-transform", + "version": "4.0.0" + } + }, + "hasha": { + "link": "../../··hasha@5.2.2/node_modules/hasha" + }, + "make-dir": { + "link": "../../··make-dir@3.1.0/node_modules/make-dir" + }, + "package-hash": { + "link": "../../··package-hash@4.0.0/node_modules/package-hash" + }, + "write-file-atomic": { + "link": "../../··write-file-atomic@3.0.3/node_modules/write-file-atomic" + } + } + }, + { + "id": "··camelcase@5.3.1", + "node_modules": { + "camelcase": { + "pkg": { + "name": "camelcase", + "version": "5.3.1" + } + } + } + }, + { + "id": "··caniuse-lite@1.0.30001812", + "node_modules": { + "caniuse-lite": { + "pkg": { + "name": "caniuse-lite", + "version": "1.0.30001812" + } + } + } + }, + { + "id": "··caseless@0.12.0", + "node_modules": { + "caseless": { + "pkg": { + "name": "caseless", + "version": "0.12.0" + } + } + } + }, + { + "id": "··chokidar@3.6.0", + "node_modules": { + "anymatch": { + "link": "../../··anymatch@3.1.3/node_modules/anymatch" + }, + "braces": { + "link": "../../··braces@3.0.3/node_modules/braces" + }, + "chokidar": { + "pkg": { + "name": "chokidar", + "version": "3.6.0" + } + }, + "fsevents": { + "link": "../../··fsevents@2.3.3/node_modules/fsevents" + }, + "glob-parent": { + "link": "../../··glob-parent@5.1.2/node_modules/glob-parent" + }, + "is-binary-path": { + "link": "../../··is-binary-path@2.1.0/node_modules/is-binary-path" + }, + "is-glob": { + "link": "../../··is-glob@4.0.3/node_modules/is-glob" + }, + "normalize-path": { + "link": "../../··normalize-path@3.0.0/node_modules/normalize-path" + }, + "readdirp": { + "link": "../../··readdirp@3.6.0/node_modules/readdirp" + } + } + }, + { + "id": "··clean-stack@2.2.0", + "node_modules": { + "clean-stack": { + "pkg": { + "name": "clean-stack", + "version": "2.2.0" + } + } + } + }, + { + "id": "··cliui@6.0.0", + "node_modules": { + "cliui": { + "pkg": { + "name": "cliui", + "version": "6.0.0" + } + }, + "string-width": { + "link": "../../··string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../../··strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "wrap-ansi": { + "link": "../../··wrap-ansi@6.2.0/node_modules/wrap-ansi" + } + } + }, + { + "id": "··cliui@7.0.4", + "node_modules": { + "cliui": { + "pkg": { + "name": "cliui", + "version": "7.0.4" + } + }, + "string-width": { + "link": "../../··string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../../··strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "wrap-ansi": { + "link": "../../··wrap-ansi@7.0.0/node_modules/wrap-ansi" + } + } + }, + { + "id": "··color-convert@2.0.1", + "node_modules": { + "color-convert": { + "pkg": { + "name": "color-convert", + "version": "2.0.1" + } + }, + "color-name": { + "link": "../../··color-name@1.1.4/node_modules/color-name" + } + } + }, + { + "id": "··color-name@1.1.4", + "node_modules": { + "color-name": { + "pkg": { + "name": "color-name", + "version": "1.1.4" + } + } + } + }, + { + "id": "··color-support@1.1.3", + "node_modules": { + "color-support": { + "pkg": { + "name": "color-support", + "version": "1.1.3" + } + } + } + }, + { + "id": "··combined-stream@1.0.8", + "node_modules": { + "combined-stream": { + "pkg": { + "name": "combined-stream", + "version": "1.0.8" + } + }, + "delayed-stream": { + "link": "../../··delayed-stream@1.0.0/node_modules/delayed-stream" + } + } + }, + { + "id": "··commondir@1.0.1", + "node_modules": { + "commondir": { + "pkg": { + "name": "commondir", + "version": "1.0.1" + } + } + } + }, + { + "id": "··concat-map@0.0.1", + "node_modules": { + "concat-map": { + "pkg": { + "name": "concat-map", + "version": "0.0.1" + } + } + } + }, + { + "id": "··convert-source-map@1.9.0", + "node_modules": { + "convert-source-map": { + "pkg": { + "name": "convert-source-map", + "version": "1.9.0" + } + } + } + }, + { + "id": "··convert-source-map@2.0.0", + "node_modules": { + "convert-source-map": { + "pkg": { + "name": "convert-source-map", + "version": "2.0.0" + } + } + } + }, + { + "id": "··core-util-is@1.0.2", + "node_modules": { + "core-util-is": { + "pkg": { + "name": "core-util-is", + "version": "1.0.2" + } + } + } + }, + { + "id": "··coveralls@3.1.1", + "node_modules": { + ".bin": { + "dir": true + }, + "coveralls": { + "pkg": { + "name": "coveralls", + "version": "3.1.1" + } + }, + "js-yaml": { + "link": "../../··js-yaml@3.15.2/node_modules/js-yaml" + }, + "lcov-parse": { + "link": "../../··lcov-parse@1.0.0/node_modules/lcov-parse" + }, + "log-driver": { + "link": "../../··log-driver@1.2.7/node_modules/log-driver" + }, + "minimist": { + "link": "../../··minimist@1.2.8/node_modules/minimist" + }, + "request": { + "link": "../../··request@2.88.2/node_modules/request" + } + } + }, + { + "id": "··cross-spawn@7.0.6", + "node_modules": { + ".bin": { + "dir": true + }, + "cross-spawn": { + "pkg": { + "name": "cross-spawn", + "version": "7.0.6" + } + }, + "path-key": { + "link": "../../··path-key@3.1.1/node_modules/path-key" + }, + "shebang-command": { + "link": "../../··shebang-command@2.0.0/node_modules/shebang-command" + }, + "which": { + "link": "../../··which@2.0.2/node_modules/which" + } + } + }, + { + "id": "··dashdash@1.14.1", + "node_modules": { + "assert-plus": { + "link": "../../··assert-plus@1.0.0/node_modules/assert-plus" + }, + "dashdash": { + "pkg": { + "name": "dashdash", + "version": "1.14.1" + } + } + } + }, + { + "id": "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "node_modules": { + "debug": { + "pkg": { + "name": "debug", + "version": "4.3.4" + } + }, + "ms": { + "link": "../../··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms/node_modules/ms" + } + } + }, + { + "id": "··decamelize@1.2.0", + "node_modules": { + "decamelize": { + "pkg": { + "name": "decamelize", + "version": "1.2.0" + } + } + } + }, + { + "id": "··default-require-extensions@3.0.1", + "node_modules": { + "default-require-extensions": { + "pkg": { + "name": "default-require-extensions", + "version": "3.0.1" + } + }, + "strip-bom": { + "link": "../../··strip-bom@4.0.0/node_modules/strip-bom" + } + } + }, + { + "id": "··delayed-stream@1.0.0", + "node_modules": { + "delayed-stream": { + "pkg": { + "name": "delayed-stream", + "version": "1.0.0" + } + } + } + }, + { + "id": "··diff@4.0.4", + "node_modules": { + "diff": { + "pkg": { + "name": "diff", + "version": "4.0.4" + } + } + } + }, + { + "id": "··ecc-jsbn@0.1.2", + "node_modules": { + "ecc-jsbn": { + "pkg": { + "name": "ecc-jsbn", + "version": "0.1.2" + } + }, + "jsbn": { + "link": "../../··jsbn@0.1.1/node_modules/jsbn" + }, + "safer-buffer": { + "link": "../../··safer-buffer@2.1.2/node_modules/safer-buffer" + } + } + }, + { + "id": "··electron-to-chromium@1.5.439", + "node_modules": { + "electron-to-chromium": { + "pkg": { + "name": "electron-to-chromium", + "version": "1.5.439" + } + } + } + }, + { + "id": "··emoji-regex@8.0.0", + "node_modules": { + "emoji-regex": { + "pkg": { + "name": "emoji-regex", + "version": "8.0.0" + } + } + } + }, + { + "id": "··es6-error@4.1.1", + "node_modules": { + "es6-error": { + "pkg": { + "name": "es6-error", + "version": "4.1.1" + } + } + } + }, + { + "id": "··escalade@3.2.0", + "node_modules": { + "escalade": { + "pkg": { + "name": "escalade", + "version": "3.2.0" + } + } + } + }, + { + "id": "··escape-string-regexp@2.0.0", + "node_modules": { + "escape-string-regexp": { + "pkg": { + "name": "escape-string-regexp", + "version": "2.0.0" + } + } + } + }, + { + "id": "··esprima@4.0.1", + "node_modules": { + "esprima": { + "pkg": { + "name": "esprima", + "version": "4.0.1" + } + } + } + }, + { + "id": "··events-to-array@1.1.2", + "node_modules": { + "events-to-array": { + "pkg": { + "name": "events-to-array", + "version": "1.1.2" + } + } + } + }, + { + "id": "··extend@3.0.2", + "node_modules": { + "extend": { + "pkg": { + "name": "extend", + "version": "3.0.2" + } + } + } + }, + { + "id": "··extsprintf@1.3.0", + "node_modules": { + "extsprintf": { + "pkg": { + "name": "extsprintf", + "version": "1.3.0" + } + } + } + }, + { + "id": "··fast-deep-equal@3.1.3", + "node_modules": { + "fast-deep-equal": { + "pkg": { + "name": "fast-deep-equal", + "version": "3.1.3" + } + } + } + }, + { + "id": "··fast-json-stable-stringify@2.1.0", + "node_modules": { + "fast-json-stable-stringify": { + "pkg": { + "name": "fast-json-stable-stringify", + "version": "2.1.0" + } + } + } + }, + { + "id": "··fill-range@7.1.1", + "node_modules": { + "fill-range": { + "pkg": { + "name": "fill-range", + "version": "7.1.1" + } + }, + "to-regex-range": { + "link": "../../··to-regex-range@5.0.1/node_modules/to-regex-range" + } + } + }, + { + "id": "··find-cache-dir@3.3.2", + "node_modules": { + "commondir": { + "link": "../../··commondir@1.0.1/node_modules/commondir" + }, + "find-cache-dir": { + "pkg": { + "name": "find-cache-dir", + "version": "3.3.2" + } + }, + "make-dir": { + "link": "../../··make-dir@3.1.0/node_modules/make-dir" + }, + "pkg-dir": { + "link": "../../··pkg-dir@4.2.0/node_modules/pkg-dir" + } + } + }, + { + "id": "··find-up@4.1.0", + "node_modules": { + "find-up": { + "pkg": { + "name": "find-up", + "version": "4.1.0" + } + }, + "locate-path": { + "link": "../../··locate-path@5.0.0/node_modules/locate-path" + }, + "path-exists": { + "link": "../../··path-exists@4.0.0/node_modules/path-exists" + } + } + }, + { + "id": "··findit@2.0.0", + "node_modules": { + "findit": { + "pkg": { + "name": "findit", + "version": "2.0.0" + } + } + } + }, + { + "id": "··foreground-child@2.0.0", + "node_modules": { + "cross-spawn": { + "link": "../../··cross-spawn@7.0.6/node_modules/cross-spawn" + }, + "foreground-child": { + "pkg": { + "name": "foreground-child", + "version": "2.0.0" + } + }, + "signal-exit": { + "link": "../../··signal-exit@3.0.7/node_modules/signal-exit" + } + } + }, + { + "id": "··forever-agent@0.6.1", + "node_modules": { + "forever-agent": { + "pkg": { + "name": "forever-agent", + "version": "0.6.1" + } + } + } + }, + { + "id": "··form-data@2.3.3", + "node_modules": { + "asynckit": { + "link": "../../··asynckit@0.4.0/node_modules/asynckit" + }, + "combined-stream": { + "link": "../../··combined-stream@1.0.8/node_modules/combined-stream" + }, + "form-data": { + "pkg": { + "name": "form-data", + "version": "2.3.3" + } + }, + "mime-types": { + "link": "../../··mime-types@2.1.35/node_modules/mime-types" + } + } + }, + { + "id": "··fromentries@1.3.2", + "node_modules": { + "fromentries": { + "pkg": { + "name": "fromentries", + "version": "1.3.2" + } + } + } + }, + { + "id": "··fs-exists-cached@1.0.0", + "node_modules": { + "fs-exists-cached": { + "pkg": { + "name": "fs-exists-cached", + "version": "1.0.0" + } + } + } + }, + { + "id": "··fs.realpath@1.0.0", + "node_modules": { + "fs.realpath": { + "pkg": { + "name": "fs.realpath", + "version": "1.0.0" + } + } + } + }, + { + "id": "··fsevents@2.3.3", + "node_modules": { + "fsevents": { + "pkg": { + "name": "fsevents", + "version": "2.3.3" + } + } + } + }, + { + "id": "··function-loop@2.0.1", + "node_modules": { + "function-loop": { + "pkg": { + "name": "function-loop", + "version": "2.0.1" + } + } + } + }, + { + "id": "··gensync@1.0.0-beta.2", + "node_modules": { + "gensync": { + "pkg": { + "name": "gensync", + "version": "1.0.0-beta.2" + } + } + } + }, + { + "id": "··get-caller-file@2.0.5", + "node_modules": { + "get-caller-file": { + "pkg": { + "name": "get-caller-file", + "version": "2.0.5" + } + } + } + }, + { + "id": "··get-package-type@0.1.0", + "node_modules": { + "get-package-type": { + "pkg": { + "name": "get-package-type", + "version": "0.1.0" + } + } + } + }, + { + "id": "··getpass@0.1.7", + "node_modules": { + "assert-plus": { + "link": "../../··assert-plus@1.0.0/node_modules/assert-plus" + }, + "getpass": { + "pkg": { + "name": "getpass", + "version": "0.1.7" + } + } + } + }, + { + "id": "··glob-parent@5.1.2", + "node_modules": { + "glob-parent": { + "pkg": { + "name": "glob-parent", + "version": "5.1.2" + } + }, + "is-glob": { + "link": "../../··is-glob@4.0.3/node_modules/is-glob" + } + } + }, + { + "id": "··glob@7.2.3", + "node_modules": { + "fs.realpath": { + "link": "../../··fs.realpath@1.0.0/node_modules/fs.realpath" + }, + "glob": { + "pkg": { + "name": "glob", + "version": "7.2.3" + } + }, + "inflight": { + "link": "../../··inflight@1.0.6/node_modules/inflight" + }, + "inherits": { + "link": "../../··inherits@2.0.4/node_modules/inherits" + }, + "minimatch": { + "link": "../../··minimatch@3.1.5/node_modules/minimatch" + }, + "once": { + "link": "../../··once@1.4.0/node_modules/once" + }, + "path-is-absolute": { + "link": "../../··path-is-absolute@1.0.1/node_modules/path-is-absolute" + } + } + }, + { + "id": "··graceful-fs@4.2.11", + "node_modules": { + "graceful-fs": { + "pkg": { + "name": "graceful-fs", + "version": "4.2.11" + } + } + } + }, + { + "id": "··har-schema@2.0.0", + "node_modules": { + "har-schema": { + "pkg": { + "name": "har-schema", + "version": "2.0.0" + } + } + } + }, + { + "id": "··har-validator@5.1.5", + "node_modules": { + "ajv": { + "link": "../../··ajv@6.15.0/node_modules/ajv" + }, + "har-schema": { + "link": "../../··har-schema@2.0.0/node_modules/har-schema" + }, + "har-validator": { + "pkg": { + "name": "har-validator", + "version": "5.1.5" + } + } + } + }, + { + "id": "··has-flag@4.0.0", + "node_modules": { + "has-flag": { + "pkg": { + "name": "has-flag", + "version": "4.0.0" + } + } + } + }, + { + "id": "··hasha@5.2.2", + "node_modules": { + "hasha": { + "pkg": { + "name": "hasha", + "version": "5.2.2" + } + }, + "is-stream": { + "link": "../../··is-stream@2.0.1/node_modules/is-stream" + }, + "type-fest": { + "link": "../../··type-fest@0.8.1/node_modules/type-fest" + } + } + }, + { + "id": "··html-escaper@2.0.2", + "node_modules": { + "html-escaper": { + "pkg": { + "name": "html-escaper", + "version": "2.0.2" + } + } + } + }, + { + "id": "··http-signature@1.2.0", + "node_modules": { + ".bin": { + "dir": true + }, + "assert-plus": { + "link": "../../··assert-plus@1.0.0/node_modules/assert-plus" + }, + "http-signature": { + "pkg": { + "name": "http-signature", + "version": "1.2.0" + } + }, + "jsprim": { + "link": "../../··jsprim@1.4.2/node_modules/jsprim" + }, + "sshpk": { + "link": "../../··sshpk@1.18.0/node_modules/sshpk" + } + } + }, + { + "id": "··imurmurhash@0.1.4", + "node_modules": { + "imurmurhash": { + "pkg": { + "name": "imurmurhash", + "version": "0.1.4" + } + } + } + }, + { + "id": "··indent-string@4.0.0", + "node_modules": { + "indent-string": { + "pkg": { + "name": "indent-string", + "version": "4.0.0" + } + } + } + }, + { + "id": "··inflight@1.0.6", + "node_modules": { + "inflight": { + "pkg": { + "name": "inflight", + "version": "1.0.6" + } + }, + "once": { + "link": "../../··once@1.4.0/node_modules/once" + }, + "wrappy": { + "link": "../../··wrappy@1.0.2/node_modules/wrappy" + } + } + }, + { + "id": "··inherits@2.0.4", + "node_modules": { + "inherits": { + "pkg": { + "name": "inherits", + "version": "2.0.4" + } + } + } + }, + { + "id": "··is-binary-path@2.1.0", + "node_modules": { + "binary-extensions": { + "link": "../../··binary-extensions@2.3.0/node_modules/binary-extensions" + }, + "is-binary-path": { + "pkg": { + "name": "is-binary-path", + "version": "2.1.0" + } + } + } + }, + { + "id": "··is-extglob@2.1.1", + "node_modules": { + "is-extglob": { + "pkg": { + "name": "is-extglob", + "version": "2.1.1" + } + } + } + }, + { + "id": "··is-fullwidth-code-point@3.0.0", + "node_modules": { + "is-fullwidth-code-point": { + "pkg": { + "name": "is-fullwidth-code-point", + "version": "3.0.0" + } + } + } + }, + { + "id": "··is-glob@4.0.3", + "node_modules": { + "is-extglob": { + "link": "../../··is-extglob@2.1.1/node_modules/is-extglob" + }, + "is-glob": { + "pkg": { + "name": "is-glob", + "version": "4.0.3" + } + } + } + }, + { + "id": "··is-number@7.0.0", + "node_modules": { + "is-number": { + "pkg": { + "name": "is-number", + "version": "7.0.0" + } + } + } + }, + { + "id": "··is-stream@2.0.1", + "node_modules": { + "is-stream": { + "pkg": { + "name": "is-stream", + "version": "2.0.1" + } + } + } + }, + { + "id": "··is-typedarray@1.0.0", + "node_modules": { + "is-typedarray": { + "pkg": { + "name": "is-typedarray", + "version": "1.0.0" + } + } + } + }, + { + "id": "··is-windows@1.0.2", + "node_modules": { + "is-windows": { + "pkg": { + "name": "is-windows", + "version": "1.0.2" + } + } + } + }, + { + "id": "··isexe@2.0.0", + "node_modules": { + "isexe": { + "pkg": { + "name": "isexe", + "version": "2.0.0" + } + } + } + }, + { + "id": "··isstream@0.1.2", + "node_modules": { + "isstream": { + "pkg": { + "name": "isstream", + "version": "0.1.2" + } + } + } + }, + { + "id": "··istanbul-lib-coverage@3.2.2", + "node_modules": { + "istanbul-lib-coverage": { + "pkg": { + "name": "istanbul-lib-coverage", + "version": "3.2.2" + } + } + } + }, + { + "id": "··istanbul-lib-hook@3.0.0", + "node_modules": { + "append-transform": { + "link": "../../··append-transform@2.0.0/node_modules/append-transform" + }, + "istanbul-lib-hook": { + "pkg": { + "name": "istanbul-lib-hook", + "version": "3.0.0" + } + } + } + }, + { + "id": "··istanbul-lib-instrument@4.0.3", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/core": { + "link": "../../../··@babel§core@7.29.7/node_modules/@babel/core" + }, + "@istanbuljs/schema": { + "link": "../../../··@istanbuljs§schema@0.1.6/node_modules/@istanbuljs/schema" + }, + "istanbul-lib-coverage": { + "link": "../../··istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-instrument": { + "pkg": { + "name": "istanbul-lib-instrument", + "version": "4.0.3" + } + }, + "semver": { + "link": "../../··semver@6.3.1/node_modules/semver" + } + } + }, + { + "id": "··istanbul-lib-processinfo@2.0.3", + "node_modules": { + ".bin": { + "dir": true + }, + "archy": { + "link": "../../··archy@1.0.0/node_modules/archy" + }, + "cross-spawn": { + "link": "../../··cross-spawn@7.0.6/node_modules/cross-spawn" + }, + "istanbul-lib-coverage": { + "link": "../../··istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-processinfo": { + "pkg": { + "name": "istanbul-lib-processinfo", + "version": "2.0.3" + } + }, + "p-map": { + "link": "../../··p-map@3.0.0/node_modules/p-map" + }, + "rimraf": { + "link": "../../··rimraf@3.0.2/node_modules/rimraf" + }, + "uuid": { + "link": "../../··uuid@8.3.2/node_modules/uuid" + } + } + }, + { + "id": "··istanbul-lib-report@3.0.1", + "node_modules": { + "istanbul-lib-coverage": { + "link": "../../··istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-report": { + "pkg": { + "name": "istanbul-lib-report", + "version": "3.0.1" + } + }, + "make-dir": { + "link": "../../··make-dir@4.0.0/node_modules/make-dir" + }, + "supports-color": { + "link": "../../··supports-color@7.2.0/node_modules/supports-color" + } + } + }, + { + "id": "··istanbul-lib-source-maps@4.0.1", + "node_modules": { + "debug": { + "link": "../../··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms/node_modules/debug" + }, + "istanbul-lib-coverage": { + "link": "../../··istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-source-maps": { + "pkg": { + "name": "istanbul-lib-source-maps", + "version": "4.0.1" + } + }, + "source-map": { + "link": "../../··source-map@0.6.1/node_modules/source-map" + } + } + }, + { + "id": "··istanbul-reports@3.2.0", + "node_modules": { + "html-escaper": { + "link": "../../··html-escaper@2.0.2/node_modules/html-escaper" + }, + "istanbul-lib-report": { + "link": "../../··istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report" + }, + "istanbul-reports": { + "pkg": { + "name": "istanbul-reports", + "version": "3.2.0" + } + } + } + }, + { + "id": "··jackspeak@1.4.2", + "node_modules": { + "cliui": { + "link": "../../··cliui@7.0.4/node_modules/cliui" + }, + "jackspeak": { + "pkg": { + "name": "jackspeak", + "version": "1.4.2" + } + } + } + }, + { + "id": "··js-tokens@4.0.0", + "node_modules": { + "js-tokens": { + "pkg": { + "name": "js-tokens", + "version": "4.0.0" + } + } + } + }, + { + "id": "··js-yaml@3.15.2", + "node_modules": { + ".bin": { + "dir": true + }, + "argparse": { + "link": "../../··argparse@1.0.10/node_modules/argparse" + }, + "esprima": { + "link": "../../··esprima@4.0.1/node_modules/esprima" + }, + "js-yaml": { + "pkg": { + "name": "js-yaml", + "version": "3.15.2" + } + } + } + }, + { + "id": "··jsbn@0.1.1", + "node_modules": { + "jsbn": { + "pkg": { + "name": "jsbn", + "version": "0.1.1" + } + } + } + }, + { + "id": "··jsesc@3.1.0", + "node_modules": { + "jsesc": { + "pkg": { + "name": "jsesc", + "version": "3.1.0" + } + } + } + }, + { + "id": "··json-schema-traverse@0.4.1", + "node_modules": { + "json-schema-traverse": { + "pkg": { + "name": "json-schema-traverse", + "version": "0.4.1" + } + } + } + }, + { + "id": "··json-schema@0.4.0", + "node_modules": { + "json-schema": { + "pkg": { + "name": "json-schema", + "version": "0.4.0" + } + } + } + }, + { + "id": "··json-stringify-safe@5.0.1", + "node_modules": { + "json-stringify-safe": { + "pkg": { + "name": "json-stringify-safe", + "version": "5.0.1" + } + } + } + }, + { + "id": "··json5@2.2.3", + "node_modules": { + "json5": { + "pkg": { + "name": "json5", + "version": "2.2.3" + } + } + } + }, + { + "id": "··jsprim@1.4.2", + "node_modules": { + "assert-plus": { + "link": "../../··assert-plus@1.0.0/node_modules/assert-plus" + }, + "extsprintf": { + "link": "../../··extsprintf@1.3.0/node_modules/extsprintf" + }, + "json-schema": { + "link": "../../··json-schema@0.4.0/node_modules/json-schema" + }, + "jsprim": { + "pkg": { + "name": "jsprim", + "version": "1.4.2" + } + }, + "verror": { + "link": "../../··verror@1.10.0/node_modules/verror" + } + } + }, + { + "id": "··lcov-parse@1.0.0", + "node_modules": { + "lcov-parse": { + "pkg": { + "name": "lcov-parse", + "version": "1.0.0" + } + } + } + }, + { + "id": "··left-pad@1.3.0", + "node_modules": { + "left-pad": { + "pkg": { + "name": "left-pad", + "version": "1.3.0" + } + } + } + }, + { + "id": "··libtap@1.4.1", + "node_modules": { + ".bin": { + "dir": true + }, + "async-hook-domain": { + "link": "../../··async-hook-domain@2.0.4/node_modules/async-hook-domain" + }, + "bind-obj-methods": { + "link": "../../··bind-obj-methods@3.0.0/node_modules/bind-obj-methods" + }, + "diff": { + "link": "../../··diff@4.0.4/node_modules/diff" + }, + "function-loop": { + "link": "../../··function-loop@2.0.1/node_modules/function-loop" + }, + "libtap": { + "pkg": { + "name": "libtap", + "version": "1.4.1" + } + }, + "minipass": { + "link": "../../··minipass@3.3.6/node_modules/minipass" + }, + "own-or": { + "link": "../../··own-or@1.0.0/node_modules/own-or" + }, + "own-or-env": { + "link": "../../··own-or-env@1.0.2/node_modules/own-or-env" + }, + "signal-exit": { + "link": "../../··signal-exit@3.0.7/node_modules/signal-exit" + }, + "stack-utils": { + "link": "../../··stack-utils@2.0.6/node_modules/stack-utils" + }, + "tap-parser": { + "link": "../../··tap-parser@11.0.2/node_modules/tap-parser" + }, + "tap-yaml": { + "link": "../../··tap-yaml@1.0.2/node_modules/tap-yaml" + }, + "tcompare": { + "link": "../../··tcompare@5.0.7/node_modules/tcompare" + }, + "trivial-deferred": { + "link": "../../··trivial-deferred@1.1.2/node_modules/trivial-deferred" + } + } + }, + { + "id": "··locate-path@5.0.0", + "node_modules": { + "locate-path": { + "pkg": { + "name": "locate-path", + "version": "5.0.0" + } + }, + "p-locate": { + "link": "../../··p-locate@4.1.0/node_modules/p-locate" + } + } + }, + { + "id": "··lodash.flattendeep@4.4.0", + "node_modules": { + "lodash.flattendeep": { + "pkg": { + "name": "lodash.flattendeep", + "version": "4.4.0" + } + } + } + }, + { + "id": "··log-driver@1.2.7", + "node_modules": { + "log-driver": { + "pkg": { + "name": "log-driver", + "version": "1.2.7" + } + } + } + }, + { + "id": "··loose-envify@1.4.0", + "node_modules": { + "js-tokens": { + "link": "../../··js-tokens@4.0.0/node_modules/js-tokens" + }, + "loose-envify": { + "pkg": { + "name": "loose-envify", + "version": "1.4.0" + } + } + } + }, + { + "id": "··lru-cache@5.1.1", + "node_modules": { + "lru-cache": { + "pkg": { + "name": "lru-cache", + "version": "5.1.1" + } + }, + "yallist": { + "link": "../../··yallist@3.1.1/node_modules/yallist" + } + } + }, + { + "id": "··lru-cache@6.0.0", + "node_modules": { + "lru-cache": { + "pkg": { + "name": "lru-cache", + "version": "6.0.0" + } + }, + "yallist": { + "link": "../../··yallist@4.0.0/node_modules/yallist" + } + } + }, + { + "id": "··make-dir@3.1.0", + "node_modules": { + ".bin": { + "dir": true + }, + "make-dir": { + "pkg": { + "name": "make-dir", + "version": "3.1.0" + } + }, + "semver": { + "link": "../../··semver@6.3.1/node_modules/semver" + } + } + }, + { + "id": "··make-dir@4.0.0", + "node_modules": { + ".bin": { + "dir": true + }, + "make-dir": { + "pkg": { + "name": "make-dir", + "version": "4.0.0" + } + }, + "semver": { + "link": "../../·npm·semver@7.6.0/node_modules/semver" + } + } + }, + { + "id": "··mime-db@1.52.0", + "node_modules": { + "mime-db": { + "pkg": { + "name": "mime-db", + "version": "1.52.0" + } + } + } + }, + { + "id": "··mime-types@2.1.35", + "node_modules": { + "mime-db": { + "link": "../../··mime-db@1.52.0/node_modules/mime-db" + }, + "mime-types": { + "pkg": { + "name": "mime-types", + "version": "2.1.35" + } + } + } + }, + { + "id": "··minimatch@3.1.5", + "node_modules": { + "brace-expansion": { + "link": "../../··brace-expansion@1.1.21/node_modules/brace-expansion" + }, + "minimatch": { + "pkg": { + "name": "minimatch", + "version": "3.1.5" + } + } + } + }, + { + "id": "··minimist@1.2.8", + "node_modules": { + "minimist": { + "pkg": { + "name": "minimist", + "version": "1.2.8" + } + } + } + }, + { + "id": "··minipass@3.3.6", + "node_modules": { + "minipass": { + "pkg": { + "name": "minipass", + "version": "3.3.6" + } + }, + "yallist": { + "link": "../../··yallist@4.0.0/node_modules/yallist" + } + } + }, + { + "id": "··mkdirp@1.0.4", + "node_modules": { + "mkdirp": { + "pkg": { + "name": "mkdirp", + "version": "1.0.4" + } + } + } + }, + { + "id": "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "node_modules": { + "ms": { + "pkg": { + "name": "ms", + "version": "2.1.3" + } + } + } + }, + { + "id": "··node-preload@0.2.1", + "node_modules": { + "node-preload": { + "pkg": { + "name": "node-preload", + "version": "0.2.1" + } + }, + "process-on-spawn": { + "link": "../../··process-on-spawn@1.1.0/node_modules/process-on-spawn" + } + } + }, + { + "id": "··node-releases@2.0.57", + "node_modules": { + "node-releases": { + "pkg": { + "name": "node-releases", + "version": "2.0.57" + } + } + } + }, + { + "id": "··normalize-path@3.0.0", + "node_modules": { + "normalize-path": { + "pkg": { + "name": "normalize-path", + "version": "3.0.0" + } + } + } + }, + { + "id": "··nyc@15.1.0", + "node_modules": { + ".bin": { + "dir": true + }, + "@istanbuljs/load-nyc-config": { + "link": "../../../··@istanbuljs§load-nyc-config@1.1.0/node_modules/@istanbuljs/load-nyc-config" + }, + "@istanbuljs/schema": { + "link": "../../../··@istanbuljs§schema@0.1.6/node_modules/@istanbuljs/schema" + }, + "caching-transform": { + "link": "../../··caching-transform@4.0.0/node_modules/caching-transform" + }, + "convert-source-map": { + "link": "../../··convert-source-map@1.9.0/node_modules/convert-source-map" + }, + "decamelize": { + "link": "../../··decamelize@1.2.0/node_modules/decamelize" + }, + "find-cache-dir": { + "link": "../../··find-cache-dir@3.3.2/node_modules/find-cache-dir" + }, + "find-up": { + "link": "../../··find-up@4.1.0/node_modules/find-up" + }, + "foreground-child": { + "link": "../../··foreground-child@2.0.0/node_modules/foreground-child" + }, + "get-package-type": { + "link": "../../··get-package-type@0.1.0/node_modules/get-package-type" + }, + "glob": { + "link": "../../··glob@7.2.3/node_modules/glob" + }, + "istanbul-lib-coverage": { + "link": "../../··istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-hook": { + "link": "../../··istanbul-lib-hook@3.0.0/node_modules/istanbul-lib-hook" + }, + "istanbul-lib-instrument": { + "link": "../../··istanbul-lib-instrument@4.0.3/node_modules/istanbul-lib-instrument" + }, + "istanbul-lib-processinfo": { + "link": "../../··istanbul-lib-processinfo@2.0.3/node_modules/istanbul-lib-processinfo" + }, + "istanbul-lib-report": { + "link": "../../··istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report" + }, + "istanbul-lib-source-maps": { + "link": "../../··istanbul-lib-source-maps@4.0.1/node_modules/istanbul-lib-source-maps" + }, + "istanbul-reports": { + "link": "../../··istanbul-reports@3.2.0/node_modules/istanbul-reports" + }, + "make-dir": { + "link": "../../··make-dir@3.1.0/node_modules/make-dir" + }, + "node-preload": { + "link": "../../··node-preload@0.2.1/node_modules/node-preload" + }, + "nyc": { + "pkg": { + "name": "nyc", + "version": "15.1.0" + } + }, + "p-map": { + "link": "../../··p-map@3.0.0/node_modules/p-map" + }, + "process-on-spawn": { + "link": "../../··process-on-spawn@1.1.0/node_modules/process-on-spawn" + }, + "resolve-from": { + "link": "../../··resolve-from@5.0.0/node_modules/resolve-from" + }, + "rimraf": { + "link": "../../··rimraf@3.0.2/node_modules/rimraf" + }, + "signal-exit": { + "link": "../../··signal-exit@3.0.7/node_modules/signal-exit" + }, + "spawn-wrap": { + "link": "../../··spawn-wrap@2.0.0/node_modules/spawn-wrap" + }, + "test-exclude": { + "link": "../../··test-exclude@6.0.0/node_modules/test-exclude" + }, + "yargs": { + "link": "../../··yargs@15.4.1/node_modules/yargs" + } + } + }, + { + "id": "··oauth-sign@0.9.0", + "node_modules": { + "oauth-sign": { + "pkg": { + "name": "oauth-sign", + "version": "0.9.0" + } + } + } + }, + { + "id": "··once@1.4.0", + "node_modules": { + "once": { + "pkg": { + "name": "once", + "version": "1.4.0" + } + }, + "wrappy": { + "link": "../../··wrappy@1.0.2/node_modules/wrappy" + } + } + }, + { + "id": "··opener@1.5.2", + "node_modules": { + "opener": { + "pkg": { + "name": "opener", + "version": "1.5.2" + } + } + } + }, + { + "id": "··own-or-env@1.0.2", + "node_modules": { + "own-or": { + "link": "../../··own-or@1.0.0/node_modules/own-or" + }, + "own-or-env": { + "pkg": { + "name": "own-or-env", + "version": "1.0.2" + } + } + } + }, + { + "id": "··own-or@1.0.0", + "node_modules": { + "own-or": { + "pkg": { + "name": "own-or", + "version": "1.0.0" + } + } + } + }, + { + "id": "··p-limit@2.3.0", + "node_modules": { + "p-limit": { + "pkg": { + "name": "p-limit", + "version": "2.3.0" + } + }, + "p-try": { + "link": "../../··p-try@2.2.0/node_modules/p-try" + } + } + }, + { + "id": "··p-locate@4.1.0", + "node_modules": { + "p-limit": { + "link": "../../··p-limit@2.3.0/node_modules/p-limit" + }, + "p-locate": { + "pkg": { + "name": "p-locate", + "version": "4.1.0" + } + } + } + }, + { + "id": "··p-map@3.0.0", + "node_modules": { + "aggregate-error": { + "link": "../../··aggregate-error@3.1.0/node_modules/aggregate-error" + }, + "p-map": { + "pkg": { + "name": "p-map", + "version": "3.0.0" + } + } + } + }, + { + "id": "··p-try@2.2.0", + "node_modules": { + "p-try": { + "pkg": { + "name": "p-try", + "version": "2.2.0" + } + } + } + }, + { + "id": "··package-hash@4.0.0", + "node_modules": { + "graceful-fs": { + "link": "../../··graceful-fs@4.2.11/node_modules/graceful-fs" + }, + "hasha": { + "link": "../../··hasha@5.2.2/node_modules/hasha" + }, + "lodash.flattendeep": { + "link": "../../··lodash.flattendeep@4.4.0/node_modules/lodash.flattendeep" + }, + "package-hash": { + "pkg": { + "name": "package-hash", + "version": "4.0.0" + } + }, + "release-zalgo": { + "link": "../../··release-zalgo@1.0.0/node_modules/release-zalgo" + } + } + }, + { + "id": "··path-exists@4.0.0", + "node_modules": { + "path-exists": { + "pkg": { + "name": "path-exists", + "version": "4.0.0" + } + } + } + }, + { + "id": "··path-is-absolute@1.0.1", + "node_modules": { + "path-is-absolute": { + "pkg": { + "name": "path-is-absolute", + "version": "1.0.1" + } + } + } + }, + { + "id": "··path-key@3.1.1", + "node_modules": { + "path-key": { + "pkg": { + "name": "path-key", + "version": "3.1.1" + } + } + } + }, + { + "id": "··performance-now@2.1.0", + "node_modules": { + "performance-now": { + "pkg": { + "name": "performance-now", + "version": "2.1.0" + } + } + } + }, + { + "id": "··picocolors@1.1.1", + "node_modules": { + "picocolors": { + "pkg": { + "name": "picocolors", + "version": "1.1.1" + } + } + } + }, + { + "id": "··picomatch@2.3.2", + "node_modules": { + "picomatch": { + "pkg": { + "name": "picomatch", + "version": "2.3.2" + } + } + } + }, + { + "id": "··pkg-dir@4.2.0", + "node_modules": { + "find-up": { + "link": "../../··find-up@4.1.0/node_modules/find-up" + }, + "pkg-dir": { + "pkg": { + "name": "pkg-dir", + "version": "4.2.0" + } + } + } + }, + { + "id": "··process-on-spawn@1.1.0", + "node_modules": { + "fromentries": { + "link": "../../··fromentries@1.3.2/node_modules/fromentries" + }, + "process-on-spawn": { + "pkg": { + "name": "process-on-spawn", + "version": "1.1.0" + } + } + } + }, + { + "id": "··psl@1.15.0", + "node_modules": { + "psl": { + "pkg": { + "name": "psl", + "version": "1.15.0" + } + }, + "punycode": { + "link": "../../··punycode@2.3.1/node_modules/punycode" + } + } + }, + { + "id": "··punycode@2.3.1", + "node_modules": { + "punycode": { + "pkg": { + "name": "punycode", + "version": "2.3.1" + } + } + } + }, + { + "id": "··qs@6.5.5", + "node_modules": { + "qs": { + "pkg": { + "name": "qs", + "version": "6.5.5" + } + } + } + }, + { + "id": "··react@18.2.0", + "node_modules": { + ".bin": { + "dir": true + }, + "loose-envify": { + "link": "../../··loose-envify@1.4.0/node_modules/loose-envify" + }, + "react": { + "pkg": { + "name": "react", + "version": "18.2.0" + } + } + } + }, + { + "id": "··readdirp@3.6.0", + "node_modules": { + "picomatch": { + "link": "../../··picomatch@2.3.2/node_modules/picomatch" + }, + "readdirp": { + "pkg": { + "name": "readdirp", + "version": "3.6.0" + } + } + } + }, + { + "id": "··release-zalgo@1.0.0", + "node_modules": { + "es6-error": { + "link": "../../··es6-error@4.1.1/node_modules/es6-error" + }, + "release-zalgo": { + "pkg": { + "name": "release-zalgo", + "version": "1.0.0" + } + } + } + }, + { + "id": "··request@2.88.2", + "node_modules": { + ".bin": { + "dir": true + }, + "aws-sign2": { + "link": "../../··aws-sign2@0.7.0/node_modules/aws-sign2" + }, + "aws4": { + "link": "../../··aws4@1.13.2/node_modules/aws4" + }, + "caseless": { + "link": "../../··caseless@0.12.0/node_modules/caseless" + }, + "combined-stream": { + "link": "../../··combined-stream@1.0.8/node_modules/combined-stream" + }, + "extend": { + "link": "../../··extend@3.0.2/node_modules/extend" + }, + "forever-agent": { + "link": "../../··forever-agent@0.6.1/node_modules/forever-agent" + }, + "form-data": { + "link": "../../··form-data@2.3.3/node_modules/form-data" + }, + "har-validator": { + "link": "../../··har-validator@5.1.5/node_modules/har-validator" + }, + "http-signature": { + "link": "../../··http-signature@1.2.0/node_modules/http-signature" + }, + "is-typedarray": { + "link": "../../··is-typedarray@1.0.0/node_modules/is-typedarray" + }, + "isstream": { + "link": "../../··isstream@0.1.2/node_modules/isstream" + }, + "json-stringify-safe": { + "link": "../../··json-stringify-safe@5.0.1/node_modules/json-stringify-safe" + }, + "mime-types": { + "link": "../../··mime-types@2.1.35/node_modules/mime-types" + }, + "oauth-sign": { + "link": "../../··oauth-sign@0.9.0/node_modules/oauth-sign" + }, + "performance-now": { + "link": "../../··performance-now@2.1.0/node_modules/performance-now" + }, + "qs": { + "link": "../../··qs@6.5.5/node_modules/qs" + }, + "request": { + "pkg": { + "name": "request", + "version": "2.88.2" + } + }, + "safe-buffer": { + "link": "../../··safe-buffer@5.2.1/node_modules/safe-buffer" + }, + "tough-cookie": { + "link": "../../··tough-cookie@2.5.0/node_modules/tough-cookie" + }, + "tunnel-agent": { + "link": "../../··tunnel-agent@0.6.0/node_modules/tunnel-agent" + }, + "uuid": { + "link": "../../··uuid@3.4.0/node_modules/uuid" + } + } + }, + { + "id": "··require-directory@2.1.1", + "node_modules": { + "require-directory": { + "pkg": { + "name": "require-directory", + "version": "2.1.1" + } + } + } + }, + { + "id": "··require-main-filename@2.0.0", + "node_modules": { + "require-main-filename": { + "pkg": { + "name": "require-main-filename", + "version": "2.0.0" + } + } + } + }, + { + "id": "··resolve-from@5.0.0", + "node_modules": { + "resolve-from": { + "pkg": { + "name": "resolve-from", + "version": "5.0.0" + } + } + } + }, + { + "id": "··rimraf@3.0.2", + "node_modules": { + "glob": { + "link": "../../··glob@7.2.3/node_modules/glob" + }, + "rimraf": { + "pkg": { + "name": "rimraf", + "version": "3.0.2" + } + } + } + }, + { + "id": "··safe-buffer@5.2.1", + "node_modules": { + "safe-buffer": { + "pkg": { + "name": "safe-buffer", + "version": "5.2.1" + } + } + } + }, + { + "id": "··safer-buffer@2.1.2", + "node_modules": { + "safer-buffer": { + "pkg": { + "name": "safer-buffer", + "version": "2.1.2" + } + } + } + }, + { + "id": "··semver@6.3.1", + "node_modules": { + "semver": { + "pkg": { + "name": "semver", + "version": "6.3.1" + } + } + } + }, + { + "id": "··set-blocking@2.0.0", + "node_modules": { + "set-blocking": { + "pkg": { + "name": "set-blocking", + "version": "2.0.0" + } + } + } + }, + { + "id": "··shebang-command@2.0.0", + "node_modules": { + "shebang-command": { + "pkg": { + "name": "shebang-command", + "version": "2.0.0" + } + }, + "shebang-regex": { + "link": "../../··shebang-regex@3.0.0/node_modules/shebang-regex" + } + } + }, + { + "id": "··shebang-regex@3.0.0", + "node_modules": { + "shebang-regex": { + "pkg": { + "name": "shebang-regex", + "version": "3.0.0" + } + } + } + }, + { + "id": "··signal-exit@3.0.7", + "node_modules": { + "signal-exit": { + "pkg": { + "name": "signal-exit", + "version": "3.0.7" + } + } + } + }, + { + "id": "··source-map-support@0.5.21", + "node_modules": { + "buffer-from": { + "link": "../../··buffer-from@1.1.2/node_modules/buffer-from" + }, + "source-map": { + "link": "../../··source-map@0.6.1/node_modules/source-map" + }, + "source-map-support": { + "pkg": { + "name": "source-map-support", + "version": "0.5.21" + } + } + } + }, + { + "id": "··source-map@0.6.1", + "node_modules": { + "source-map": { + "pkg": { + "name": "source-map", + "version": "0.6.1" + } + } + } + }, + { + "id": "··spawn-wrap@2.0.0", + "node_modules": { + ".bin": { + "dir": true + }, + "foreground-child": { + "link": "../../··foreground-child@2.0.0/node_modules/foreground-child" + }, + "is-windows": { + "link": "../../··is-windows@1.0.2/node_modules/is-windows" + }, + "make-dir": { + "link": "../../··make-dir@3.1.0/node_modules/make-dir" + }, + "rimraf": { + "link": "../../··rimraf@3.0.2/node_modules/rimraf" + }, + "signal-exit": { + "link": "../../··signal-exit@3.0.7/node_modules/signal-exit" + }, + "spawn-wrap": { + "pkg": { + "name": "spawn-wrap", + "version": "2.0.0" + } + }, + "which": { + "link": "../../··which@2.0.2/node_modules/which" + } + } + }, + { + "id": "··sprintf-js@1.0.3", + "node_modules": { + "sprintf-js": { + "pkg": { + "name": "sprintf-js", + "version": "1.0.3" + } + } + } + }, + { + "id": "··sshpk@1.18.0", + "node_modules": { + "asn1": { + "link": "../../··asn1@0.2.6/node_modules/asn1" + }, + "assert-plus": { + "link": "../../··assert-plus@1.0.0/node_modules/assert-plus" + }, + "bcrypt-pbkdf": { + "link": "../../··bcrypt-pbkdf@1.0.2/node_modules/bcrypt-pbkdf" + }, + "dashdash": { + "link": "../../··dashdash@1.14.1/node_modules/dashdash" + }, + "ecc-jsbn": { + "link": "../../··ecc-jsbn@0.1.2/node_modules/ecc-jsbn" + }, + "getpass": { + "link": "../../··getpass@0.1.7/node_modules/getpass" + }, + "jsbn": { + "link": "../../··jsbn@0.1.1/node_modules/jsbn" + }, + "safer-buffer": { + "link": "../../··safer-buffer@2.1.2/node_modules/safer-buffer" + }, + "sshpk": { + "pkg": { + "name": "sshpk", + "version": "1.18.0" + } + }, + "tweetnacl": { + "link": "../../··tweetnacl@0.14.5/node_modules/tweetnacl" + } + } + }, + { + "id": "··stack-utils@2.0.6", + "node_modules": { + "escape-string-regexp": { + "link": "../../··escape-string-regexp@2.0.0/node_modules/escape-string-regexp" + }, + "stack-utils": { + "pkg": { + "name": "stack-utils", + "version": "2.0.6" + } + } + } + }, + { + "id": "··string-width@4.2.3", + "node_modules": { + "emoji-regex": { + "link": "../../··emoji-regex@8.0.0/node_modules/emoji-regex" + }, + "is-fullwidth-code-point": { + "link": "../../··is-fullwidth-code-point@3.0.0/node_modules/is-fullwidth-code-point" + }, + "string-width": { + "pkg": { + "name": "string-width", + "version": "4.2.3" + } + }, + "strip-ansi": { + "link": "../../··strip-ansi@6.0.1/node_modules/strip-ansi" + } + } + }, + { + "id": "··strip-ansi@6.0.1", + "node_modules": { + "ansi-regex": { + "link": "../../··ansi-regex@5.0.1/node_modules/ansi-regex" + }, + "strip-ansi": { + "pkg": { + "name": "strip-ansi", + "version": "6.0.1" + } + } + } + }, + { + "id": "··strip-bom@4.0.0", + "node_modules": { + "strip-bom": { + "pkg": { + "name": "strip-bom", + "version": "4.0.0" + } + } + } + }, + { + "id": "··supports-color@7.2.0", + "node_modules": { + "has-flag": { + "link": "../../··has-flag@4.0.0/node_modules/has-flag" + }, + "supports-color": { + "pkg": { + "name": "supports-color", + "version": "7.2.0" + } + } + } + }, + { + "id": "··tap-mocha-reporter@5.0.4", + "node_modules": { + ".bin": { + "dir": true + }, + "color-support": { + "link": "../../··color-support@1.1.3/node_modules/color-support" + }, + "debug": { + "link": "../../··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms/node_modules/debug" + }, + "diff": { + "link": "../../··diff@4.0.4/node_modules/diff" + }, + "escape-string-regexp": { + "link": "../../··escape-string-regexp@2.0.0/node_modules/escape-string-regexp" + }, + "glob": { + "link": "../../··glob@7.2.3/node_modules/glob" + }, + "tap-mocha-reporter": { + "pkg": { + "name": "tap-mocha-reporter", + "version": "5.0.4" + } + }, + "tap-parser": { + "link": "../../··tap-parser@11.0.2/node_modules/tap-parser" + }, + "tap-yaml": { + "link": "../../··tap-yaml@1.0.2/node_modules/tap-yaml" + }, + "unicode-length": { + "link": "../../··unicode-length@2.1.0/node_modules/unicode-length" + } + } + }, + { + "id": "··tap-parser@11.0.2", + "node_modules": { + "events-to-array": { + "link": "../../··events-to-array@1.1.2/node_modules/events-to-array" + }, + "minipass": { + "link": "../../··minipass@3.3.6/node_modules/minipass" + }, + "tap-parser": { + "pkg": { + "name": "tap-parser", + "version": "11.0.2" + } + }, + "tap-yaml": { + "link": "../../··tap-yaml@1.0.2/node_modules/tap-yaml" + } + } + }, + { + "id": "··tap-yaml@1.0.2", + "node_modules": { + "tap-yaml": { + "pkg": { + "name": "tap-yaml", + "version": "1.0.2" + } + }, + "yaml": { + "link": "../../··yaml@1.10.3/node_modules/yaml" + } + } + }, + { + "id": "··tap@15.2.3", + "node_modules": { + ".bin": { + "dir": true + }, + "chokidar": { + "link": "../../··chokidar@3.6.0/node_modules/chokidar" + }, + "coveralls": { + "link": "../../··coveralls@3.1.1/node_modules/coveralls" + }, + "findit": { + "link": "../../··findit@2.0.0/node_modules/findit" + }, + "foreground-child": { + "link": "../../··foreground-child@2.0.0/node_modules/foreground-child" + }, + "fs-exists-cached": { + "link": "../../··fs-exists-cached@1.0.0/node_modules/fs-exists-cached" + }, + "glob": { + "link": "../../··glob@7.2.3/node_modules/glob" + }, + "isexe": { + "link": "../../··isexe@2.0.0/node_modules/isexe" + }, + "istanbul-lib-processinfo": { + "link": "../../··istanbul-lib-processinfo@2.0.3/node_modules/istanbul-lib-processinfo" + }, + "jackspeak": { + "link": "../../··jackspeak@1.4.2/node_modules/jackspeak" + }, + "libtap": { + "link": "../../··libtap@1.4.1/node_modules/libtap" + }, + "minipass": { + "link": "../../··minipass@3.3.6/node_modules/minipass" + }, + "mkdirp": { + "link": "../../··mkdirp@1.0.4/node_modules/mkdirp" + }, + "nyc": { + "link": "../../··nyc@15.1.0/node_modules/nyc" + }, + "opener": { + "link": "../../··opener@1.5.2/node_modules/opener" + }, + "rimraf": { + "link": "../../··rimraf@3.0.2/node_modules/rimraf" + }, + "signal-exit": { + "link": "../../··signal-exit@3.0.7/node_modules/signal-exit" + }, + "source-map-support": { + "link": "../../··source-map-support@0.5.21/node_modules/source-map-support" + }, + "tap": { + "pkg": { + "name": "tap", + "version": "15.2.3" + } + }, + "tap-mocha-reporter": { + "link": "../../··tap-mocha-reporter@5.0.4/node_modules/tap-mocha-reporter" + }, + "tap-parser": { + "link": "../../··tap-parser@11.0.2/node_modules/tap-parser" + }, + "tap-yaml": { + "link": "../../··tap-yaml@1.0.2/node_modules/tap-yaml" + }, + "tcompare": { + "link": "../../··tcompare@5.0.7/node_modules/tcompare" + }, + "which": { + "link": "../../··which@2.0.2/node_modules/which" + } + } + }, + { + "id": "··tcompare@5.0.7", + "node_modules": { + "diff": { + "link": "../../··diff@4.0.4/node_modules/diff" + }, + "tcompare": { + "pkg": { + "name": "tcompare", + "version": "5.0.7" + } + } + } + }, + { + "id": "··test-exclude@6.0.0", + "node_modules": { + "@istanbuljs/schema": { + "link": "../../../··@istanbuljs§schema@0.1.6/node_modules/@istanbuljs/schema" + }, + "glob": { + "link": "../../··glob@7.2.3/node_modules/glob" + }, + "minimatch": { + "link": "../../··minimatch@3.1.5/node_modules/minimatch" + }, + "test-exclude": { + "pkg": { + "name": "test-exclude", + "version": "6.0.0" + } + } + } + }, + { + "id": "··to-regex-range@5.0.1", + "node_modules": { + "is-number": { + "link": "../../··is-number@7.0.0/node_modules/is-number" + }, + "to-regex-range": { + "pkg": { + "name": "to-regex-range", + "version": "5.0.1" + } + } + } + }, + { + "id": "··tough-cookie@2.5.0", + "node_modules": { + "psl": { + "link": "../../··psl@1.15.0/node_modules/psl" + }, + "punycode": { + "link": "../../··punycode@2.3.1/node_modules/punycode" + }, + "tough-cookie": { + "pkg": { + "name": "tough-cookie", + "version": "2.5.0" + } + } + } + }, + { + "id": "··trivial-deferred@1.1.2", + "node_modules": { + "trivial-deferred": { + "pkg": { + "name": "trivial-deferred", + "version": "1.1.2" + } + } + } + }, + { + "id": "··tunnel-agent@0.6.0", + "node_modules": { + "safe-buffer": { + "link": "../../··safe-buffer@5.2.1/node_modules/safe-buffer" + }, + "tunnel-agent": { + "pkg": { + "name": "tunnel-agent", + "version": "0.6.0" + } + } + } + }, + { + "id": "··tweetnacl@0.14.5", + "node_modules": { + "tweetnacl": { + "pkg": { + "name": "tweetnacl", + "version": "0.14.5" + } + } + } + }, + { + "id": "··type-fest@0.8.1", + "node_modules": { + "type-fest": { + "pkg": { + "name": "type-fest", + "version": "0.8.1" + } + } + } + }, + { + "id": "··typedarray-to-buffer@3.1.5", + "node_modules": { + "is-typedarray": { + "link": "../../··is-typedarray@1.0.0/node_modules/is-typedarray" + }, + "typedarray-to-buffer": { + "pkg": { + "name": "typedarray-to-buffer", + "version": "3.1.5" + } + } + } + }, + { + "id": "··unicode-length@2.1.0", + "node_modules": { + "punycode": { + "link": "../../··punycode@2.3.1/node_modules/punycode" + }, + "unicode-length": { + "pkg": { + "name": "unicode-length", + "version": "2.1.0" + } + } + } + }, + { + "id": "··update-browserslist-db@1.3.3", + "node_modules": { + ".bin": { + "dir": true + }, + "browserslist": { + "link": "../../··browserslist@4.29.1/node_modules/browserslist" + }, + "escalade": { + "link": "../../··escalade@3.2.0/node_modules/escalade" + }, + "picocolors": { + "link": "../../··picocolors@1.1.1/node_modules/picocolors" + }, + "update-browserslist-db": { + "pkg": { + "name": "update-browserslist-db", + "version": "1.3.3" + } + } + } + }, + { + "id": "··uri-js@4.4.1", + "node_modules": { + "punycode": { + "link": "../../··punycode@2.3.1/node_modules/punycode" + }, + "uri-js": { + "pkg": { + "name": "uri-js", + "version": "4.4.1" + } + } + } + }, + { + "id": "··use-sync-external-store@1.2.0", + "node_modules": { + "react": { + "link": "../../··react@18.2.0/node_modules/react" + }, + "use-sync-external-store": { + "pkg": { + "name": "use-sync-external-store", + "version": "1.2.0" + } + } + } + }, + { + "id": "··uuid@3.4.0", + "node_modules": { + "uuid": { + "pkg": { + "name": "uuid", + "version": "3.4.0" + } + } + } + }, + { + "id": "··uuid@8.3.2", + "node_modules": { + "uuid": { + "pkg": { + "name": "uuid", + "version": "8.3.2" + } + } + } + }, + { + "id": "··verror@1.10.0", + "node_modules": { + "assert-plus": { + "link": "../../··assert-plus@1.0.0/node_modules/assert-plus" + }, + "core-util-is": { + "link": "../../··core-util-is@1.0.2/node_modules/core-util-is" + }, + "extsprintf": { + "link": "../../··extsprintf@1.3.0/node_modules/extsprintf" + }, + "verror": { + "pkg": { + "name": "verror", + "version": "1.10.0" + } + } + } + }, + { + "id": "··which-module@2.0.1", + "node_modules": { + "which-module": { + "pkg": { + "name": "which-module", + "version": "2.0.1" + } + } + } + }, + { + "id": "··which@2.0.2", + "node_modules": { + "isexe": { + "link": "../../··isexe@2.0.0/node_modules/isexe" + }, + "which": { + "pkg": { + "name": "which", + "version": "2.0.2" + } + } + } + }, + { + "id": "··wrap-ansi@6.2.0", + "node_modules": { + "ansi-styles": { + "link": "../../··ansi-styles@4.3.0/node_modules/ansi-styles" + }, + "string-width": { + "link": "../../··string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../../··strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "wrap-ansi": { + "pkg": { + "name": "wrap-ansi", + "version": "6.2.0" + } + } + } + }, + { + "id": "··wrap-ansi@7.0.0", + "node_modules": { + "ansi-styles": { + "link": "../../··ansi-styles@4.3.0/node_modules/ansi-styles" + }, + "string-width": { + "link": "../../··string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../../··strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "wrap-ansi": { + "pkg": { + "name": "wrap-ansi", + "version": "7.0.0" + } + } + } + }, + { + "id": "··wrappy@1.0.2", + "node_modules": { + "wrappy": { + "pkg": { + "name": "wrappy", + "version": "1.0.2" + } + } + } + }, + { + "id": "··write-file-atomic@3.0.3", + "node_modules": { + "imurmurhash": { + "link": "../../··imurmurhash@0.1.4/node_modules/imurmurhash" + }, + "is-typedarray": { + "link": "../../··is-typedarray@1.0.0/node_modules/is-typedarray" + }, + "signal-exit": { + "link": "../../··signal-exit@3.0.7/node_modules/signal-exit" + }, + "typedarray-to-buffer": { + "link": "../../··typedarray-to-buffer@3.1.5/node_modules/typedarray-to-buffer" + }, + "write-file-atomic": { + "pkg": { + "name": "write-file-atomic", + "version": "3.0.3" + } + } + } + }, + { + "id": "··y18n@4.0.3", + "node_modules": { + "y18n": { + "pkg": { + "name": "y18n", + "version": "4.0.3" + } + } + } + }, + { + "id": "··yallist@3.1.1", + "node_modules": { + "yallist": { + "pkg": { + "name": "yallist", + "version": "3.1.1" + } + } + } + }, + { + "id": "··yallist@4.0.0", + "node_modules": { + "yallist": { + "pkg": { + "name": "yallist", + "version": "4.0.0" + } + } + } + }, + { + "id": "··yaml@1.10.3", + "node_modules": { + "yaml": { + "pkg": { + "name": "yaml", + "version": "1.10.3" + } + } + } + }, + { + "id": "··yargs-parser@18.1.3", + "node_modules": { + "camelcase": { + "link": "../../··camelcase@5.3.1/node_modules/camelcase" + }, + "decamelize": { + "link": "../../··decamelize@1.2.0/node_modules/decamelize" + }, + "yargs-parser": { + "pkg": { + "name": "yargs-parser", + "version": "18.1.3" + } + } + } + }, + { + "id": "··yargs@15.4.1", + "node_modules": { + "cliui": { + "link": "../../··cliui@6.0.0/node_modules/cliui" + }, + "decamelize": { + "link": "../../··decamelize@1.2.0/node_modules/decamelize" + }, + "find-up": { + "link": "../../··find-up@4.1.0/node_modules/find-up" + }, + "get-caller-file": { + "link": "../../··get-caller-file@2.0.5/node_modules/get-caller-file" + }, + "require-directory": { + "link": "../../··require-directory@2.1.1/node_modules/require-directory" + }, + "require-main-filename": { + "link": "../../··require-main-filename@2.0.0/node_modules/require-main-filename" + }, + "set-blocking": { + "link": "../../··set-blocking@2.0.0/node_modules/set-blocking" + }, + "string-width": { + "link": "../../··string-width@4.2.3/node_modules/string-width" + }, + "which-module": { + "link": "../../··which-module@2.0.1/node_modules/which-module" + }, + "y18n": { + "link": "../../··y18n@4.0.3/node_modules/y18n" + }, + "yargs": { + "pkg": { + "name": "yargs", + "version": "15.4.1" + } + }, + "yargs-parser": { + "link": "../../··yargs-parser@18.1.3/node_modules/yargs-parser" + } + } + } + ], + "importers": { + ".bin": { + "dir": true + }, + "@isaacs/string-locale-compare": { + "link": "../.vlt/··@isaacs§string-locale-compare@1.1.0/node_modules/@isaacs/string-locale-compare" + }, + "debug": { + "link": ".vlt/··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms/node_modules/debug" + }, + "left-pad": { + "link": ".vlt/··left-pad@1.3.0/node_modules/left-pad" + }, + "lp-alias": { + "link": ".vlt/·npm·left-pad@1.1.3/node_modules/left-pad" + }, + "lp-remote": { + "link": ".vlt/remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz/node_modules/left-pad" + }, + "ms-tgz": { + "link": ".vlt/file·vendor§ms-2.1.2.tgz/node_modules/ms" + }, + "react": { + "link": ".vlt/··react@18.2.0/node_modules/react" + }, + "semver_x": { + "link": ".vlt/·npm·semver@7.6.0/node_modules/semver" + }, + "slc-git": { + "link": ".vlt/git·github%3Aisaacs§string-locale-compare·v1.1.0/node_modules/@isaacs/string-locale-compare" + }, + "use-sync-external-store": { + "link": ".vlt/··use-sync-external-store@1.2.0/node_modules/use-sync-external-store" + } + }, + "members": {}, + "linkTargets": {} +} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.0-rc.14/README.md b/crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.0-rc.14/README.md new file mode 100644 index 00000000..3f345794 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.0-rc.14/README.md @@ -0,0 +1,21 @@ +# vlt 1.0.0-rc.14 installed layout + +`listing.json` is the on-disk layout real vlt 1.0.0-rc.14 produced, captured with +`scripts/capture-vlt-tree.mjs` right after a cold `vlt install` (isolated +XDG dirs and VLT_CACHE, VLT_TELEMETRY=0, LANG=C, no lockfile). Store entry +names are byte-exact; the crawler tests in `crawler_npm_e2e.rs` stage the +listing as real directories, package.json files and relative symlinks. + +Project (`package.json` dependencies): + +- "left-pad": "1.3.0", "debug": "4.3.4", "@isaacs/string-locale-compare": "1.1.0" +- "react": "18.2.0", "use-sync-external-store": "1.2.0" +- "lp-alias": "npm:left-pad@1.1.3", "semver_x": "npm:semver@7.6.0" +- "slc-git": "github:isaacs/string-locale-compare#v1.1.0" +- "lp-remote": "https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz" +- "ms-tgz": "file:./vendor/ms-2.1.2.tgz" +- "localdir": "file:./vendor/localdir" (a left-pad@1.3.0 copy renamed localdir) + +`vlt.json`: `{"config": {"registries": {"npm": "https://registry.npmjs.org/"}}, "modifiers": {":root > #debug > #ms": "2.1.3"}}`. + +The git dependency's devDependencies account for most store entries. diff --git a/crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.0-rc.14/listing.json b/crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.0-rc.14/listing.json new file mode 100644 index 00000000..6383b3bc --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.0-rc.14/listing.json @@ -0,0 +1,4596 @@ +{ + "vlt": "1.0.0-rc.14", + "lockfileVersion": 0, + "storeFiles": [ + "vlt.json" + ], + "hoist": { + "@babel/code-frame": { + "link": "../../·npm·@babel§code-frame@7.29.7/node_modules/@babel/code-frame" + }, + "@babel/compat-data": { + "link": "../../·npm·@babel§compat-data@7.29.7/node_modules/@babel/compat-data" + }, + "@babel/core": { + "link": "../../·npm·@babel§core@7.29.7/node_modules/@babel/core" + }, + "@babel/generator": { + "link": "../../·npm·@babel§generator@7.29.8/node_modules/@babel/generator" + }, + "@babel/helper-compilation-targets": { + "link": "../../·npm·@babel§helper-compilation-targets@7.29.7/node_modules/@babel/helper-compilation-targets" + }, + "@babel/helper-globals": { + "link": "../../·npm·@babel§helper-globals@7.29.7/node_modules/@babel/helper-globals" + }, + "@babel/helper-module-imports": { + "link": "../../·npm·@babel§helper-module-imports@7.29.7/node_modules/@babel/helper-module-imports" + }, + "@babel/helper-module-transforms": { + "link": "../../·npm·@babel§helper-module-transforms@7.29.7·%E1%B9%97%3A3/node_modules/@babel/helper-module-transforms" + }, + "@babel/helper-string-parser": { + "link": "../../·npm·@babel§helper-string-parser@7.29.7/node_modules/@babel/helper-string-parser" + }, + "@babel/helper-validator-identifier": { + "link": "../../·npm·@babel§helper-validator-identifier@7.29.7/node_modules/@babel/helper-validator-identifier" + }, + "@babel/helper-validator-option": { + "link": "../../·npm·@babel§helper-validator-option@7.29.7/node_modules/@babel/helper-validator-option" + }, + "@babel/helpers": { + "link": "../../·npm·@babel§helpers@7.29.7/node_modules/@babel/helpers" + }, + "@babel/parser": { + "link": "../../·npm·@babel§parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/template": { + "link": "../../·npm·@babel§template@7.29.7/node_modules/@babel/template" + }, + "@babel/traverse": { + "link": "../../·npm·@babel§traverse@7.29.8/node_modules/@babel/traverse" + }, + "@babel/types": { + "link": "../../·npm·@babel§types@7.29.8/node_modules/@babel/types" + }, + "@isaacs/string-locale-compare": { + "link": "../../·npm·@isaacs§string-locale-compare@1.1.0/node_modules/@isaacs/string-locale-compare" + }, + "@istanbuljs/load-nyc-config": { + "link": "../../·npm·@istanbuljs§load-nyc-config@1.1.0/node_modules/@istanbuljs/load-nyc-config" + }, + "@istanbuljs/schema": { + "link": "../../·npm·@istanbuljs§schema@0.1.6/node_modules/@istanbuljs/schema" + }, + "@jridgewell/gen-mapping": { + "link": "../../·npm·@jridgewell§gen-mapping@0.3.13/node_modules/@jridgewell/gen-mapping" + }, + "@jridgewell/remapping": { + "link": "../../·npm·@jridgewell§remapping@2.3.5/node_modules/@jridgewell/remapping" + }, + "@jridgewell/resolve-uri": { + "link": "../../·npm·@jridgewell§resolve-uri@3.1.2/node_modules/@jridgewell/resolve-uri" + }, + "@jridgewell/sourcemap-codec": { + "link": "../../·npm·@jridgewell§sourcemap-codec@1.6.0/node_modules/@jridgewell/sourcemap-codec" + }, + "@jridgewell/trace-mapping": { + "link": "../../·npm·@jridgewell§trace-mapping@0.3.31/node_modules/@jridgewell/trace-mapping" + }, + "aggregate-error": { + "link": "../·npm·aggregate-error@3.1.0/node_modules/aggregate-error" + }, + "ajv": { + "link": "../·npm·ajv@6.15.0/node_modules/ajv" + }, + "ansi-regex": { + "link": "../·npm·ansi-regex@5.0.1/node_modules/ansi-regex" + }, + "ansi-styles": { + "link": "../·npm·ansi-styles@4.3.0/node_modules/ansi-styles" + }, + "anymatch": { + "link": "../·npm·anymatch@3.1.3/node_modules/anymatch" + }, + "append-transform": { + "link": "../·npm·append-transform@2.0.0/node_modules/append-transform" + }, + "archy": { + "link": "../·npm·archy@1.0.0/node_modules/archy" + }, + "argparse": { + "link": "../·npm·argparse@1.0.10/node_modules/argparse" + }, + "asn1": { + "link": "../·npm·asn1@0.2.6/node_modules/asn1" + }, + "assert-plus": { + "link": "../·npm·assert-plus@1.0.0/node_modules/assert-plus" + }, + "async-hook-domain": { + "link": "../·npm·async-hook-domain@2.0.4/node_modules/async-hook-domain" + }, + "asynckit": { + "link": "../·npm·asynckit@0.4.0/node_modules/asynckit" + }, + "aws-sign2": { + "link": "../·npm·aws-sign2@0.7.0/node_modules/aws-sign2" + }, + "aws4": { + "link": "../·npm·aws4@1.13.2/node_modules/aws4" + }, + "balanced-match": { + "link": "../·npm·balanced-match@1.0.2/node_modules/balanced-match" + }, + "baseline-browser-mapping": { + "link": "../·npm·baseline-browser-mapping@2.11.26/node_modules/baseline-browser-mapping" + }, + "bcrypt-pbkdf": { + "link": "../·npm·bcrypt-pbkdf@1.0.2/node_modules/bcrypt-pbkdf" + }, + "binary-extensions": { + "link": "../·npm·binary-extensions@2.3.0/node_modules/binary-extensions" + }, + "bind-obj-methods": { + "link": "../·npm·bind-obj-methods@3.0.0/node_modules/bind-obj-methods" + }, + "brace-expansion": { + "link": "../·npm·brace-expansion@1.1.21/node_modules/brace-expansion" + }, + "braces": { + "link": "../·npm·braces@3.0.3/node_modules/braces" + }, + "browserslist": { + "link": "../·npm·browserslist@4.29.1/node_modules/browserslist" + }, + "buffer-from": { + "link": "../·npm·buffer-from@1.1.2/node_modules/buffer-from" + }, + "caching-transform": { + "link": "../·npm·caching-transform@4.0.0/node_modules/caching-transform" + }, + "camelcase": { + "link": "../·npm·camelcase@5.3.1/node_modules/camelcase" + }, + "caniuse-lite": { + "link": "../·npm·caniuse-lite@1.0.30001812/node_modules/caniuse-lite" + }, + "caseless": { + "link": "../·npm·caseless@0.12.0/node_modules/caseless" + }, + "chokidar": { + "link": "../·npm·chokidar@3.6.0/node_modules/chokidar" + }, + "clean-stack": { + "link": "../·npm·clean-stack@2.2.0/node_modules/clean-stack" + }, + "cliui": { + "link": "../·npm·cliui@7.0.4/node_modules/cliui" + }, + "color-convert": { + "link": "../·npm·color-convert@2.0.1/node_modules/color-convert" + }, + "color-name": { + "link": "../·npm·color-name@1.1.4/node_modules/color-name" + }, + "color-support": { + "link": "../·npm·color-support@1.1.3/node_modules/color-support" + }, + "combined-stream": { + "link": "../·npm·combined-stream@1.0.8/node_modules/combined-stream" + }, + "commondir": { + "link": "../·npm·commondir@1.0.1/node_modules/commondir" + }, + "concat-map": { + "link": "../·npm·concat-map@0.0.1/node_modules/concat-map" + }, + "convert-source-map": { + "link": "../·npm·convert-source-map@2.0.0/node_modules/convert-source-map" + }, + "core-util-is": { + "link": "../·npm·core-util-is@1.0.2/node_modules/core-util-is" + }, + "coveralls": { + "link": "../·npm·coveralls@3.1.1/node_modules/coveralls" + }, + "cross-spawn": { + "link": "../·npm·cross-spawn@7.0.6/node_modules/cross-spawn" + }, + "dashdash": { + "link": "../·npm·dashdash@1.14.1/node_modules/dashdash" + }, + "debug": { + "link": "../·npm·debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms/node_modules/debug" + }, + "decamelize": { + "link": "../·npm·decamelize@1.2.0/node_modules/decamelize" + }, + "default-require-extensions": { + "link": "../·npm·default-require-extensions@3.0.1/node_modules/default-require-extensions" + }, + "delayed-stream": { + "link": "../·npm·delayed-stream@1.0.0/node_modules/delayed-stream" + }, + "diff": { + "link": "../·npm·diff@4.0.4/node_modules/diff" + }, + "ecc-jsbn": { + "link": "../·npm·ecc-jsbn@0.1.2/node_modules/ecc-jsbn" + }, + "electron-to-chromium": { + "link": "../·npm·electron-to-chromium@1.5.439/node_modules/electron-to-chromium" + }, + "emoji-regex": { + "link": "../·npm·emoji-regex@8.0.0/node_modules/emoji-regex" + }, + "es6-error": { + "link": "../·npm·es6-error@4.1.1/node_modules/es6-error" + }, + "escalade": { + "link": "../·npm·escalade@3.2.0/node_modules/escalade" + }, + "escape-string-regexp": { + "link": "../·npm·escape-string-regexp@2.0.0/node_modules/escape-string-regexp" + }, + "esprima": { + "link": "../·npm·esprima@4.0.1/node_modules/esprima" + }, + "events-to-array": { + "link": "../·npm·events-to-array@1.1.2/node_modules/events-to-array" + }, + "extend": { + "link": "../·npm·extend@3.0.2/node_modules/extend" + }, + "extsprintf": { + "link": "../·npm·extsprintf@1.3.0/node_modules/extsprintf" + }, + "fast-deep-equal": { + "link": "../·npm·fast-deep-equal@3.1.3/node_modules/fast-deep-equal" + }, + "fast-json-stable-stringify": { + "link": "../·npm·fast-json-stable-stringify@2.1.0/node_modules/fast-json-stable-stringify" + }, + "fill-range": { + "link": "../·npm·fill-range@7.1.1/node_modules/fill-range" + }, + "find-cache-dir": { + "link": "../·npm·find-cache-dir@3.3.2/node_modules/find-cache-dir" + }, + "find-up": { + "link": "../·npm·find-up@4.1.0/node_modules/find-up" + }, + "findit": { + "link": "../·npm·findit@2.0.0/node_modules/findit" + }, + "foreground-child": { + "link": "../·npm·foreground-child@2.0.0/node_modules/foreground-child" + }, + "forever-agent": { + "link": "../·npm·forever-agent@0.6.1/node_modules/forever-agent" + }, + "form-data": { + "link": "../·npm·form-data@2.3.3/node_modules/form-data" + }, + "fromentries": { + "link": "../·npm·fromentries@1.3.2/node_modules/fromentries" + }, + "fs-exists-cached": { + "link": "../·npm·fs-exists-cached@1.0.0/node_modules/fs-exists-cached" + }, + "fs.realpath": { + "link": "../·npm·fs.realpath@1.0.0/node_modules/fs.realpath" + }, + "fsevents": { + "link": "../·npm·fsevents@2.3.3/node_modules/fsevents" + }, + "function-loop": { + "link": "../·npm·function-loop@2.0.1/node_modules/function-loop" + }, + "gensync": { + "link": "../·npm·gensync@1.0.0-beta.2/node_modules/gensync" + }, + "get-caller-file": { + "link": "../·npm·get-caller-file@2.0.5/node_modules/get-caller-file" + }, + "get-package-type": { + "link": "../·npm·get-package-type@0.1.0/node_modules/get-package-type" + }, + "getpass": { + "link": "../·npm·getpass@0.1.7/node_modules/getpass" + }, + "glob": { + "link": "../·npm·glob@7.2.3/node_modules/glob" + }, + "glob-parent": { + "link": "../·npm·glob-parent@5.1.2/node_modules/glob-parent" + }, + "graceful-fs": { + "link": "../·npm·graceful-fs@4.2.11/node_modules/graceful-fs" + }, + "har-schema": { + "link": "../·npm·har-schema@2.0.0/node_modules/har-schema" + }, + "har-validator": { + "link": "../·npm·har-validator@5.1.5/node_modules/har-validator" + }, + "has-flag": { + "link": "../·npm·has-flag@4.0.0/node_modules/has-flag" + }, + "hasha": { + "link": "../·npm·hasha@5.2.2/node_modules/hasha" + }, + "html-escaper": { + "link": "../·npm·html-escaper@2.0.2/node_modules/html-escaper" + }, + "http-signature": { + "link": "../·npm·http-signature@1.2.0/node_modules/http-signature" + }, + "imurmurhash": { + "link": "../·npm·imurmurhash@0.1.4/node_modules/imurmurhash" + }, + "indent-string": { + "link": "../·npm·indent-string@4.0.0/node_modules/indent-string" + }, + "inflight": { + "link": "../·npm·inflight@1.0.6/node_modules/inflight" + }, + "inherits": { + "link": "../·npm·inherits@2.0.4/node_modules/inherits" + }, + "is-binary-path": { + "link": "../·npm·is-binary-path@2.1.0/node_modules/is-binary-path" + }, + "is-extglob": { + "link": "../·npm·is-extglob@2.1.1/node_modules/is-extglob" + }, + "is-fullwidth-code-point": { + "link": "../·npm·is-fullwidth-code-point@3.0.0/node_modules/is-fullwidth-code-point" + }, + "is-glob": { + "link": "../·npm·is-glob@4.0.3/node_modules/is-glob" + }, + "is-number": { + "link": "../·npm·is-number@7.0.0/node_modules/is-number" + }, + "is-stream": { + "link": "../·npm·is-stream@2.0.1/node_modules/is-stream" + }, + "is-typedarray": { + "link": "../·npm·is-typedarray@1.0.0/node_modules/is-typedarray" + }, + "is-windows": { + "link": "../·npm·is-windows@1.0.2/node_modules/is-windows" + }, + "isexe": { + "link": "../·npm·isexe@2.0.0/node_modules/isexe" + }, + "isstream": { + "link": "../·npm·isstream@0.1.2/node_modules/isstream" + }, + "istanbul-lib-coverage": { + "link": "../·npm·istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-hook": { + "link": "../·npm·istanbul-lib-hook@3.0.0/node_modules/istanbul-lib-hook" + }, + "istanbul-lib-instrument": { + "link": "../·npm·istanbul-lib-instrument@4.0.3/node_modules/istanbul-lib-instrument" + }, + "istanbul-lib-processinfo": { + "link": "../·npm·istanbul-lib-processinfo@2.0.3/node_modules/istanbul-lib-processinfo" + }, + "istanbul-lib-report": { + "link": "../·npm·istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report" + }, + "istanbul-lib-source-maps": { + "link": "../·npm·istanbul-lib-source-maps@4.0.1/node_modules/istanbul-lib-source-maps" + }, + "istanbul-reports": { + "link": "../·npm·istanbul-reports@3.2.0/node_modules/istanbul-reports" + }, + "jackspeak": { + "link": "../·npm·jackspeak@1.4.2/node_modules/jackspeak" + }, + "js-tokens": { + "link": "../·npm·js-tokens@4.0.0/node_modules/js-tokens" + }, + "js-yaml": { + "link": "../·npm·js-yaml@3.15.2/node_modules/js-yaml" + }, + "jsbn": { + "link": "../·npm·jsbn@0.1.1/node_modules/jsbn" + }, + "jsesc": { + "link": "../·npm·jsesc@3.1.0/node_modules/jsesc" + }, + "json-schema": { + "link": "../·npm·json-schema@0.4.0/node_modules/json-schema" + }, + "json-schema-traverse": { + "link": "../·npm·json-schema-traverse@0.4.1/node_modules/json-schema-traverse" + }, + "json-stringify-safe": { + "link": "../·npm·json-stringify-safe@5.0.1/node_modules/json-stringify-safe" + }, + "json5": { + "link": "../·npm·json5@2.2.3/node_modules/json5" + }, + "jsprim": { + "link": "../·npm·jsprim@1.4.2/node_modules/jsprim" + }, + "lcov-parse": { + "link": "../·npm·lcov-parse@1.0.0/node_modules/lcov-parse" + }, + "left-pad": { + "link": "../·npm·left-pad@1.3.0/node_modules/left-pad" + }, + "libtap": { + "link": "../·npm·libtap@1.4.1/node_modules/libtap" + }, + "locate-path": { + "link": "../·npm·locate-path@5.0.0/node_modules/locate-path" + }, + "lodash.flattendeep": { + "link": "../·npm·lodash.flattendeep@4.4.0/node_modules/lodash.flattendeep" + }, + "log-driver": { + "link": "../·npm·log-driver@1.2.7/node_modules/log-driver" + }, + "loose-envify": { + "link": "../·npm·loose-envify@1.4.0/node_modules/loose-envify" + }, + "lru-cache": { + "link": "../·npm·lru-cache@6.0.0/node_modules/lru-cache" + }, + "make-dir": { + "link": "../·npm·make-dir@4.0.0/node_modules/make-dir" + }, + "mime-db": { + "link": "../·npm·mime-db@1.52.0/node_modules/mime-db" + }, + "mime-types": { + "link": "../·npm·mime-types@2.1.35/node_modules/mime-types" + }, + "minimatch": { + "link": "../·npm·minimatch@3.1.5/node_modules/minimatch" + }, + "minimist": { + "link": "../·npm·minimist@1.2.8/node_modules/minimist" + }, + "minipass": { + "link": "../·npm·minipass@3.3.6/node_modules/minipass" + }, + "mkdirp": { + "link": "../·npm·mkdirp@1.0.4/node_modules/mkdirp" + }, + "ms-tgz": { + "link": "../file·vendor§ms-2.1.2.tgz/node_modules/ms" + }, + "node-preload": { + "link": "../·npm·node-preload@0.2.1/node_modules/node-preload" + }, + "node-releases": { + "link": "../·npm·node-releases@2.0.57/node_modules/node-releases" + }, + "normalize-path": { + "link": "../·npm·normalize-path@3.0.0/node_modules/normalize-path" + }, + "nyc": { + "link": "../·npm·nyc@15.1.0/node_modules/nyc" + }, + "oauth-sign": { + "link": "../·npm·oauth-sign@0.9.0/node_modules/oauth-sign" + }, + "once": { + "link": "../·npm·once@1.4.0/node_modules/once" + }, + "opener": { + "link": "../·npm·opener@1.5.2/node_modules/opener" + }, + "own-or": { + "link": "../·npm·own-or@1.0.0/node_modules/own-or" + }, + "own-or-env": { + "link": "../·npm·own-or-env@1.0.2/node_modules/own-or-env" + }, + "p-limit": { + "link": "../·npm·p-limit@2.3.0/node_modules/p-limit" + }, + "p-locate": { + "link": "../·npm·p-locate@4.1.0/node_modules/p-locate" + }, + "p-map": { + "link": "../·npm·p-map@3.0.0/node_modules/p-map" + }, + "p-try": { + "link": "../·npm·p-try@2.2.0/node_modules/p-try" + }, + "package-hash": { + "link": "../·npm·package-hash@4.0.0/node_modules/package-hash" + }, + "path-exists": { + "link": "../·npm·path-exists@4.0.0/node_modules/path-exists" + }, + "path-is-absolute": { + "link": "../·npm·path-is-absolute@1.0.1/node_modules/path-is-absolute" + }, + "path-key": { + "link": "../·npm·path-key@3.1.1/node_modules/path-key" + }, + "performance-now": { + "link": "../·npm·performance-now@2.1.0/node_modules/performance-now" + }, + "picocolors": { + "link": "../·npm·picocolors@1.1.1/node_modules/picocolors" + }, + "picomatch": { + "link": "../·npm·picomatch@2.3.2/node_modules/picomatch" + }, + "pkg-dir": { + "link": "../·npm·pkg-dir@4.2.0/node_modules/pkg-dir" + }, + "process-on-spawn": { + "link": "../·npm·process-on-spawn@1.1.0/node_modules/process-on-spawn" + }, + "psl": { + "link": "../·npm·psl@1.15.0/node_modules/psl" + }, + "punycode": { + "link": "../·npm·punycode@2.3.1/node_modules/punycode" + }, + "qs": { + "link": "../·npm·qs@6.5.5/node_modules/qs" + }, + "react": { + "link": "../·npm·react@18.2.0/node_modules/react" + }, + "readdirp": { + "link": "../·npm·readdirp@3.6.0/node_modules/readdirp" + }, + "release-zalgo": { + "link": "../·npm·release-zalgo@1.0.0/node_modules/release-zalgo" + }, + "request": { + "link": "../·npm·request@2.88.2/node_modules/request" + }, + "require-directory": { + "link": "../·npm·require-directory@2.1.1/node_modules/require-directory" + }, + "require-main-filename": { + "link": "../·npm·require-main-filename@2.0.0/node_modules/require-main-filename" + }, + "resolve-from": { + "link": "../·npm·resolve-from@5.0.0/node_modules/resolve-from" + }, + "rimraf": { + "link": "../·npm·rimraf@3.0.2/node_modules/rimraf" + }, + "safe-buffer": { + "link": "../·npm·safe-buffer@5.2.1/node_modules/safe-buffer" + }, + "safer-buffer": { + "link": "../·npm·safer-buffer@2.1.2/node_modules/safer-buffer" + }, + "semver_x": { + "link": "../·npm·semver@7.6.0/node_modules/semver" + }, + "set-blocking": { + "link": "../·npm·set-blocking@2.0.0/node_modules/set-blocking" + }, + "shebang-command": { + "link": "../·npm·shebang-command@2.0.0/node_modules/shebang-command" + }, + "shebang-regex": { + "link": "../·npm·shebang-regex@3.0.0/node_modules/shebang-regex" + }, + "signal-exit": { + "link": "../·npm·signal-exit@3.0.7/node_modules/signal-exit" + }, + "source-map": { + "link": "../·npm·source-map@0.6.1/node_modules/source-map" + }, + "source-map-support": { + "link": "../·npm·source-map-support@0.5.21/node_modules/source-map-support" + }, + "spawn-wrap": { + "link": "../·npm·spawn-wrap@2.0.0/node_modules/spawn-wrap" + }, + "sprintf-js": { + "link": "../·npm·sprintf-js@1.0.3/node_modules/sprintf-js" + }, + "sshpk": { + "link": "../·npm·sshpk@1.18.0/node_modules/sshpk" + }, + "stack-utils": { + "link": "../·npm·stack-utils@2.0.6/node_modules/stack-utils" + }, + "string-width": { + "link": "../·npm·string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../·npm·strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "strip-bom": { + "link": "../·npm·strip-bom@4.0.0/node_modules/strip-bom" + }, + "supports-color": { + "link": "../·npm·supports-color@7.2.0/node_modules/supports-color" + }, + "tap": { + "link": "../·npm·tap@15.2.3/node_modules/tap" + }, + "tap-mocha-reporter": { + "link": "../·npm·tap-mocha-reporter@5.0.4/node_modules/tap-mocha-reporter" + }, + "tap-parser": { + "link": "../·npm·tap-parser@11.0.2/node_modules/tap-parser" + }, + "tap-yaml": { + "link": "../·npm·tap-yaml@1.0.2/node_modules/tap-yaml" + }, + "tcompare": { + "link": "../·npm·tcompare@5.0.7/node_modules/tcompare" + }, + "test-exclude": { + "link": "../·npm·test-exclude@6.0.0/node_modules/test-exclude" + }, + "to-regex-range": { + "link": "../·npm·to-regex-range@5.0.1/node_modules/to-regex-range" + }, + "tough-cookie": { + "link": "../·npm·tough-cookie@2.5.0/node_modules/tough-cookie" + }, + "trivial-deferred": { + "link": "../·npm·trivial-deferred@1.1.2/node_modules/trivial-deferred" + }, + "tunnel-agent": { + "link": "../·npm·tunnel-agent@0.6.0/node_modules/tunnel-agent" + }, + "tweetnacl": { + "link": "../·npm·tweetnacl@0.14.5/node_modules/tweetnacl" + }, + "type-fest": { + "link": "../·npm·type-fest@0.8.1/node_modules/type-fest" + }, + "typedarray-to-buffer": { + "link": "../·npm·typedarray-to-buffer@3.1.5/node_modules/typedarray-to-buffer" + }, + "unicode-length": { + "link": "../·npm·unicode-length@2.1.0/node_modules/unicode-length" + }, + "update-browserslist-db": { + "link": "../·npm·update-browserslist-db@1.3.3·%E1%B9%97%3A6/node_modules/update-browserslist-db" + }, + "uri-js": { + "link": "../·npm·uri-js@4.4.1/node_modules/uri-js" + }, + "use-sync-external-store": { + "link": "../·npm·use-sync-external-store@1.2.0/node_modules/use-sync-external-store" + }, + "uuid": { + "link": "../·npm·uuid@8.3.2/node_modules/uuid" + }, + "verror": { + "link": "../·npm·verror@1.10.0/node_modules/verror" + }, + "which": { + "link": "../·npm·which@2.0.2/node_modules/which" + }, + "which-module": { + "link": "../·npm·which-module@2.0.1/node_modules/which-module" + }, + "wrap-ansi": { + "link": "../·npm·wrap-ansi@7.0.0/node_modules/wrap-ansi" + }, + "wrappy": { + "link": "../·npm·wrappy@1.0.2/node_modules/wrappy" + }, + "write-file-atomic": { + "link": "../·npm·write-file-atomic@3.0.3/node_modules/write-file-atomic" + }, + "y18n": { + "link": "../·npm·y18n@4.0.3/node_modules/y18n" + }, + "yallist": { + "link": "../·npm·yallist@4.0.0/node_modules/yallist" + }, + "yaml": { + "link": "../·npm·yaml@1.10.3/node_modules/yaml" + }, + "yargs": { + "link": "../·npm·yargs@15.4.1/node_modules/yargs" + }, + "yargs-parser": { + "link": "../·npm·yargs-parser@18.1.3/node_modules/yargs-parser" + } + }, + "store": [ + { + "id": "file·vendor§ms-2.1.2.tgz", + "node_modules": { + "ms": { + "pkg": { + "name": "ms", + "version": "2.1.2" + } + } + } + }, + { + "id": "git·github%3Aisaacs§string-locale-compare·v1.1.0", + "node_modules": { + ".bin": { + "dir": true + }, + "@isaacs/string-locale-compare": { + "pkg": { + "name": "@isaacs/string-locale-compare", + "version": "1.1.0" + } + }, + "tap": { + "link": "../../·npm·tap@15.2.3/node_modules/tap" + } + } + }, + { + "id": "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "node_modules": { + "left-pad": { + "pkg": { + "name": "left-pad", + "version": "1.2.0" + } + } + } + }, + { + "id": "·npm·@babel§code-frame@7.29.7", + "node_modules": { + "@babel/code-frame": { + "pkg": { + "name": "@babel/code-frame", + "version": "7.29.7" + } + }, + "@babel/helper-validator-identifier": { + "link": "../../../·npm·@babel§helper-validator-identifier@7.29.7/node_modules/@babel/helper-validator-identifier" + }, + "js-tokens": { + "link": "../../·npm·js-tokens@4.0.0/node_modules/js-tokens" + }, + "picocolors": { + "link": "../../·npm·picocolors@1.1.1/node_modules/picocolors" + } + } + }, + { + "id": "·npm·@babel§compat-data@7.29.7", + "node_modules": { + "@babel/compat-data": { + "pkg": { + "name": "@babel/compat-data", + "version": "7.29.7" + } + } + } + }, + { + "id": "·npm·@babel§core@7.29.7", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/code-frame": { + "link": "../../../·npm·@babel§code-frame@7.29.7/node_modules/@babel/code-frame" + }, + "@babel/core": { + "pkg": { + "name": "@babel/core", + "version": "7.29.7" + } + }, + "@babel/generator": { + "link": "../../../·npm·@babel§generator@7.29.8/node_modules/@babel/generator" + }, + "@babel/helper-compilation-targets": { + "link": "../../../·npm·@babel§helper-compilation-targets@7.29.7/node_modules/@babel/helper-compilation-targets" + }, + "@babel/helper-module-transforms": { + "link": "../../../·npm·@babel§helper-module-transforms@7.29.7·%E1%B9%97%3A3/node_modules/@babel/helper-module-transforms" + }, + "@babel/helpers": { + "link": "../../../·npm·@babel§helpers@7.29.7/node_modules/@babel/helpers" + }, + "@babel/parser": { + "link": "../../../·npm·@babel§parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/template": { + "link": "../../../·npm·@babel§template@7.29.7/node_modules/@babel/template" + }, + "@babel/traverse": { + "link": "../../../·npm·@babel§traverse@7.29.8/node_modules/@babel/traverse" + }, + "@babel/types": { + "link": "../../../·npm·@babel§types@7.29.8/node_modules/@babel/types" + }, + "@jridgewell/remapping": { + "link": "../../../·npm·@jridgewell§remapping@2.3.5/node_modules/@jridgewell/remapping" + }, + "convert-source-map": { + "link": "../../·npm·convert-source-map@2.0.0/node_modules/convert-source-map" + }, + "debug": { + "link": "../../·npm·debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms/node_modules/debug" + }, + "gensync": { + "link": "../../·npm·gensync@1.0.0-beta.2/node_modules/gensync" + }, + "json5": { + "link": "../../·npm·json5@2.2.3/node_modules/json5" + }, + "semver": { + "link": "../../·npm·semver@6.3.1/node_modules/semver" + } + } + }, + { + "id": "·npm·@babel§generator@7.29.8", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/generator": { + "pkg": { + "name": "@babel/generator", + "version": "7.29.8" + } + }, + "@babel/parser": { + "link": "../../../·npm·@babel§parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/types": { + "link": "../../../·npm·@babel§types@7.29.8/node_modules/@babel/types" + }, + "@jridgewell/gen-mapping": { + "link": "../../../·npm·@jridgewell§gen-mapping@0.3.13/node_modules/@jridgewell/gen-mapping" + }, + "@jridgewell/trace-mapping": { + "link": "../../../·npm·@jridgewell§trace-mapping@0.3.31/node_modules/@jridgewell/trace-mapping" + }, + "jsesc": { + "link": "../../·npm·jsesc@3.1.0/node_modules/jsesc" + } + } + }, + { + "id": "·npm·@babel§helper-compilation-targets@7.29.7", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/compat-data": { + "link": "../../../·npm·@babel§compat-data@7.29.7/node_modules/@babel/compat-data" + }, + "@babel/helper-compilation-targets": { + "pkg": { + "name": "@babel/helper-compilation-targets", + "version": "7.29.7" + } + }, + "@babel/helper-validator-option": { + "link": "../../../·npm·@babel§helper-validator-option@7.29.7/node_modules/@babel/helper-validator-option" + }, + "browserslist": { + "link": "../../·npm·browserslist@4.29.1/node_modules/browserslist" + }, + "lru-cache": { + "link": "../../·npm·lru-cache@5.1.1/node_modules/lru-cache" + }, + "semver": { + "link": "../../·npm·semver@6.3.1/node_modules/semver" + } + } + }, + { + "id": "·npm·@babel§helper-globals@7.29.7", + "node_modules": { + "@babel/helper-globals": { + "pkg": { + "name": "@babel/helper-globals", + "version": "7.29.7" + } + } + } + }, + { + "id": "·npm·@babel§helper-module-imports@7.29.7", + "node_modules": { + "@babel/helper-module-imports": { + "pkg": { + "name": "@babel/helper-module-imports", + "version": "7.29.7" + } + }, + "@babel/traverse": { + "link": "../../../·npm·@babel§traverse@7.29.8/node_modules/@babel/traverse" + }, + "@babel/types": { + "link": "../../../·npm·@babel§types@7.29.8/node_modules/@babel/types" + } + } + }, + { + "id": "·npm·@babel§helper-module-transforms@7.29.7·%E1%B9%97%3A3", + "node_modules": { + "@babel/core": { + "link": "../../../·npm·@babel§core@7.29.7/node_modules/@babel/core" + }, + "@babel/helper-module-imports": { + "link": "../../../·npm·@babel§helper-module-imports@7.29.7/node_modules/@babel/helper-module-imports" + }, + "@babel/helper-module-transforms": { + "pkg": { + "name": "@babel/helper-module-transforms", + "version": "7.29.7" + } + }, + "@babel/helper-validator-identifier": { + "link": "../../../·npm·@babel§helper-validator-identifier@7.29.7/node_modules/@babel/helper-validator-identifier" + }, + "@babel/traverse": { + "link": "../../../·npm·@babel§traverse@7.29.8/node_modules/@babel/traverse" + } + } + }, + { + "id": "·npm·@babel§helper-string-parser@7.29.7", + "node_modules": { + "@babel/helper-string-parser": { + "pkg": { + "name": "@babel/helper-string-parser", + "version": "7.29.7" + } + } + } + }, + { + "id": "·npm·@babel§helper-validator-identifier@7.29.7", + "node_modules": { + "@babel/helper-validator-identifier": { + "pkg": { + "name": "@babel/helper-validator-identifier", + "version": "7.29.7" + } + } + } + }, + { + "id": "·npm·@babel§helper-validator-option@7.29.7", + "node_modules": { + "@babel/helper-validator-option": { + "pkg": { + "name": "@babel/helper-validator-option", + "version": "7.29.7" + } + } + } + }, + { + "id": "·npm·@babel§helpers@7.29.7", + "node_modules": { + "@babel/helpers": { + "pkg": { + "name": "@babel/helpers", + "version": "7.29.7" + } + }, + "@babel/template": { + "link": "../../../·npm·@babel§template@7.29.7/node_modules/@babel/template" + }, + "@babel/types": { + "link": "../../../·npm·@babel§types@7.29.8/node_modules/@babel/types" + } + } + }, + { + "id": "·npm·@babel§parser@7.29.9", + "node_modules": { + "@babel/parser": { + "pkg": { + "name": "@babel/parser", + "version": "7.29.9" + } + }, + "@babel/types": { + "link": "../../../·npm·@babel§types@7.29.8/node_modules/@babel/types" + } + } + }, + { + "id": "·npm·@babel§template@7.29.7", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/code-frame": { + "link": "../../../·npm·@babel§code-frame@7.29.7/node_modules/@babel/code-frame" + }, + "@babel/parser": { + "link": "../../../·npm·@babel§parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/template": { + "pkg": { + "name": "@babel/template", + "version": "7.29.7" + } + }, + "@babel/types": { + "link": "../../../·npm·@babel§types@7.29.8/node_modules/@babel/types" + } + } + }, + { + "id": "·npm·@babel§traverse@7.29.8", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/code-frame": { + "link": "../../../·npm·@babel§code-frame@7.29.7/node_modules/@babel/code-frame" + }, + "@babel/generator": { + "link": "../../../·npm·@babel§generator@7.29.8/node_modules/@babel/generator" + }, + "@babel/helper-globals": { + "link": "../../../·npm·@babel§helper-globals@7.29.7/node_modules/@babel/helper-globals" + }, + "@babel/parser": { + "link": "../../../·npm·@babel§parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/template": { + "link": "../../../·npm·@babel§template@7.29.7/node_modules/@babel/template" + }, + "@babel/traverse": { + "pkg": { + "name": "@babel/traverse", + "version": "7.29.8" + } + }, + "@babel/types": { + "link": "../../../·npm·@babel§types@7.29.8/node_modules/@babel/types" + }, + "debug": { + "link": "../../·npm·debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms/node_modules/debug" + } + } + }, + { + "id": "·npm·@babel§types@7.29.8", + "node_modules": { + "@babel/helper-string-parser": { + "link": "../../../·npm·@babel§helper-string-parser@7.29.7/node_modules/@babel/helper-string-parser" + }, + "@babel/helper-validator-identifier": { + "link": "../../../·npm·@babel§helper-validator-identifier@7.29.7/node_modules/@babel/helper-validator-identifier" + }, + "@babel/types": { + "pkg": { + "name": "@babel/types", + "version": "7.29.8" + } + } + } + }, + { + "id": "·npm·@isaacs§string-locale-compare@1.1.0", + "node_modules": { + "@isaacs/string-locale-compare": { + "pkg": { + "name": "@isaacs/string-locale-compare", + "version": "1.1.0" + } + } + } + }, + { + "id": "·npm·@istanbuljs§load-nyc-config@1.1.0", + "node_modules": { + ".bin": { + "dir": true + }, + "@istanbuljs/load-nyc-config": { + "pkg": { + "name": "@istanbuljs/load-nyc-config", + "version": "1.1.0" + } + }, + "camelcase": { + "link": "../../·npm·camelcase@5.3.1/node_modules/camelcase" + }, + "find-up": { + "link": "../../·npm·find-up@4.1.0/node_modules/find-up" + }, + "get-package-type": { + "link": "../../·npm·get-package-type@0.1.0/node_modules/get-package-type" + }, + "js-yaml": { + "link": "../../·npm·js-yaml@3.15.2/node_modules/js-yaml" + }, + "resolve-from": { + "link": "../../·npm·resolve-from@5.0.0/node_modules/resolve-from" + } + } + }, + { + "id": "·npm·@istanbuljs§schema@0.1.6", + "node_modules": { + "@istanbuljs/schema": { + "pkg": { + "name": "@istanbuljs/schema", + "version": "0.1.6" + } + } + } + }, + { + "id": "·npm·@jridgewell§gen-mapping@0.3.13", + "node_modules": { + "@jridgewell/gen-mapping": { + "pkg": { + "name": "@jridgewell/gen-mapping", + "version": "0.3.13" + } + }, + "@jridgewell/sourcemap-codec": { + "link": "../../../·npm·@jridgewell§sourcemap-codec@1.6.0/node_modules/@jridgewell/sourcemap-codec" + }, + "@jridgewell/trace-mapping": { + "link": "../../../·npm·@jridgewell§trace-mapping@0.3.31/node_modules/@jridgewell/trace-mapping" + } + } + }, + { + "id": "·npm·@jridgewell§remapping@2.3.5", + "node_modules": { + "@jridgewell/gen-mapping": { + "link": "../../../·npm·@jridgewell§gen-mapping@0.3.13/node_modules/@jridgewell/gen-mapping" + }, + "@jridgewell/remapping": { + "pkg": { + "name": "@jridgewell/remapping", + "version": "2.3.5" + } + }, + "@jridgewell/trace-mapping": { + "link": "../../../·npm·@jridgewell§trace-mapping@0.3.31/node_modules/@jridgewell/trace-mapping" + } + } + }, + { + "id": "·npm·@jridgewell§resolve-uri@3.1.2", + "node_modules": { + "@jridgewell/resolve-uri": { + "pkg": { + "name": "@jridgewell/resolve-uri", + "version": "3.1.2" + } + } + } + }, + { + "id": "·npm·@jridgewell§sourcemap-codec@1.6.0", + "node_modules": { + "@jridgewell/sourcemap-codec": { + "pkg": { + "name": "@jridgewell/sourcemap-codec", + "version": "1.6.0" + } + } + } + }, + { + "id": "·npm·@jridgewell§trace-mapping@0.3.31", + "node_modules": { + "@jridgewell/resolve-uri": { + "link": "../../../·npm·@jridgewell§resolve-uri@3.1.2/node_modules/@jridgewell/resolve-uri" + }, + "@jridgewell/sourcemap-codec": { + "link": "../../../·npm·@jridgewell§sourcemap-codec@1.6.0/node_modules/@jridgewell/sourcemap-codec" + }, + "@jridgewell/trace-mapping": { + "pkg": { + "name": "@jridgewell/trace-mapping", + "version": "0.3.31" + } + } + } + }, + { + "id": "·npm·aggregate-error@3.1.0", + "node_modules": { + "aggregate-error": { + "pkg": { + "name": "aggregate-error", + "version": "3.1.0" + } + }, + "clean-stack": { + "link": "../../·npm·clean-stack@2.2.0/node_modules/clean-stack" + }, + "indent-string": { + "link": "../../·npm·indent-string@4.0.0/node_modules/indent-string" + } + } + }, + { + "id": "·npm·ajv@6.15.0", + "node_modules": { + "ajv": { + "pkg": { + "name": "ajv", + "version": "6.15.0" + } + }, + "fast-deep-equal": { + "link": "../../·npm·fast-deep-equal@3.1.3/node_modules/fast-deep-equal" + }, + "fast-json-stable-stringify": { + "link": "../../·npm·fast-json-stable-stringify@2.1.0/node_modules/fast-json-stable-stringify" + }, + "json-schema-traverse": { + "link": "../../·npm·json-schema-traverse@0.4.1/node_modules/json-schema-traverse" + }, + "uri-js": { + "link": "../../·npm·uri-js@4.4.1/node_modules/uri-js" + } + } + }, + { + "id": "·npm·ansi-regex@5.0.1", + "node_modules": { + "ansi-regex": { + "pkg": { + "name": "ansi-regex", + "version": "5.0.1" + } + } + } + }, + { + "id": "·npm·ansi-styles@4.3.0", + "node_modules": { + "ansi-styles": { + "pkg": { + "name": "ansi-styles", + "version": "4.3.0" + } + }, + "color-convert": { + "link": "../../·npm·color-convert@2.0.1/node_modules/color-convert" + } + } + }, + { + "id": "·npm·anymatch@3.1.3", + "node_modules": { + "anymatch": { + "pkg": { + "name": "anymatch", + "version": "3.1.3" + } + }, + "normalize-path": { + "link": "../../·npm·normalize-path@3.0.0/node_modules/normalize-path" + }, + "picomatch": { + "link": "../../·npm·picomatch@2.3.2/node_modules/picomatch" + } + } + }, + { + "id": "·npm·append-transform@2.0.0", + "node_modules": { + "append-transform": { + "pkg": { + "name": "append-transform", + "version": "2.0.0" + } + }, + "default-require-extensions": { + "link": "../../·npm·default-require-extensions@3.0.1/node_modules/default-require-extensions" + } + } + }, + { + "id": "·npm·archy@1.0.0", + "node_modules": { + "archy": { + "pkg": { + "name": "archy", + "version": "1.0.0" + } + } + } + }, + { + "id": "·npm·argparse@1.0.10", + "node_modules": { + "argparse": { + "pkg": { + "name": "argparse", + "version": "1.0.10" + } + }, + "sprintf-js": { + "link": "../../·npm·sprintf-js@1.0.3/node_modules/sprintf-js" + } + } + }, + { + "id": "·npm·asn1@0.2.6", + "node_modules": { + "asn1": { + "pkg": { + "name": "asn1", + "version": "0.2.6" + } + }, + "safer-buffer": { + "link": "../../·npm·safer-buffer@2.1.2/node_modules/safer-buffer" + } + } + }, + { + "id": "·npm·assert-plus@1.0.0", + "node_modules": { + "assert-plus": { + "pkg": { + "name": "assert-plus", + "version": "1.0.0" + } + } + } + }, + { + "id": "·npm·async-hook-domain@2.0.4", + "node_modules": { + "async-hook-domain": { + "pkg": { + "name": "async-hook-domain", + "version": "2.0.4" + } + } + } + }, + { + "id": "·npm·asynckit@0.4.0", + "node_modules": { + "asynckit": { + "pkg": { + "name": "asynckit", + "version": "0.4.0" + } + } + } + }, + { + "id": "·npm·aws-sign2@0.7.0", + "node_modules": { + "aws-sign2": { + "pkg": { + "name": "aws-sign2", + "version": "0.7.0" + } + } + } + }, + { + "id": "·npm·aws4@1.13.2", + "node_modules": { + "aws4": { + "pkg": { + "name": "aws4", + "version": "1.13.2" + } + } + } + }, + { + "id": "·npm·balanced-match@1.0.2", + "node_modules": { + "balanced-match": { + "pkg": { + "name": "balanced-match", + "version": "1.0.2" + } + } + } + }, + { + "id": "·npm·baseline-browser-mapping@2.11.26", + "node_modules": { + "baseline-browser-mapping": { + "pkg": { + "name": "baseline-browser-mapping", + "version": "2.11.26" + } + } + } + }, + { + "id": "·npm·bcrypt-pbkdf@1.0.2", + "node_modules": { + "bcrypt-pbkdf": { + "pkg": { + "name": "bcrypt-pbkdf", + "version": "1.0.2" + } + }, + "tweetnacl": { + "link": "../../·npm·tweetnacl@0.14.5/node_modules/tweetnacl" + } + } + }, + { + "id": "·npm·binary-extensions@2.3.0", + "node_modules": { + "binary-extensions": { + "pkg": { + "name": "binary-extensions", + "version": "2.3.0" + } + } + } + }, + { + "id": "·npm·bind-obj-methods@3.0.0", + "node_modules": { + "bind-obj-methods": { + "pkg": { + "name": "bind-obj-methods", + "version": "3.0.0" + } + } + } + }, + { + "id": "·npm·brace-expansion@1.1.21", + "node_modules": { + "balanced-match": { + "link": "../../·npm·balanced-match@1.0.2/node_modules/balanced-match" + }, + "brace-expansion": { + "pkg": { + "name": "brace-expansion", + "version": "1.1.21" + } + }, + "concat-map": { + "link": "../../·npm·concat-map@0.0.1/node_modules/concat-map" + } + } + }, + { + "id": "·npm·braces@3.0.3", + "node_modules": { + "braces": { + "pkg": { + "name": "braces", + "version": "3.0.3" + } + }, + "fill-range": { + "link": "../../·npm·fill-range@7.1.1/node_modules/fill-range" + } + } + }, + { + "id": "·npm·browserslist@4.29.1", + "node_modules": { + ".bin": { + "dir": true + }, + "baseline-browser-mapping": { + "link": "../../·npm·baseline-browser-mapping@2.11.26/node_modules/baseline-browser-mapping" + }, + "browserslist": { + "pkg": { + "name": "browserslist", + "version": "4.29.1" + } + }, + "caniuse-lite": { + "link": "../../·npm·caniuse-lite@1.0.30001812/node_modules/caniuse-lite" + }, + "electron-to-chromium": { + "link": "../../·npm·electron-to-chromium@1.5.439/node_modules/electron-to-chromium" + }, + "node-releases": { + "link": "../../·npm·node-releases@2.0.57/node_modules/node-releases" + }, + "update-browserslist-db": { + "link": "../../·npm·update-browserslist-db@1.3.3·%E1%B9%97%3A6/node_modules/update-browserslist-db" + } + } + }, + { + "id": "·npm·buffer-from@1.1.2", + "node_modules": { + "buffer-from": { + "pkg": { + "name": "buffer-from", + "version": "1.1.2" + } + } + } + }, + { + "id": "·npm·caching-transform@4.0.0", + "node_modules": { + "caching-transform": { + "pkg": { + "name": "caching-transform", + "version": "4.0.0" + } + }, + "hasha": { + "link": "../../·npm·hasha@5.2.2/node_modules/hasha" + }, + "make-dir": { + "link": "../../·npm·make-dir@3.1.0/node_modules/make-dir" + }, + "package-hash": { + "link": "../../·npm·package-hash@4.0.0/node_modules/package-hash" + }, + "write-file-atomic": { + "link": "../../·npm·write-file-atomic@3.0.3/node_modules/write-file-atomic" + } + } + }, + { + "id": "·npm·camelcase@5.3.1", + "node_modules": { + "camelcase": { + "pkg": { + "name": "camelcase", + "version": "5.3.1" + } + } + } + }, + { + "id": "·npm·caniuse-lite@1.0.30001812", + "node_modules": { + "caniuse-lite": { + "pkg": { + "name": "caniuse-lite", + "version": "1.0.30001812" + } + } + } + }, + { + "id": "·npm·caseless@0.12.0", + "node_modules": { + "caseless": { + "pkg": { + "name": "caseless", + "version": "0.12.0" + } + } + } + }, + { + "id": "·npm·chokidar@3.6.0", + "node_modules": { + "anymatch": { + "link": "../../·npm·anymatch@3.1.3/node_modules/anymatch" + }, + "braces": { + "link": "../../·npm·braces@3.0.3/node_modules/braces" + }, + "chokidar": { + "pkg": { + "name": "chokidar", + "version": "3.6.0" + } + }, + "fsevents": { + "link": "../../·npm·fsevents@2.3.3/node_modules/fsevents" + }, + "glob-parent": { + "link": "../../·npm·glob-parent@5.1.2/node_modules/glob-parent" + }, + "is-binary-path": { + "link": "../../·npm·is-binary-path@2.1.0/node_modules/is-binary-path" + }, + "is-glob": { + "link": "../../·npm·is-glob@4.0.3/node_modules/is-glob" + }, + "normalize-path": { + "link": "../../·npm·normalize-path@3.0.0/node_modules/normalize-path" + }, + "readdirp": { + "link": "../../·npm·readdirp@3.6.0/node_modules/readdirp" + } + } + }, + { + "id": "·npm·clean-stack@2.2.0", + "node_modules": { + "clean-stack": { + "pkg": { + "name": "clean-stack", + "version": "2.2.0" + } + } + } + }, + { + "id": "·npm·cliui@6.0.0", + "node_modules": { + "cliui": { + "pkg": { + "name": "cliui", + "version": "6.0.0" + } + }, + "string-width": { + "link": "../../·npm·string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../../·npm·strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "wrap-ansi": { + "link": "../../·npm·wrap-ansi@6.2.0/node_modules/wrap-ansi" + } + } + }, + { + "id": "·npm·cliui@7.0.4", + "node_modules": { + "cliui": { + "pkg": { + "name": "cliui", + "version": "7.0.4" + } + }, + "string-width": { + "link": "../../·npm·string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../../·npm·strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "wrap-ansi": { + "link": "../../·npm·wrap-ansi@7.0.0/node_modules/wrap-ansi" + } + } + }, + { + "id": "·npm·color-convert@2.0.1", + "node_modules": { + "color-convert": { + "pkg": { + "name": "color-convert", + "version": "2.0.1" + } + }, + "color-name": { + "link": "../../·npm·color-name@1.1.4/node_modules/color-name" + } + } + }, + { + "id": "·npm·color-name@1.1.4", + "node_modules": { + "color-name": { + "pkg": { + "name": "color-name", + "version": "1.1.4" + } + } + } + }, + { + "id": "·npm·color-support@1.1.3", + "node_modules": { + "color-support": { + "pkg": { + "name": "color-support", + "version": "1.1.3" + } + } + } + }, + { + "id": "·npm·combined-stream@1.0.8", + "node_modules": { + "combined-stream": { + "pkg": { + "name": "combined-stream", + "version": "1.0.8" + } + }, + "delayed-stream": { + "link": "../../·npm·delayed-stream@1.0.0/node_modules/delayed-stream" + } + } + }, + { + "id": "·npm·commondir@1.0.1", + "node_modules": { + "commondir": { + "pkg": { + "name": "commondir", + "version": "1.0.1" + } + } + } + }, + { + "id": "·npm·concat-map@0.0.1", + "node_modules": { + "concat-map": { + "pkg": { + "name": "concat-map", + "version": "0.0.1" + } + } + } + }, + { + "id": "·npm·convert-source-map@1.9.0", + "node_modules": { + "convert-source-map": { + "pkg": { + "name": "convert-source-map", + "version": "1.9.0" + } + } + } + }, + { + "id": "·npm·convert-source-map@2.0.0", + "node_modules": { + "convert-source-map": { + "pkg": { + "name": "convert-source-map", + "version": "2.0.0" + } + } + } + }, + { + "id": "·npm·core-util-is@1.0.2", + "node_modules": { + "core-util-is": { + "pkg": { + "name": "core-util-is", + "version": "1.0.2" + } + } + } + }, + { + "id": "·npm·coveralls@3.1.1", + "node_modules": { + ".bin": { + "dir": true + }, + "coveralls": { + "pkg": { + "name": "coveralls", + "version": "3.1.1" + } + }, + "js-yaml": { + "link": "../../·npm·js-yaml@3.15.2/node_modules/js-yaml" + }, + "lcov-parse": { + "link": "../../·npm·lcov-parse@1.0.0/node_modules/lcov-parse" + }, + "log-driver": { + "link": "../../·npm·log-driver@1.2.7/node_modules/log-driver" + }, + "minimist": { + "link": "../../·npm·minimist@1.2.8/node_modules/minimist" + }, + "request": { + "link": "../../·npm·request@2.88.2/node_modules/request" + } + } + }, + { + "id": "·npm·cross-spawn@7.0.6", + "node_modules": { + ".bin": { + "dir": true + }, + "cross-spawn": { + "pkg": { + "name": "cross-spawn", + "version": "7.0.6" + } + }, + "path-key": { + "link": "../../·npm·path-key@3.1.1/node_modules/path-key" + }, + "shebang-command": { + "link": "../../·npm·shebang-command@2.0.0/node_modules/shebang-command" + }, + "which": { + "link": "../../·npm·which@2.0.2/node_modules/which" + } + } + }, + { + "id": "·npm·dashdash@1.14.1", + "node_modules": { + "assert-plus": { + "link": "../../·npm·assert-plus@1.0.0/node_modules/assert-plus" + }, + "dashdash": { + "pkg": { + "name": "dashdash", + "version": "1.14.1" + } + } + } + }, + { + "id": "·npm·debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "node_modules": { + "debug": { + "pkg": { + "name": "debug", + "version": "4.3.4" + } + }, + "ms": { + "link": "../../·npm·ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms/node_modules/ms" + } + } + }, + { + "id": "·npm·decamelize@1.2.0", + "node_modules": { + "decamelize": { + "pkg": { + "name": "decamelize", + "version": "1.2.0" + } + } + } + }, + { + "id": "·npm·default-require-extensions@3.0.1", + "node_modules": { + "default-require-extensions": { + "pkg": { + "name": "default-require-extensions", + "version": "3.0.1" + } + }, + "strip-bom": { + "link": "../../·npm·strip-bom@4.0.0/node_modules/strip-bom" + } + } + }, + { + "id": "·npm·delayed-stream@1.0.0", + "node_modules": { + "delayed-stream": { + "pkg": { + "name": "delayed-stream", + "version": "1.0.0" + } + } + } + }, + { + "id": "·npm·diff@4.0.4", + "node_modules": { + "diff": { + "pkg": { + "name": "diff", + "version": "4.0.4" + } + } + } + }, + { + "id": "·npm·ecc-jsbn@0.1.2", + "node_modules": { + "ecc-jsbn": { + "pkg": { + "name": "ecc-jsbn", + "version": "0.1.2" + } + }, + "jsbn": { + "link": "../../·npm·jsbn@0.1.1/node_modules/jsbn" + }, + "safer-buffer": { + "link": "../../·npm·safer-buffer@2.1.2/node_modules/safer-buffer" + } + } + }, + { + "id": "·npm·electron-to-chromium@1.5.439", + "node_modules": { + "electron-to-chromium": { + "pkg": { + "name": "electron-to-chromium", + "version": "1.5.439" + } + } + } + }, + { + "id": "·npm·emoji-regex@8.0.0", + "node_modules": { + "emoji-regex": { + "pkg": { + "name": "emoji-regex", + "version": "8.0.0" + } + } + } + }, + { + "id": "·npm·es6-error@4.1.1", + "node_modules": { + "es6-error": { + "pkg": { + "name": "es6-error", + "version": "4.1.1" + } + } + } + }, + { + "id": "·npm·escalade@3.2.0", + "node_modules": { + "escalade": { + "pkg": { + "name": "escalade", + "version": "3.2.0" + } + } + } + }, + { + "id": "·npm·escape-string-regexp@2.0.0", + "node_modules": { + "escape-string-regexp": { + "pkg": { + "name": "escape-string-regexp", + "version": "2.0.0" + } + } + } + }, + { + "id": "·npm·esprima@4.0.1", + "node_modules": { + "esprima": { + "pkg": { + "name": "esprima", + "version": "4.0.1" + } + } + } + }, + { + "id": "·npm·events-to-array@1.1.2", + "node_modules": { + "events-to-array": { + "pkg": { + "name": "events-to-array", + "version": "1.1.2" + } + } + } + }, + { + "id": "·npm·extend@3.0.2", + "node_modules": { + "extend": { + "pkg": { + "name": "extend", + "version": "3.0.2" + } + } + } + }, + { + "id": "·npm·extsprintf@1.3.0", + "node_modules": { + "extsprintf": { + "pkg": { + "name": "extsprintf", + "version": "1.3.0" + } + } + } + }, + { + "id": "·npm·fast-deep-equal@3.1.3", + "node_modules": { + "fast-deep-equal": { + "pkg": { + "name": "fast-deep-equal", + "version": "3.1.3" + } + } + } + }, + { + "id": "·npm·fast-json-stable-stringify@2.1.0", + "node_modules": { + "fast-json-stable-stringify": { + "pkg": { + "name": "fast-json-stable-stringify", + "version": "2.1.0" + } + } + } + }, + { + "id": "·npm·fill-range@7.1.1", + "node_modules": { + "fill-range": { + "pkg": { + "name": "fill-range", + "version": "7.1.1" + } + }, + "to-regex-range": { + "link": "../../·npm·to-regex-range@5.0.1/node_modules/to-regex-range" + } + } + }, + { + "id": "·npm·find-cache-dir@3.3.2", + "node_modules": { + "commondir": { + "link": "../../·npm·commondir@1.0.1/node_modules/commondir" + }, + "find-cache-dir": { + "pkg": { + "name": "find-cache-dir", + "version": "3.3.2" + } + }, + "make-dir": { + "link": "../../·npm·make-dir@3.1.0/node_modules/make-dir" + }, + "pkg-dir": { + "link": "../../·npm·pkg-dir@4.2.0/node_modules/pkg-dir" + } + } + }, + { + "id": "·npm·find-up@4.1.0", + "node_modules": { + "find-up": { + "pkg": { + "name": "find-up", + "version": "4.1.0" + } + }, + "locate-path": { + "link": "../../·npm·locate-path@5.0.0/node_modules/locate-path" + }, + "path-exists": { + "link": "../../·npm·path-exists@4.0.0/node_modules/path-exists" + } + } + }, + { + "id": "·npm·findit@2.0.0", + "node_modules": { + "findit": { + "pkg": { + "name": "findit", + "version": "2.0.0" + } + } + } + }, + { + "id": "·npm·foreground-child@2.0.0", + "node_modules": { + "cross-spawn": { + "link": "../../·npm·cross-spawn@7.0.6/node_modules/cross-spawn" + }, + "foreground-child": { + "pkg": { + "name": "foreground-child", + "version": "2.0.0" + } + }, + "signal-exit": { + "link": "../../·npm·signal-exit@3.0.7/node_modules/signal-exit" + } + } + }, + { + "id": "·npm·forever-agent@0.6.1", + "node_modules": { + "forever-agent": { + "pkg": { + "name": "forever-agent", + "version": "0.6.1" + } + } + } + }, + { + "id": "·npm·form-data@2.3.3", + "node_modules": { + "asynckit": { + "link": "../../·npm·asynckit@0.4.0/node_modules/asynckit" + }, + "combined-stream": { + "link": "../../·npm·combined-stream@1.0.8/node_modules/combined-stream" + }, + "form-data": { + "pkg": { + "name": "form-data", + "version": "2.3.3" + } + }, + "mime-types": { + "link": "../../·npm·mime-types@2.1.35/node_modules/mime-types" + } + } + }, + { + "id": "·npm·fromentries@1.3.2", + "node_modules": { + "fromentries": { + "pkg": { + "name": "fromentries", + "version": "1.3.2" + } + } + } + }, + { + "id": "·npm·fs-exists-cached@1.0.0", + "node_modules": { + "fs-exists-cached": { + "pkg": { + "name": "fs-exists-cached", + "version": "1.0.0" + } + } + } + }, + { + "id": "·npm·fs.realpath@1.0.0", + "node_modules": { + "fs.realpath": { + "pkg": { + "name": "fs.realpath", + "version": "1.0.0" + } + } + } + }, + { + "id": "·npm·fsevents@2.3.3", + "node_modules": { + "fsevents": { + "pkg": { + "name": "fsevents", + "version": "2.3.3" + } + } + } + }, + { + "id": "·npm·function-loop@2.0.1", + "node_modules": { + "function-loop": { + "pkg": { + "name": "function-loop", + "version": "2.0.1" + } + } + } + }, + { + "id": "·npm·gensync@1.0.0-beta.2", + "node_modules": { + "gensync": { + "pkg": { + "name": "gensync", + "version": "1.0.0-beta.2" + } + } + } + }, + { + "id": "·npm·get-caller-file@2.0.5", + "node_modules": { + "get-caller-file": { + "pkg": { + "name": "get-caller-file", + "version": "2.0.5" + } + } + } + }, + { + "id": "·npm·get-package-type@0.1.0", + "node_modules": { + "get-package-type": { + "pkg": { + "name": "get-package-type", + "version": "0.1.0" + } + } + } + }, + { + "id": "·npm·getpass@0.1.7", + "node_modules": { + "assert-plus": { + "link": "../../·npm·assert-plus@1.0.0/node_modules/assert-plus" + }, + "getpass": { + "pkg": { + "name": "getpass", + "version": "0.1.7" + } + } + } + }, + { + "id": "·npm·glob-parent@5.1.2", + "node_modules": { + "glob-parent": { + "pkg": { + "name": "glob-parent", + "version": "5.1.2" + } + }, + "is-glob": { + "link": "../../·npm·is-glob@4.0.3/node_modules/is-glob" + } + } + }, + { + "id": "·npm·glob@7.2.3", + "node_modules": { + "fs.realpath": { + "link": "../../·npm·fs.realpath@1.0.0/node_modules/fs.realpath" + }, + "glob": { + "pkg": { + "name": "glob", + "version": "7.2.3" + } + }, + "inflight": { + "link": "../../·npm·inflight@1.0.6/node_modules/inflight" + }, + "inherits": { + "link": "../../·npm·inherits@2.0.4/node_modules/inherits" + }, + "minimatch": { + "link": "../../·npm·minimatch@3.1.5/node_modules/minimatch" + }, + "once": { + "link": "../../·npm·once@1.4.0/node_modules/once" + }, + "path-is-absolute": { + "link": "../../·npm·path-is-absolute@1.0.1/node_modules/path-is-absolute" + } + } + }, + { + "id": "·npm·graceful-fs@4.2.11", + "node_modules": { + "graceful-fs": { + "pkg": { + "name": "graceful-fs", + "version": "4.2.11" + } + } + } + }, + { + "id": "·npm·har-schema@2.0.0", + "node_modules": { + "har-schema": { + "pkg": { + "name": "har-schema", + "version": "2.0.0" + } + } + } + }, + { + "id": "·npm·har-validator@5.1.5", + "node_modules": { + "ajv": { + "link": "../../·npm·ajv@6.15.0/node_modules/ajv" + }, + "har-schema": { + "link": "../../·npm·har-schema@2.0.0/node_modules/har-schema" + }, + "har-validator": { + "pkg": { + "name": "har-validator", + "version": "5.1.5" + } + } + } + }, + { + "id": "·npm·has-flag@4.0.0", + "node_modules": { + "has-flag": { + "pkg": { + "name": "has-flag", + "version": "4.0.0" + } + } + } + }, + { + "id": "·npm·hasha@5.2.2", + "node_modules": { + "hasha": { + "pkg": { + "name": "hasha", + "version": "5.2.2" + } + }, + "is-stream": { + "link": "../../·npm·is-stream@2.0.1/node_modules/is-stream" + }, + "type-fest": { + "link": "../../·npm·type-fest@0.8.1/node_modules/type-fest" + } + } + }, + { + "id": "·npm·html-escaper@2.0.2", + "node_modules": { + "html-escaper": { + "pkg": { + "name": "html-escaper", + "version": "2.0.2" + } + } + } + }, + { + "id": "·npm·http-signature@1.2.0", + "node_modules": { + ".bin": { + "dir": true + }, + "assert-plus": { + "link": "../../·npm·assert-plus@1.0.0/node_modules/assert-plus" + }, + "http-signature": { + "pkg": { + "name": "http-signature", + "version": "1.2.0" + } + }, + "jsprim": { + "link": "../../·npm·jsprim@1.4.2/node_modules/jsprim" + }, + "sshpk": { + "link": "../../·npm·sshpk@1.18.0/node_modules/sshpk" + } + } + }, + { + "id": "·npm·imurmurhash@0.1.4", + "node_modules": { + "imurmurhash": { + "pkg": { + "name": "imurmurhash", + "version": "0.1.4" + } + } + } + }, + { + "id": "·npm·indent-string@4.0.0", + "node_modules": { + "indent-string": { + "pkg": { + "name": "indent-string", + "version": "4.0.0" + } + } + } + }, + { + "id": "·npm·inflight@1.0.6", + "node_modules": { + "inflight": { + "pkg": { + "name": "inflight", + "version": "1.0.6" + } + }, + "once": { + "link": "../../·npm·once@1.4.0/node_modules/once" + }, + "wrappy": { + "link": "../../·npm·wrappy@1.0.2/node_modules/wrappy" + } + } + }, + { + "id": "·npm·inherits@2.0.4", + "node_modules": { + "inherits": { + "pkg": { + "name": "inherits", + "version": "2.0.4" + } + } + } + }, + { + "id": "·npm·is-binary-path@2.1.0", + "node_modules": { + "binary-extensions": { + "link": "../../·npm·binary-extensions@2.3.0/node_modules/binary-extensions" + }, + "is-binary-path": { + "pkg": { + "name": "is-binary-path", + "version": "2.1.0" + } + } + } + }, + { + "id": "·npm·is-extglob@2.1.1", + "node_modules": { + "is-extglob": { + "pkg": { + "name": "is-extglob", + "version": "2.1.1" + } + } + } + }, + { + "id": "·npm·is-fullwidth-code-point@3.0.0", + "node_modules": { + "is-fullwidth-code-point": { + "pkg": { + "name": "is-fullwidth-code-point", + "version": "3.0.0" + } + } + } + }, + { + "id": "·npm·is-glob@4.0.3", + "node_modules": { + "is-extglob": { + "link": "../../·npm·is-extglob@2.1.1/node_modules/is-extglob" + }, + "is-glob": { + "pkg": { + "name": "is-glob", + "version": "4.0.3" + } + } + } + }, + { + "id": "·npm·is-number@7.0.0", + "node_modules": { + "is-number": { + "pkg": { + "name": "is-number", + "version": "7.0.0" + } + } + } + }, + { + "id": "·npm·is-stream@2.0.1", + "node_modules": { + "is-stream": { + "pkg": { + "name": "is-stream", + "version": "2.0.1" + } + } + } + }, + { + "id": "·npm·is-typedarray@1.0.0", + "node_modules": { + "is-typedarray": { + "pkg": { + "name": "is-typedarray", + "version": "1.0.0" + } + } + } + }, + { + "id": "·npm·is-windows@1.0.2", + "node_modules": { + "is-windows": { + "pkg": { + "name": "is-windows", + "version": "1.0.2" + } + } + } + }, + { + "id": "·npm·isexe@2.0.0", + "node_modules": { + "isexe": { + "pkg": { + "name": "isexe", + "version": "2.0.0" + } + } + } + }, + { + "id": "·npm·isstream@0.1.2", + "node_modules": { + "isstream": { + "pkg": { + "name": "isstream", + "version": "0.1.2" + } + } + } + }, + { + "id": "·npm·istanbul-lib-coverage@3.2.2", + "node_modules": { + "istanbul-lib-coverage": { + "pkg": { + "name": "istanbul-lib-coverage", + "version": "3.2.2" + } + } + } + }, + { + "id": "·npm·istanbul-lib-hook@3.0.0", + "node_modules": { + "append-transform": { + "link": "../../·npm·append-transform@2.0.0/node_modules/append-transform" + }, + "istanbul-lib-hook": { + "pkg": { + "name": "istanbul-lib-hook", + "version": "3.0.0" + } + } + } + }, + { + "id": "·npm·istanbul-lib-instrument@4.0.3", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/core": { + "link": "../../../·npm·@babel§core@7.29.7/node_modules/@babel/core" + }, + "@istanbuljs/schema": { + "link": "../../../·npm·@istanbuljs§schema@0.1.6/node_modules/@istanbuljs/schema" + }, + "istanbul-lib-coverage": { + "link": "../../·npm·istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-instrument": { + "pkg": { + "name": "istanbul-lib-instrument", + "version": "4.0.3" + } + }, + "semver": { + "link": "../../·npm·semver@6.3.1/node_modules/semver" + } + } + }, + { + "id": "·npm·istanbul-lib-processinfo@2.0.3", + "node_modules": { + ".bin": { + "dir": true + }, + "archy": { + "link": "../../·npm·archy@1.0.0/node_modules/archy" + }, + "cross-spawn": { + "link": "../../·npm·cross-spawn@7.0.6/node_modules/cross-spawn" + }, + "istanbul-lib-coverage": { + "link": "../../·npm·istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-processinfo": { + "pkg": { + "name": "istanbul-lib-processinfo", + "version": "2.0.3" + } + }, + "p-map": { + "link": "../../·npm·p-map@3.0.0/node_modules/p-map" + }, + "rimraf": { + "link": "../../·npm·rimraf@3.0.2/node_modules/rimraf" + }, + "uuid": { + "link": "../../·npm·uuid@8.3.2/node_modules/uuid" + } + } + }, + { + "id": "·npm·istanbul-lib-report@3.0.1", + "node_modules": { + "istanbul-lib-coverage": { + "link": "../../·npm·istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-report": { + "pkg": { + "name": "istanbul-lib-report", + "version": "3.0.1" + } + }, + "make-dir": { + "link": "../../·npm·make-dir@4.0.0/node_modules/make-dir" + }, + "supports-color": { + "link": "../../·npm·supports-color@7.2.0/node_modules/supports-color" + } + } + }, + { + "id": "·npm·istanbul-lib-source-maps@4.0.1", + "node_modules": { + "debug": { + "link": "../../·npm·debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms/node_modules/debug" + }, + "istanbul-lib-coverage": { + "link": "../../·npm·istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-source-maps": { + "pkg": { + "name": "istanbul-lib-source-maps", + "version": "4.0.1" + } + }, + "source-map": { + "link": "../../·npm·source-map@0.6.1/node_modules/source-map" + } + } + }, + { + "id": "·npm·istanbul-reports@3.2.0", + "node_modules": { + "html-escaper": { + "link": "../../·npm·html-escaper@2.0.2/node_modules/html-escaper" + }, + "istanbul-lib-report": { + "link": "../../·npm·istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report" + }, + "istanbul-reports": { + "pkg": { + "name": "istanbul-reports", + "version": "3.2.0" + } + } + } + }, + { + "id": "·npm·jackspeak@1.4.2", + "node_modules": { + "cliui": { + "link": "../../·npm·cliui@7.0.4/node_modules/cliui" + }, + "jackspeak": { + "pkg": { + "name": "jackspeak", + "version": "1.4.2" + } + } + } + }, + { + "id": "·npm·js-tokens@4.0.0", + "node_modules": { + "js-tokens": { + "pkg": { + "name": "js-tokens", + "version": "4.0.0" + } + } + } + }, + { + "id": "·npm·js-yaml@3.15.2", + "node_modules": { + ".bin": { + "dir": true + }, + "argparse": { + "link": "../../·npm·argparse@1.0.10/node_modules/argparse" + }, + "esprima": { + "link": "../../·npm·esprima@4.0.1/node_modules/esprima" + }, + "js-yaml": { + "pkg": { + "name": "js-yaml", + "version": "3.15.2" + } + } + } + }, + { + "id": "·npm·jsbn@0.1.1", + "node_modules": { + "jsbn": { + "pkg": { + "name": "jsbn", + "version": "0.1.1" + } + } + } + }, + { + "id": "·npm·jsesc@3.1.0", + "node_modules": { + "jsesc": { + "pkg": { + "name": "jsesc", + "version": "3.1.0" + } + } + } + }, + { + "id": "·npm·json-schema-traverse@0.4.1", + "node_modules": { + "json-schema-traverse": { + "pkg": { + "name": "json-schema-traverse", + "version": "0.4.1" + } + } + } + }, + { + "id": "·npm·json-schema@0.4.0", + "node_modules": { + "json-schema": { + "pkg": { + "name": "json-schema", + "version": "0.4.0" + } + } + } + }, + { + "id": "·npm·json-stringify-safe@5.0.1", + "node_modules": { + "json-stringify-safe": { + "pkg": { + "name": "json-stringify-safe", + "version": "5.0.1" + } + } + } + }, + { + "id": "·npm·json5@2.2.3", + "node_modules": { + "json5": { + "pkg": { + "name": "json5", + "version": "2.2.3" + } + } + } + }, + { + "id": "·npm·jsprim@1.4.2", + "node_modules": { + "assert-plus": { + "link": "../../·npm·assert-plus@1.0.0/node_modules/assert-plus" + }, + "extsprintf": { + "link": "../../·npm·extsprintf@1.3.0/node_modules/extsprintf" + }, + "json-schema": { + "link": "../../·npm·json-schema@0.4.0/node_modules/json-schema" + }, + "jsprim": { + "pkg": { + "name": "jsprim", + "version": "1.4.2" + } + }, + "verror": { + "link": "../../·npm·verror@1.10.0/node_modules/verror" + } + } + }, + { + "id": "·npm·lcov-parse@1.0.0", + "node_modules": { + "lcov-parse": { + "pkg": { + "name": "lcov-parse", + "version": "1.0.0" + } + } + } + }, + { + "id": "·npm·left-pad@1.1.3", + "node_modules": { + "left-pad": { + "pkg": { + "name": "left-pad", + "version": "1.1.3" + } + } + } + }, + { + "id": "·npm·left-pad@1.3.0", + "node_modules": { + "left-pad": { + "pkg": { + "name": "left-pad", + "version": "1.3.0" + } + } + } + }, + { + "id": "·npm·libtap@1.4.1", + "node_modules": { + ".bin": { + "dir": true + }, + "async-hook-domain": { + "link": "../../·npm·async-hook-domain@2.0.4/node_modules/async-hook-domain" + }, + "bind-obj-methods": { + "link": "../../·npm·bind-obj-methods@3.0.0/node_modules/bind-obj-methods" + }, + "diff": { + "link": "../../·npm·diff@4.0.4/node_modules/diff" + }, + "function-loop": { + "link": "../../·npm·function-loop@2.0.1/node_modules/function-loop" + }, + "libtap": { + "pkg": { + "name": "libtap", + "version": "1.4.1" + } + }, + "minipass": { + "link": "../../·npm·minipass@3.3.6/node_modules/minipass" + }, + "own-or": { + "link": "../../·npm·own-or@1.0.0/node_modules/own-or" + }, + "own-or-env": { + "link": "../../·npm·own-or-env@1.0.2/node_modules/own-or-env" + }, + "signal-exit": { + "link": "../../·npm·signal-exit@3.0.7/node_modules/signal-exit" + }, + "stack-utils": { + "link": "../../·npm·stack-utils@2.0.6/node_modules/stack-utils" + }, + "tap-parser": { + "link": "../../·npm·tap-parser@11.0.2/node_modules/tap-parser" + }, + "tap-yaml": { + "link": "../../·npm·tap-yaml@1.0.2/node_modules/tap-yaml" + }, + "tcompare": { + "link": "../../·npm·tcompare@5.0.7/node_modules/tcompare" + }, + "trivial-deferred": { + "link": "../../·npm·trivial-deferred@1.1.2/node_modules/trivial-deferred" + } + } + }, + { + "id": "·npm·locate-path@5.0.0", + "node_modules": { + "locate-path": { + "pkg": { + "name": "locate-path", + "version": "5.0.0" + } + }, + "p-locate": { + "link": "../../·npm·p-locate@4.1.0/node_modules/p-locate" + } + } + }, + { + "id": "·npm·lodash.flattendeep@4.4.0", + "node_modules": { + "lodash.flattendeep": { + "pkg": { + "name": "lodash.flattendeep", + "version": "4.4.0" + } + } + } + }, + { + "id": "·npm·log-driver@1.2.7", + "node_modules": { + "log-driver": { + "pkg": { + "name": "log-driver", + "version": "1.2.7" + } + } + } + }, + { + "id": "·npm·loose-envify@1.4.0", + "node_modules": { + "js-tokens": { + "link": "../../·npm·js-tokens@4.0.0/node_modules/js-tokens" + }, + "loose-envify": { + "pkg": { + "name": "loose-envify", + "version": "1.4.0" + } + } + } + }, + { + "id": "·npm·lru-cache@5.1.1", + "node_modules": { + "lru-cache": { + "pkg": { + "name": "lru-cache", + "version": "5.1.1" + } + }, + "yallist": { + "link": "../../·npm·yallist@3.1.1/node_modules/yallist" + } + } + }, + { + "id": "·npm·lru-cache@6.0.0", + "node_modules": { + "lru-cache": { + "pkg": { + "name": "lru-cache", + "version": "6.0.0" + } + }, + "yallist": { + "link": "../../·npm·yallist@4.0.0/node_modules/yallist" + } + } + }, + { + "id": "·npm·make-dir@3.1.0", + "node_modules": { + ".bin": { + "dir": true + }, + "make-dir": { + "pkg": { + "name": "make-dir", + "version": "3.1.0" + } + }, + "semver": { + "link": "../../·npm·semver@6.3.1/node_modules/semver" + } + } + }, + { + "id": "·npm·make-dir@4.0.0", + "node_modules": { + ".bin": { + "dir": true + }, + "make-dir": { + "pkg": { + "name": "make-dir", + "version": "4.0.0" + } + }, + "semver": { + "link": "../../·npm·semver@7.6.0/node_modules/semver" + } + } + }, + { + "id": "·npm·mime-db@1.52.0", + "node_modules": { + "mime-db": { + "pkg": { + "name": "mime-db", + "version": "1.52.0" + } + } + } + }, + { + "id": "·npm·mime-types@2.1.35", + "node_modules": { + "mime-db": { + "link": "../../·npm·mime-db@1.52.0/node_modules/mime-db" + }, + "mime-types": { + "pkg": { + "name": "mime-types", + "version": "2.1.35" + } + } + } + }, + { + "id": "·npm·minimatch@3.1.5", + "node_modules": { + "brace-expansion": { + "link": "../../·npm·brace-expansion@1.1.21/node_modules/brace-expansion" + }, + "minimatch": { + "pkg": { + "name": "minimatch", + "version": "3.1.5" + } + } + } + }, + { + "id": "·npm·minimist@1.2.8", + "node_modules": { + "minimist": { + "pkg": { + "name": "minimist", + "version": "1.2.8" + } + } + } + }, + { + "id": "·npm·minipass@3.3.6", + "node_modules": { + "minipass": { + "pkg": { + "name": "minipass", + "version": "3.3.6" + } + }, + "yallist": { + "link": "../../·npm·yallist@4.0.0/node_modules/yallist" + } + } + }, + { + "id": "·npm·mkdirp@1.0.4", + "node_modules": { + "mkdirp": { + "pkg": { + "name": "mkdirp", + "version": "1.0.4" + } + } + } + }, + { + "id": "·npm·ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "node_modules": { + "ms": { + "pkg": { + "name": "ms", + "version": "2.1.3" + } + } + } + }, + { + "id": "·npm·node-preload@0.2.1", + "node_modules": { + "node-preload": { + "pkg": { + "name": "node-preload", + "version": "0.2.1" + } + }, + "process-on-spawn": { + "link": "../../·npm·process-on-spawn@1.1.0/node_modules/process-on-spawn" + } + } + }, + { + "id": "·npm·node-releases@2.0.57", + "node_modules": { + "node-releases": { + "pkg": { + "name": "node-releases", + "version": "2.0.57" + } + } + } + }, + { + "id": "·npm·normalize-path@3.0.0", + "node_modules": { + "normalize-path": { + "pkg": { + "name": "normalize-path", + "version": "3.0.0" + } + } + } + }, + { + "id": "·npm·nyc@15.1.0", + "node_modules": { + ".bin": { + "dir": true + }, + "@istanbuljs/load-nyc-config": { + "link": "../../../·npm·@istanbuljs§load-nyc-config@1.1.0/node_modules/@istanbuljs/load-nyc-config" + }, + "@istanbuljs/schema": { + "link": "../../../·npm·@istanbuljs§schema@0.1.6/node_modules/@istanbuljs/schema" + }, + "caching-transform": { + "link": "../../·npm·caching-transform@4.0.0/node_modules/caching-transform" + }, + "convert-source-map": { + "link": "../../·npm·convert-source-map@1.9.0/node_modules/convert-source-map" + }, + "decamelize": { + "link": "../../·npm·decamelize@1.2.0/node_modules/decamelize" + }, + "find-cache-dir": { + "link": "../../·npm·find-cache-dir@3.3.2/node_modules/find-cache-dir" + }, + "find-up": { + "link": "../../·npm·find-up@4.1.0/node_modules/find-up" + }, + "foreground-child": { + "link": "../../·npm·foreground-child@2.0.0/node_modules/foreground-child" + }, + "get-package-type": { + "link": "../../·npm·get-package-type@0.1.0/node_modules/get-package-type" + }, + "glob": { + "link": "../../·npm·glob@7.2.3/node_modules/glob" + }, + "istanbul-lib-coverage": { + "link": "../../·npm·istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-hook": { + "link": "../../·npm·istanbul-lib-hook@3.0.0/node_modules/istanbul-lib-hook" + }, + "istanbul-lib-instrument": { + "link": "../../·npm·istanbul-lib-instrument@4.0.3/node_modules/istanbul-lib-instrument" + }, + "istanbul-lib-processinfo": { + "link": "../../·npm·istanbul-lib-processinfo@2.0.3/node_modules/istanbul-lib-processinfo" + }, + "istanbul-lib-report": { + "link": "../../·npm·istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report" + }, + "istanbul-lib-source-maps": { + "link": "../../·npm·istanbul-lib-source-maps@4.0.1/node_modules/istanbul-lib-source-maps" + }, + "istanbul-reports": { + "link": "../../·npm·istanbul-reports@3.2.0/node_modules/istanbul-reports" + }, + "make-dir": { + "link": "../../·npm·make-dir@3.1.0/node_modules/make-dir" + }, + "node-preload": { + "link": "../../·npm·node-preload@0.2.1/node_modules/node-preload" + }, + "nyc": { + "pkg": { + "name": "nyc", + "version": "15.1.0" + } + }, + "p-map": { + "link": "../../·npm·p-map@3.0.0/node_modules/p-map" + }, + "process-on-spawn": { + "link": "../../·npm·process-on-spawn@1.1.0/node_modules/process-on-spawn" + }, + "resolve-from": { + "link": "../../·npm·resolve-from@5.0.0/node_modules/resolve-from" + }, + "rimraf": { + "link": "../../·npm·rimraf@3.0.2/node_modules/rimraf" + }, + "signal-exit": { + "link": "../../·npm·signal-exit@3.0.7/node_modules/signal-exit" + }, + "spawn-wrap": { + "link": "../../·npm·spawn-wrap@2.0.0/node_modules/spawn-wrap" + }, + "test-exclude": { + "link": "../../·npm·test-exclude@6.0.0/node_modules/test-exclude" + }, + "yargs": { + "link": "../../·npm·yargs@15.4.1/node_modules/yargs" + } + } + }, + { + "id": "·npm·oauth-sign@0.9.0", + "node_modules": { + "oauth-sign": { + "pkg": { + "name": "oauth-sign", + "version": "0.9.0" + } + } + } + }, + { + "id": "·npm·once@1.4.0", + "node_modules": { + "once": { + "pkg": { + "name": "once", + "version": "1.4.0" + } + }, + "wrappy": { + "link": "../../·npm·wrappy@1.0.2/node_modules/wrappy" + } + } + }, + { + "id": "·npm·opener@1.5.2", + "node_modules": { + "opener": { + "pkg": { + "name": "opener", + "version": "1.5.2" + } + } + } + }, + { + "id": "·npm·own-or-env@1.0.2", + "node_modules": { + "own-or": { + "link": "../../·npm·own-or@1.0.0/node_modules/own-or" + }, + "own-or-env": { + "pkg": { + "name": "own-or-env", + "version": "1.0.2" + } + } + } + }, + { + "id": "·npm·own-or@1.0.0", + "node_modules": { + "own-or": { + "pkg": { + "name": "own-or", + "version": "1.0.0" + } + } + } + }, + { + "id": "·npm·p-limit@2.3.0", + "node_modules": { + "p-limit": { + "pkg": { + "name": "p-limit", + "version": "2.3.0" + } + }, + "p-try": { + "link": "../../·npm·p-try@2.2.0/node_modules/p-try" + } + } + }, + { + "id": "·npm·p-locate@4.1.0", + "node_modules": { + "p-limit": { + "link": "../../·npm·p-limit@2.3.0/node_modules/p-limit" + }, + "p-locate": { + "pkg": { + "name": "p-locate", + "version": "4.1.0" + } + } + } + }, + { + "id": "·npm·p-map@3.0.0", + "node_modules": { + "aggregate-error": { + "link": "../../·npm·aggregate-error@3.1.0/node_modules/aggregate-error" + }, + "p-map": { + "pkg": { + "name": "p-map", + "version": "3.0.0" + } + } + } + }, + { + "id": "·npm·p-try@2.2.0", + "node_modules": { + "p-try": { + "pkg": { + "name": "p-try", + "version": "2.2.0" + } + } + } + }, + { + "id": "·npm·package-hash@4.0.0", + "node_modules": { + "graceful-fs": { + "link": "../../·npm·graceful-fs@4.2.11/node_modules/graceful-fs" + }, + "hasha": { + "link": "../../·npm·hasha@5.2.2/node_modules/hasha" + }, + "lodash.flattendeep": { + "link": "../../·npm·lodash.flattendeep@4.4.0/node_modules/lodash.flattendeep" + }, + "package-hash": { + "pkg": { + "name": "package-hash", + "version": "4.0.0" + } + }, + "release-zalgo": { + "link": "../../·npm·release-zalgo@1.0.0/node_modules/release-zalgo" + } + } + }, + { + "id": "·npm·path-exists@4.0.0", + "node_modules": { + "path-exists": { + "pkg": { + "name": "path-exists", + "version": "4.0.0" + } + } + } + }, + { + "id": "·npm·path-is-absolute@1.0.1", + "node_modules": { + "path-is-absolute": { + "pkg": { + "name": "path-is-absolute", + "version": "1.0.1" + } + } + } + }, + { + "id": "·npm·path-key@3.1.1", + "node_modules": { + "path-key": { + "pkg": { + "name": "path-key", + "version": "3.1.1" + } + } + } + }, + { + "id": "·npm·performance-now@2.1.0", + "node_modules": { + "performance-now": { + "pkg": { + "name": "performance-now", + "version": "2.1.0" + } + } + } + }, + { + "id": "·npm·picocolors@1.1.1", + "node_modules": { + "picocolors": { + "pkg": { + "name": "picocolors", + "version": "1.1.1" + } + } + } + }, + { + "id": "·npm·picomatch@2.3.2", + "node_modules": { + "picomatch": { + "pkg": { + "name": "picomatch", + "version": "2.3.2" + } + } + } + }, + { + "id": "·npm·pkg-dir@4.2.0", + "node_modules": { + "find-up": { + "link": "../../·npm·find-up@4.1.0/node_modules/find-up" + }, + "pkg-dir": { + "pkg": { + "name": "pkg-dir", + "version": "4.2.0" + } + } + } + }, + { + "id": "·npm·process-on-spawn@1.1.0", + "node_modules": { + "fromentries": { + "link": "../../·npm·fromentries@1.3.2/node_modules/fromentries" + }, + "process-on-spawn": { + "pkg": { + "name": "process-on-spawn", + "version": "1.1.0" + } + } + } + }, + { + "id": "·npm·psl@1.15.0", + "node_modules": { + "psl": { + "pkg": { + "name": "psl", + "version": "1.15.0" + } + }, + "punycode": { + "link": "../../·npm·punycode@2.3.1/node_modules/punycode" + } + } + }, + { + "id": "·npm·punycode@2.3.1", + "node_modules": { + "punycode": { + "pkg": { + "name": "punycode", + "version": "2.3.1" + } + } + } + }, + { + "id": "·npm·qs@6.5.5", + "node_modules": { + "qs": { + "pkg": { + "name": "qs", + "version": "6.5.5" + } + } + } + }, + { + "id": "·npm·react@18.2.0", + "node_modules": { + ".bin": { + "dir": true + }, + "loose-envify": { + "link": "../../·npm·loose-envify@1.4.0/node_modules/loose-envify" + }, + "react": { + "pkg": { + "name": "react", + "version": "18.2.0" + } + } + } + }, + { + "id": "·npm·readdirp@3.6.0", + "node_modules": { + "picomatch": { + "link": "../../·npm·picomatch@2.3.2/node_modules/picomatch" + }, + "readdirp": { + "pkg": { + "name": "readdirp", + "version": "3.6.0" + } + } + } + }, + { + "id": "·npm·release-zalgo@1.0.0", + "node_modules": { + "es6-error": { + "link": "../../·npm·es6-error@4.1.1/node_modules/es6-error" + }, + "release-zalgo": { + "pkg": { + "name": "release-zalgo", + "version": "1.0.0" + } + } + } + }, + { + "id": "·npm·request@2.88.2", + "node_modules": { + ".bin": { + "dir": true + }, + "aws-sign2": { + "link": "../../·npm·aws-sign2@0.7.0/node_modules/aws-sign2" + }, + "aws4": { + "link": "../../·npm·aws4@1.13.2/node_modules/aws4" + }, + "caseless": { + "link": "../../·npm·caseless@0.12.0/node_modules/caseless" + }, + "combined-stream": { + "link": "../../·npm·combined-stream@1.0.8/node_modules/combined-stream" + }, + "extend": { + "link": "../../·npm·extend@3.0.2/node_modules/extend" + }, + "forever-agent": { + "link": "../../·npm·forever-agent@0.6.1/node_modules/forever-agent" + }, + "form-data": { + "link": "../../·npm·form-data@2.3.3/node_modules/form-data" + }, + "har-validator": { + "link": "../../·npm·har-validator@5.1.5/node_modules/har-validator" + }, + "http-signature": { + "link": "../../·npm·http-signature@1.2.0/node_modules/http-signature" + }, + "is-typedarray": { + "link": "../../·npm·is-typedarray@1.0.0/node_modules/is-typedarray" + }, + "isstream": { + "link": "../../·npm·isstream@0.1.2/node_modules/isstream" + }, + "json-stringify-safe": { + "link": "../../·npm·json-stringify-safe@5.0.1/node_modules/json-stringify-safe" + }, + "mime-types": { + "link": "../../·npm·mime-types@2.1.35/node_modules/mime-types" + }, + "oauth-sign": { + "link": "../../·npm·oauth-sign@0.9.0/node_modules/oauth-sign" + }, + "performance-now": { + "link": "../../·npm·performance-now@2.1.0/node_modules/performance-now" + }, + "qs": { + "link": "../../·npm·qs@6.5.5/node_modules/qs" + }, + "request": { + "pkg": { + "name": "request", + "version": "2.88.2" + } + }, + "safe-buffer": { + "link": "../../·npm·safe-buffer@5.2.1/node_modules/safe-buffer" + }, + "tough-cookie": { + "link": "../../·npm·tough-cookie@2.5.0/node_modules/tough-cookie" + }, + "tunnel-agent": { + "link": "../../·npm·tunnel-agent@0.6.0/node_modules/tunnel-agent" + }, + "uuid": { + "link": "../../·npm·uuid@3.4.0/node_modules/uuid" + } + } + }, + { + "id": "·npm·require-directory@2.1.1", + "node_modules": { + "require-directory": { + "pkg": { + "name": "require-directory", + "version": "2.1.1" + } + } + } + }, + { + "id": "·npm·require-main-filename@2.0.0", + "node_modules": { + "require-main-filename": { + "pkg": { + "name": "require-main-filename", + "version": "2.0.0" + } + } + } + }, + { + "id": "·npm·resolve-from@5.0.0", + "node_modules": { + "resolve-from": { + "pkg": { + "name": "resolve-from", + "version": "5.0.0" + } + } + } + }, + { + "id": "·npm·rimraf@3.0.2", + "node_modules": { + "glob": { + "link": "../../·npm·glob@7.2.3/node_modules/glob" + }, + "rimraf": { + "pkg": { + "name": "rimraf", + "version": "3.0.2" + } + } + } + }, + { + "id": "·npm·safe-buffer@5.2.1", + "node_modules": { + "safe-buffer": { + "pkg": { + "name": "safe-buffer", + "version": "5.2.1" + } + } + } + }, + { + "id": "·npm·safer-buffer@2.1.2", + "node_modules": { + "safer-buffer": { + "pkg": { + "name": "safer-buffer", + "version": "2.1.2" + } + } + } + }, + { + "id": "·npm·semver@6.3.1", + "node_modules": { + "semver": { + "pkg": { + "name": "semver", + "version": "6.3.1" + } + } + } + }, + { + "id": "·npm·semver@7.6.0", + "node_modules": { + "lru-cache": { + "link": "../../·npm·lru-cache@6.0.0/node_modules/lru-cache" + }, + "semver": { + "pkg": { + "name": "semver", + "version": "7.6.0" + } + } + } + }, + { + "id": "·npm·set-blocking@2.0.0", + "node_modules": { + "set-blocking": { + "pkg": { + "name": "set-blocking", + "version": "2.0.0" + } + } + } + }, + { + "id": "·npm·shebang-command@2.0.0", + "node_modules": { + "shebang-command": { + "pkg": { + "name": "shebang-command", + "version": "2.0.0" + } + }, + "shebang-regex": { + "link": "../../·npm·shebang-regex@3.0.0/node_modules/shebang-regex" + } + } + }, + { + "id": "·npm·shebang-regex@3.0.0", + "node_modules": { + "shebang-regex": { + "pkg": { + "name": "shebang-regex", + "version": "3.0.0" + } + } + } + }, + { + "id": "·npm·signal-exit@3.0.7", + "node_modules": { + "signal-exit": { + "pkg": { + "name": "signal-exit", + "version": "3.0.7" + } + } + } + }, + { + "id": "·npm·source-map-support@0.5.21", + "node_modules": { + "buffer-from": { + "link": "../../·npm·buffer-from@1.1.2/node_modules/buffer-from" + }, + "source-map": { + "link": "../../·npm·source-map@0.6.1/node_modules/source-map" + }, + "source-map-support": { + "pkg": { + "name": "source-map-support", + "version": "0.5.21" + } + } + } + }, + { + "id": "·npm·source-map@0.6.1", + "node_modules": { + "source-map": { + "pkg": { + "name": "source-map", + "version": "0.6.1" + } + } + } + }, + { + "id": "·npm·spawn-wrap@2.0.0", + "node_modules": { + ".bin": { + "dir": true + }, + "foreground-child": { + "link": "../../·npm·foreground-child@2.0.0/node_modules/foreground-child" + }, + "is-windows": { + "link": "../../·npm·is-windows@1.0.2/node_modules/is-windows" + }, + "make-dir": { + "link": "../../·npm·make-dir@3.1.0/node_modules/make-dir" + }, + "rimraf": { + "link": "../../·npm·rimraf@3.0.2/node_modules/rimraf" + }, + "signal-exit": { + "link": "../../·npm·signal-exit@3.0.7/node_modules/signal-exit" + }, + "spawn-wrap": { + "pkg": { + "name": "spawn-wrap", + "version": "2.0.0" + } + }, + "which": { + "link": "../../·npm·which@2.0.2/node_modules/which" + } + } + }, + { + "id": "·npm·sprintf-js@1.0.3", + "node_modules": { + "sprintf-js": { + "pkg": { + "name": "sprintf-js", + "version": "1.0.3" + } + } + } + }, + { + "id": "·npm·sshpk@1.18.0", + "node_modules": { + "asn1": { + "link": "../../·npm·asn1@0.2.6/node_modules/asn1" + }, + "assert-plus": { + "link": "../../·npm·assert-plus@1.0.0/node_modules/assert-plus" + }, + "bcrypt-pbkdf": { + "link": "../../·npm·bcrypt-pbkdf@1.0.2/node_modules/bcrypt-pbkdf" + }, + "dashdash": { + "link": "../../·npm·dashdash@1.14.1/node_modules/dashdash" + }, + "ecc-jsbn": { + "link": "../../·npm·ecc-jsbn@0.1.2/node_modules/ecc-jsbn" + }, + "getpass": { + "link": "../../·npm·getpass@0.1.7/node_modules/getpass" + }, + "jsbn": { + "link": "../../·npm·jsbn@0.1.1/node_modules/jsbn" + }, + "safer-buffer": { + "link": "../../·npm·safer-buffer@2.1.2/node_modules/safer-buffer" + }, + "sshpk": { + "pkg": { + "name": "sshpk", + "version": "1.18.0" + } + }, + "tweetnacl": { + "link": "../../·npm·tweetnacl@0.14.5/node_modules/tweetnacl" + } + } + }, + { + "id": "·npm·stack-utils@2.0.6", + "node_modules": { + "escape-string-regexp": { + "link": "../../·npm·escape-string-regexp@2.0.0/node_modules/escape-string-regexp" + }, + "stack-utils": { + "pkg": { + "name": "stack-utils", + "version": "2.0.6" + } + } + } + }, + { + "id": "·npm·string-width@4.2.3", + "node_modules": { + "emoji-regex": { + "link": "../../·npm·emoji-regex@8.0.0/node_modules/emoji-regex" + }, + "is-fullwidth-code-point": { + "link": "../../·npm·is-fullwidth-code-point@3.0.0/node_modules/is-fullwidth-code-point" + }, + "string-width": { + "pkg": { + "name": "string-width", + "version": "4.2.3" + } + }, + "strip-ansi": { + "link": "../../·npm·strip-ansi@6.0.1/node_modules/strip-ansi" + } + } + }, + { + "id": "·npm·strip-ansi@6.0.1", + "node_modules": { + "ansi-regex": { + "link": "../../·npm·ansi-regex@5.0.1/node_modules/ansi-regex" + }, + "strip-ansi": { + "pkg": { + "name": "strip-ansi", + "version": "6.0.1" + } + } + } + }, + { + "id": "·npm·strip-bom@4.0.0", + "node_modules": { + "strip-bom": { + "pkg": { + "name": "strip-bom", + "version": "4.0.0" + } + } + } + }, + { + "id": "·npm·supports-color@7.2.0", + "node_modules": { + "has-flag": { + "link": "../../·npm·has-flag@4.0.0/node_modules/has-flag" + }, + "supports-color": { + "pkg": { + "name": "supports-color", + "version": "7.2.0" + } + } + } + }, + { + "id": "·npm·tap-mocha-reporter@5.0.4", + "node_modules": { + ".bin": { + "dir": true + }, + "color-support": { + "link": "../../·npm·color-support@1.1.3/node_modules/color-support" + }, + "debug": { + "link": "../../·npm·debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms/node_modules/debug" + }, + "diff": { + "link": "../../·npm·diff@4.0.4/node_modules/diff" + }, + "escape-string-regexp": { + "link": "../../·npm·escape-string-regexp@2.0.0/node_modules/escape-string-regexp" + }, + "glob": { + "link": "../../·npm·glob@7.2.3/node_modules/glob" + }, + "tap-mocha-reporter": { + "pkg": { + "name": "tap-mocha-reporter", + "version": "5.0.4" + } + }, + "tap-parser": { + "link": "../../·npm·tap-parser@11.0.2/node_modules/tap-parser" + }, + "tap-yaml": { + "link": "../../·npm·tap-yaml@1.0.2/node_modules/tap-yaml" + }, + "unicode-length": { + "link": "../../·npm·unicode-length@2.1.0/node_modules/unicode-length" + } + } + }, + { + "id": "·npm·tap-parser@11.0.2", + "node_modules": { + "events-to-array": { + "link": "../../·npm·events-to-array@1.1.2/node_modules/events-to-array" + }, + "minipass": { + "link": "../../·npm·minipass@3.3.6/node_modules/minipass" + }, + "tap-parser": { + "pkg": { + "name": "tap-parser", + "version": "11.0.2" + } + }, + "tap-yaml": { + "link": "../../·npm·tap-yaml@1.0.2/node_modules/tap-yaml" + } + } + }, + { + "id": "·npm·tap-yaml@1.0.2", + "node_modules": { + "tap-yaml": { + "pkg": { + "name": "tap-yaml", + "version": "1.0.2" + } + }, + "yaml": { + "link": "../../·npm·yaml@1.10.3/node_modules/yaml" + } + } + }, + { + "id": "·npm·tap@15.2.3", + "node_modules": { + ".bin": { + "dir": true + }, + "chokidar": { + "link": "../../·npm·chokidar@3.6.0/node_modules/chokidar" + }, + "coveralls": { + "link": "../../·npm·coveralls@3.1.1/node_modules/coveralls" + }, + "findit": { + "link": "../../·npm·findit@2.0.0/node_modules/findit" + }, + "foreground-child": { + "link": "../../·npm·foreground-child@2.0.0/node_modules/foreground-child" + }, + "fs-exists-cached": { + "link": "../../·npm·fs-exists-cached@1.0.0/node_modules/fs-exists-cached" + }, + "glob": { + "link": "../../·npm·glob@7.2.3/node_modules/glob" + }, + "isexe": { + "link": "../../·npm·isexe@2.0.0/node_modules/isexe" + }, + "istanbul-lib-processinfo": { + "link": "../../·npm·istanbul-lib-processinfo@2.0.3/node_modules/istanbul-lib-processinfo" + }, + "jackspeak": { + "link": "../../·npm·jackspeak@1.4.2/node_modules/jackspeak" + }, + "libtap": { + "link": "../../·npm·libtap@1.4.1/node_modules/libtap" + }, + "minipass": { + "link": "../../·npm·minipass@3.3.6/node_modules/minipass" + }, + "mkdirp": { + "link": "../../·npm·mkdirp@1.0.4/node_modules/mkdirp" + }, + "nyc": { + "link": "../../·npm·nyc@15.1.0/node_modules/nyc" + }, + "opener": { + "link": "../../·npm·opener@1.5.2/node_modules/opener" + }, + "rimraf": { + "link": "../../·npm·rimraf@3.0.2/node_modules/rimraf" + }, + "signal-exit": { + "link": "../../·npm·signal-exit@3.0.7/node_modules/signal-exit" + }, + "source-map-support": { + "link": "../../·npm·source-map-support@0.5.21/node_modules/source-map-support" + }, + "tap": { + "pkg": { + "name": "tap", + "version": "15.2.3" + } + }, + "tap-mocha-reporter": { + "link": "../../·npm·tap-mocha-reporter@5.0.4/node_modules/tap-mocha-reporter" + }, + "tap-parser": { + "link": "../../·npm·tap-parser@11.0.2/node_modules/tap-parser" + }, + "tap-yaml": { + "link": "../../·npm·tap-yaml@1.0.2/node_modules/tap-yaml" + }, + "tcompare": { + "link": "../../·npm·tcompare@5.0.7/node_modules/tcompare" + }, + "which": { + "link": "../../·npm·which@2.0.2/node_modules/which" + } + } + }, + { + "id": "·npm·tcompare@5.0.7", + "node_modules": { + "diff": { + "link": "../../·npm·diff@4.0.4/node_modules/diff" + }, + "tcompare": { + "pkg": { + "name": "tcompare", + "version": "5.0.7" + } + } + } + }, + { + "id": "·npm·test-exclude@6.0.0", + "node_modules": { + "@istanbuljs/schema": { + "link": "../../../·npm·@istanbuljs§schema@0.1.6/node_modules/@istanbuljs/schema" + }, + "glob": { + "link": "../../·npm·glob@7.2.3/node_modules/glob" + }, + "minimatch": { + "link": "../../·npm·minimatch@3.1.5/node_modules/minimatch" + }, + "test-exclude": { + "pkg": { + "name": "test-exclude", + "version": "6.0.0" + } + } + } + }, + { + "id": "·npm·to-regex-range@5.0.1", + "node_modules": { + "is-number": { + "link": "../../·npm·is-number@7.0.0/node_modules/is-number" + }, + "to-regex-range": { + "pkg": { + "name": "to-regex-range", + "version": "5.0.1" + } + } + } + }, + { + "id": "·npm·tough-cookie@2.5.0", + "node_modules": { + "psl": { + "link": "../../·npm·psl@1.15.0/node_modules/psl" + }, + "punycode": { + "link": "../../·npm·punycode@2.3.1/node_modules/punycode" + }, + "tough-cookie": { + "pkg": { + "name": "tough-cookie", + "version": "2.5.0" + } + } + } + }, + { + "id": "·npm·trivial-deferred@1.1.2", + "node_modules": { + "trivial-deferred": { + "pkg": { + "name": "trivial-deferred", + "version": "1.1.2" + } + } + } + }, + { + "id": "·npm·tunnel-agent@0.6.0", + "node_modules": { + "safe-buffer": { + "link": "../../·npm·safe-buffer@5.2.1/node_modules/safe-buffer" + }, + "tunnel-agent": { + "pkg": { + "name": "tunnel-agent", + "version": "0.6.0" + } + } + } + }, + { + "id": "·npm·tweetnacl@0.14.5", + "node_modules": { + "tweetnacl": { + "pkg": { + "name": "tweetnacl", + "version": "0.14.5" + } + } + } + }, + { + "id": "·npm·type-fest@0.8.1", + "node_modules": { + "type-fest": { + "pkg": { + "name": "type-fest", + "version": "0.8.1" + } + } + } + }, + { + "id": "·npm·typedarray-to-buffer@3.1.5", + "node_modules": { + "is-typedarray": { + "link": "../../·npm·is-typedarray@1.0.0/node_modules/is-typedarray" + }, + "typedarray-to-buffer": { + "pkg": { + "name": "typedarray-to-buffer", + "version": "3.1.5" + } + } + } + }, + { + "id": "·npm·unicode-length@2.1.0", + "node_modules": { + "punycode": { + "link": "../../·npm·punycode@2.3.1/node_modules/punycode" + }, + "unicode-length": { + "pkg": { + "name": "unicode-length", + "version": "2.1.0" + } + } + } + }, + { + "id": "·npm·update-browserslist-db@1.3.3·%E1%B9%97%3A6", + "node_modules": { + ".bin": { + "dir": true + }, + "browserslist": { + "link": "../../·npm·browserslist@4.29.1/node_modules/browserslist" + }, + "escalade": { + "link": "../../·npm·escalade@3.2.0/node_modules/escalade" + }, + "picocolors": { + "link": "../../·npm·picocolors@1.1.1/node_modules/picocolors" + }, + "update-browserslist-db": { + "pkg": { + "name": "update-browserslist-db", + "version": "1.3.3" + } + } + } + }, + { + "id": "·npm·uri-js@4.4.1", + "node_modules": { + "punycode": { + "link": "../../·npm·punycode@2.3.1/node_modules/punycode" + }, + "uri-js": { + "pkg": { + "name": "uri-js", + "version": "4.4.1" + } + } + } + }, + { + "id": "·npm·use-sync-external-store@1.2.0", + "node_modules": { + "react": { + "link": "../../·npm·react@18.2.0/node_modules/react" + }, + "use-sync-external-store": { + "pkg": { + "name": "use-sync-external-store", + "version": "1.2.0" + } + } + } + }, + { + "id": "·npm·uuid@3.4.0", + "node_modules": { + "uuid": { + "pkg": { + "name": "uuid", + "version": "3.4.0" + } + } + } + }, + { + "id": "·npm·uuid@8.3.2", + "node_modules": { + "uuid": { + "pkg": { + "name": "uuid", + "version": "8.3.2" + } + } + } + }, + { + "id": "·npm·verror@1.10.0", + "node_modules": { + "assert-plus": { + "link": "../../·npm·assert-plus@1.0.0/node_modules/assert-plus" + }, + "core-util-is": { + "link": "../../·npm·core-util-is@1.0.2/node_modules/core-util-is" + }, + "extsprintf": { + "link": "../../·npm·extsprintf@1.3.0/node_modules/extsprintf" + }, + "verror": { + "pkg": { + "name": "verror", + "version": "1.10.0" + } + } + } + }, + { + "id": "·npm·which-module@2.0.1", + "node_modules": { + "which-module": { + "pkg": { + "name": "which-module", + "version": "2.0.1" + } + } + } + }, + { + "id": "·npm·which@2.0.2", + "node_modules": { + "isexe": { + "link": "../../·npm·isexe@2.0.0/node_modules/isexe" + }, + "which": { + "pkg": { + "name": "which", + "version": "2.0.2" + } + } + } + }, + { + "id": "·npm·wrap-ansi@6.2.0", + "node_modules": { + "ansi-styles": { + "link": "../../·npm·ansi-styles@4.3.0/node_modules/ansi-styles" + }, + "string-width": { + "link": "../../·npm·string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../../·npm·strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "wrap-ansi": { + "pkg": { + "name": "wrap-ansi", + "version": "6.2.0" + } + } + } + }, + { + "id": "·npm·wrap-ansi@7.0.0", + "node_modules": { + "ansi-styles": { + "link": "../../·npm·ansi-styles@4.3.0/node_modules/ansi-styles" + }, + "string-width": { + "link": "../../·npm·string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../../·npm·strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "wrap-ansi": { + "pkg": { + "name": "wrap-ansi", + "version": "7.0.0" + } + } + } + }, + { + "id": "·npm·wrappy@1.0.2", + "node_modules": { + "wrappy": { + "pkg": { + "name": "wrappy", + "version": "1.0.2" + } + } + } + }, + { + "id": "·npm·write-file-atomic@3.0.3", + "node_modules": { + "imurmurhash": { + "link": "../../·npm·imurmurhash@0.1.4/node_modules/imurmurhash" + }, + "is-typedarray": { + "link": "../../·npm·is-typedarray@1.0.0/node_modules/is-typedarray" + }, + "signal-exit": { + "link": "../../·npm·signal-exit@3.0.7/node_modules/signal-exit" + }, + "typedarray-to-buffer": { + "link": "../../·npm·typedarray-to-buffer@3.1.5/node_modules/typedarray-to-buffer" + }, + "write-file-atomic": { + "pkg": { + "name": "write-file-atomic", + "version": "3.0.3" + } + } + } + }, + { + "id": "·npm·y18n@4.0.3", + "node_modules": { + "y18n": { + "pkg": { + "name": "y18n", + "version": "4.0.3" + } + } + } + }, + { + "id": "·npm·yallist@3.1.1", + "node_modules": { + "yallist": { + "pkg": { + "name": "yallist", + "version": "3.1.1" + } + } + } + }, + { + "id": "·npm·yallist@4.0.0", + "node_modules": { + "yallist": { + "pkg": { + "name": "yallist", + "version": "4.0.0" + } + } + } + }, + { + "id": "·npm·yaml@1.10.3", + "node_modules": { + "yaml": { + "pkg": { + "name": "yaml", + "version": "1.10.3" + } + } + } + }, + { + "id": "·npm·yargs-parser@18.1.3", + "node_modules": { + "camelcase": { + "link": "../../·npm·camelcase@5.3.1/node_modules/camelcase" + }, + "decamelize": { + "link": "../../·npm·decamelize@1.2.0/node_modules/decamelize" + }, + "yargs-parser": { + "pkg": { + "name": "yargs-parser", + "version": "18.1.3" + } + } + } + }, + { + "id": "·npm·yargs@15.4.1", + "node_modules": { + "cliui": { + "link": "../../·npm·cliui@6.0.0/node_modules/cliui" + }, + "decamelize": { + "link": "../../·npm·decamelize@1.2.0/node_modules/decamelize" + }, + "find-up": { + "link": "../../·npm·find-up@4.1.0/node_modules/find-up" + }, + "get-caller-file": { + "link": "../../·npm·get-caller-file@2.0.5/node_modules/get-caller-file" + }, + "require-directory": { + "link": "../../·npm·require-directory@2.1.1/node_modules/require-directory" + }, + "require-main-filename": { + "link": "../../·npm·require-main-filename@2.0.0/node_modules/require-main-filename" + }, + "set-blocking": { + "link": "../../·npm·set-blocking@2.0.0/node_modules/set-blocking" + }, + "string-width": { + "link": "../../·npm·string-width@4.2.3/node_modules/string-width" + }, + "which-module": { + "link": "../../·npm·which-module@2.0.1/node_modules/which-module" + }, + "y18n": { + "link": "../../·npm·y18n@4.0.3/node_modules/y18n" + }, + "yargs": { + "pkg": { + "name": "yargs", + "version": "15.4.1" + } + }, + "yargs-parser": { + "link": "../../·npm·yargs-parser@18.1.3/node_modules/yargs-parser" + } + } + } + ], + "importers": { + ".bin": { + "dir": true + }, + "@isaacs/string-locale-compare": { + "link": "../.vlt/·npm·@isaacs§string-locale-compare@1.1.0/node_modules/@isaacs/string-locale-compare" + }, + "debug": { + "link": ".vlt/·npm·debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms/node_modules/debug" + }, + "left-pad": { + "link": ".vlt/·npm·left-pad@1.3.0/node_modules/left-pad" + }, + "localdir": { + "link": "../vendor/localdir" + }, + "lp-alias": { + "link": ".vlt/·npm·left-pad@1.1.3/node_modules/left-pad" + }, + "lp-remote": { + "link": ".vlt/remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz/node_modules/left-pad" + }, + "ms-tgz": { + "link": ".vlt/file·vendor§ms-2.1.2.tgz/node_modules/ms" + }, + "react": { + "link": ".vlt/·npm·react@18.2.0/node_modules/react" + }, + "semver_x": { + "link": ".vlt/·npm·semver@7.6.0/node_modules/semver" + }, + "slc-git": { + "link": ".vlt/git·github%3Aisaacs§string-locale-compare·v1.1.0/node_modules/@isaacs/string-locale-compare" + }, + "use-sync-external-store": { + "link": ".vlt/·npm·use-sync-external-store@1.2.0/node_modules/use-sync-external-store" + } + }, + "members": {}, + "linkTargets": { + "vendor/localdir": { + "name": "localdir", + "version": "1.3.0" + } + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.0-rc.22-workspace/README.md b/crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.0-rc.22-workspace/README.md new file mode 100644 index 00000000..111eb812 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.0-rc.22-workspace/README.md @@ -0,0 +1,14 @@ +# vlt 1.0.0-rc.22 workspace layout + +`listing.json` is the on-disk layout real vlt 1.0.0-rc.22 produced for a +two-member workspace, captured with `scripts/capture-vlt-tree.mjs` after a +cold `vlt install` (isolated XDG dirs and VLT_CACHE, VLT_TELEMETRY=0, +LANG=C). rc.15 through 1.0.7 split peer contexts into numbered store +entries, so `use-sync-external-store@1.2.0` has two real copies, +`~peer.2` (react 18) and `~peer.3` (react 17). Members hold only links +into the root store. + +- root `package.json`: `{"dependencies": {"debug": "4.3.4", "ms": "2.1.3"}}` +- `vlt.json`: `{"config": {"registries": {"npm": "https://registry.npmjs.org/"}}, "workspaces": "packages/*"}` +- `packages/a` (`@scope/a`): react 18.2.0, use-sync-external-store 1.2.0, `@scope/b: workspace:*` +- `packages/b` (`@scope/b`): react 17.0.2, use-sync-external-store 1.2.0 diff --git a/crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.0-rc.22-workspace/listing.json b/crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.0-rc.22-workspace/listing.json new file mode 100644 index 00000000..5a58d52a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.0-rc.22-workspace/listing.json @@ -0,0 +1,204 @@ +{ + "vlt": "1.0.0-rc.22", + "lockfileVersion": 1, + "storeFiles": [ + "vlt.json" + ], + "hoist": { + "debug": { + "link": "../~npm~debug@4.3.4/node_modules/debug" + }, + "js-tokens": { + "link": "../~npm~js-tokens@4.0.0/node_modules/js-tokens" + }, + "loose-envify": { + "link": "../~npm~loose-envify@1.4.0/node_modules/loose-envify" + }, + "ms": { + "link": "../~npm~ms@2.1.3/node_modules/ms" + }, + "object-assign": { + "link": "../~npm~object-assign@4.1.1/node_modules/object-assign" + }, + "react": { + "link": "../~npm~react@18.2.0/node_modules/react" + }, + "use-sync-external-store": { + "link": "../~npm~use-sync-external-store@1.2.0~peer.2/node_modules/use-sync-external-store" + } + }, + "store": [ + { + "id": "~npm~debug@4.3.4", + "node_modules": { + "debug": { + "pkg": { + "name": "debug", + "version": "4.3.4" + } + }, + "ms": { + "link": "../../~npm~ms@2.1.2/node_modules/ms" + } + } + }, + { + "id": "~npm~js-tokens@4.0.0", + "node_modules": { + "js-tokens": { + "pkg": { + "name": "js-tokens", + "version": "4.0.0" + } + } + } + }, + { + "id": "~npm~loose-envify@1.4.0", + "node_modules": { + "js-tokens": { + "link": "../../~npm~js-tokens@4.0.0/node_modules/js-tokens" + }, + "loose-envify": { + "pkg": { + "name": "loose-envify", + "version": "1.4.0" + } + } + } + }, + { + "id": "~npm~ms@2.1.2", + "node_modules": { + "ms": { + "pkg": { + "name": "ms", + "version": "2.1.2" + } + } + } + }, + { + "id": "~npm~ms@2.1.3", + "node_modules": { + "ms": { + "pkg": { + "name": "ms", + "version": "2.1.3" + } + } + } + }, + { + "id": "~npm~object-assign@4.1.1", + "node_modules": { + "object-assign": { + "pkg": { + "name": "object-assign", + "version": "4.1.1" + } + } + } + }, + { + "id": "~npm~react@17.0.2", + "node_modules": { + ".bin": { + "dir": true + }, + "loose-envify": { + "link": "../../~npm~loose-envify@1.4.0/node_modules/loose-envify" + }, + "object-assign": { + "link": "../../~npm~object-assign@4.1.1/node_modules/object-assign" + }, + "react": { + "pkg": { + "name": "react", + "version": "17.0.2" + } + } + } + }, + { + "id": "~npm~react@18.2.0", + "node_modules": { + ".bin": { + "dir": true + }, + "loose-envify": { + "link": "../../~npm~loose-envify@1.4.0/node_modules/loose-envify" + }, + "react": { + "pkg": { + "name": "react", + "version": "18.2.0" + } + } + } + }, + { + "id": "~npm~use-sync-external-store@1.2.0~peer.2", + "node_modules": { + "react": { + "link": "../../~npm~react@18.2.0/node_modules/react" + }, + "use-sync-external-store": { + "pkg": { + "name": "use-sync-external-store", + "version": "1.2.0" + } + } + } + }, + { + "id": "~npm~use-sync-external-store@1.2.0~peer.3", + "node_modules": { + "react": { + "link": "../../~npm~react@17.0.2/node_modules/react" + }, + "use-sync-external-store": { + "pkg": { + "name": "use-sync-external-store", + "version": "1.2.0" + } + } + } + } + ], + "importers": { + "debug": { + "link": ".vlt/~npm~debug@4.3.4/node_modules/debug" + }, + "ms": { + "link": ".vlt/~npm~ms@2.1.3/node_modules/ms" + } + }, + "members": { + "packages/a": { + "@scope/b": { + "link": "../../../b" + }, + "react": { + "link": "../../../node_modules/.vlt/~npm~react@18.2.0/node_modules/react" + }, + "use-sync-external-store": { + "link": "../../../node_modules/.vlt/~npm~use-sync-external-store@1.2.0~peer.2/node_modules/use-sync-external-store" + } + }, + "packages/b": { + "react": { + "link": "../../../node_modules/.vlt/~npm~react@17.0.2/node_modules/react" + }, + "use-sync-external-store": { + "link": "../../../node_modules/.vlt/~npm~use-sync-external-store@1.2.0~peer.3/node_modules/use-sync-external-store" + } + } + }, + "linkTargets": { + "packages/b": { + "name": "@scope/b", + "version": "1.0.0" + } + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.10/README.md b/crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.10/README.md new file mode 100644 index 00000000..a5ccc6da --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.10/README.md @@ -0,0 +1,21 @@ +# vlt 1.0.10 installed layout + +`listing.json` is the on-disk layout real vlt 1.0.10 produced, captured with +`scripts/capture-vlt-tree.mjs` right after a cold `vlt install` (isolated +XDG dirs and VLT_CACHE, VLT_TELEMETRY=0, LANG=C, no lockfile). Store entry +names are byte-exact; the crawler tests in `crawler_npm_e2e.rs` stage the +listing as real directories, package.json files and relative symlinks. + +Project (`package.json` dependencies): + +- "left-pad": "1.3.0", "debug": "4.3.4", "@isaacs/string-locale-compare": "1.1.0" +- "react": "18.2.0", "use-sync-external-store": "1.2.0" +- "lp-alias": "npm:left-pad@1.1.3", "semver_x": "npm:semver@7.6.0" +- "slc-git": "git+https://github.com/isaacs/string-locale-compare.git#v1.1.0" +- "lp-remote": "https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz" +- "ms-tgz": "file:./vendor/ms-2.1.2.tgz" +- "localdir": "file:./vendor/localdir" (a left-pad@1.3.0 copy renamed localdir) + +`vlt.json`: `{"config": {"registries": {"npm": "https://registry.npmjs.org/"}}, "modifiers": {":root > #debug > #ms": "2.1.3"}}`. + +The git dependency's devDependencies account for most store entries. diff --git a/crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.10/listing.json b/crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.10/listing.json new file mode 100644 index 00000000..42ca81ea --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-trees/1.0.10/listing.json @@ -0,0 +1,4596 @@ +{ + "vlt": "1.0.10", + "lockfileVersion": 1, + "storeFiles": [ + "vlt.json" + ], + "hoist": { + "@babel/code-frame": { + "link": "../../~npm~@babel+code-frame@7.29.7/node_modules/@babel/code-frame" + }, + "@babel/compat-data": { + "link": "../../~npm~@babel+compat-data@7.29.7/node_modules/@babel/compat-data" + }, + "@babel/core": { + "link": "../../~npm~@babel+core@7.29.7/node_modules/@babel/core" + }, + "@babel/generator": { + "link": "../../~npm~@babel+generator@7.29.8/node_modules/@babel/generator" + }, + "@babel/helper-compilation-targets": { + "link": "../../~npm~@babel+helper-compilation-targets@7.29.7/node_modules/@babel/helper-compilation-targets" + }, + "@babel/helper-globals": { + "link": "../../~npm~@babel+helper-globals@7.29.7/node_modules/@babel/helper-globals" + }, + "@babel/helper-module-imports": { + "link": "../../~npm~@babel+helper-module-imports@7.29.7/node_modules/@babel/helper-module-imports" + }, + "@babel/helper-module-transforms": { + "link": "../../~npm~@babel+helper-module-transforms@7.29.7~peer.a8a63a14dad8d5d8/node_modules/@babel/helper-module-transforms" + }, + "@babel/helper-string-parser": { + "link": "../../~npm~@babel+helper-string-parser@7.29.7/node_modules/@babel/helper-string-parser" + }, + "@babel/helper-validator-identifier": { + "link": "../../~npm~@babel+helper-validator-identifier@7.29.7/node_modules/@babel/helper-validator-identifier" + }, + "@babel/helper-validator-option": { + "link": "../../~npm~@babel+helper-validator-option@7.29.7/node_modules/@babel/helper-validator-option" + }, + "@babel/helpers": { + "link": "../../~npm~@babel+helpers@7.29.7/node_modules/@babel/helpers" + }, + "@babel/parser": { + "link": "../../~npm~@babel+parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/template": { + "link": "../../~npm~@babel+template@7.29.7/node_modules/@babel/template" + }, + "@babel/traverse": { + "link": "../../~npm~@babel+traverse@7.29.8/node_modules/@babel/traverse" + }, + "@babel/types": { + "link": "../../~npm~@babel+types@7.29.8/node_modules/@babel/types" + }, + "@isaacs/string-locale-compare": { + "link": "../../~npm~@isaacs+string-locale-compare@1.1.0/node_modules/@isaacs/string-locale-compare" + }, + "@istanbuljs/load-nyc-config": { + "link": "../../~npm~@istanbuljs+load-nyc-config@1.1.0/node_modules/@istanbuljs/load-nyc-config" + }, + "@istanbuljs/schema": { + "link": "../../~npm~@istanbuljs+schema@0.1.6/node_modules/@istanbuljs/schema" + }, + "@jridgewell/gen-mapping": { + "link": "../../~npm~@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/gen-mapping" + }, + "@jridgewell/remapping": { + "link": "../../~npm~@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping" + }, + "@jridgewell/resolve-uri": { + "link": "../../~npm~@jridgewell+resolve-uri@3.1.2/node_modules/@jridgewell/resolve-uri" + }, + "@jridgewell/sourcemap-codec": { + "link": "../../~npm~@jridgewell+sourcemap-codec@1.6.0/node_modules/@jridgewell/sourcemap-codec" + }, + "@jridgewell/trace-mapping": { + "link": "../../~npm~@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/trace-mapping" + }, + "aggregate-error": { + "link": "../~npm~aggregate-error@3.1.0/node_modules/aggregate-error" + }, + "ajv": { + "link": "../~npm~ajv@6.15.0/node_modules/ajv" + }, + "ansi-regex": { + "link": "../~npm~ansi-regex@5.0.1/node_modules/ansi-regex" + }, + "ansi-styles": { + "link": "../~npm~ansi-styles@4.3.0/node_modules/ansi-styles" + }, + "anymatch": { + "link": "../~npm~anymatch@3.1.3/node_modules/anymatch" + }, + "append-transform": { + "link": "../~npm~append-transform@2.0.0/node_modules/append-transform" + }, + "archy": { + "link": "../~npm~archy@1.0.0/node_modules/archy" + }, + "argparse": { + "link": "../~npm~argparse@1.0.10/node_modules/argparse" + }, + "asn1": { + "link": "../~npm~asn1@0.2.6/node_modules/asn1" + }, + "assert-plus": { + "link": "../~npm~assert-plus@1.0.0/node_modules/assert-plus" + }, + "async-hook-domain": { + "link": "../~npm~async-hook-domain@2.0.4/node_modules/async-hook-domain" + }, + "asynckit": { + "link": "../~npm~asynckit@0.4.0/node_modules/asynckit" + }, + "aws-sign2": { + "link": "../~npm~aws-sign2@0.7.0/node_modules/aws-sign2" + }, + "aws4": { + "link": "../~npm~aws4@1.13.2/node_modules/aws4" + }, + "balanced-match": { + "link": "../~npm~balanced-match@1.0.2/node_modules/balanced-match" + }, + "baseline-browser-mapping": { + "link": "../~npm~baseline-browser-mapping@2.11.26/node_modules/baseline-browser-mapping" + }, + "bcrypt-pbkdf": { + "link": "../~npm~bcrypt-pbkdf@1.0.2/node_modules/bcrypt-pbkdf" + }, + "binary-extensions": { + "link": "../~npm~binary-extensions@2.3.0/node_modules/binary-extensions" + }, + "bind-obj-methods": { + "link": "../~npm~bind-obj-methods@3.0.0/node_modules/bind-obj-methods" + }, + "brace-expansion": { + "link": "../~npm~brace-expansion@1.1.21/node_modules/brace-expansion" + }, + "braces": { + "link": "../~npm~braces@3.0.3/node_modules/braces" + }, + "browserslist": { + "link": "../~npm~browserslist@4.29.1/node_modules/browserslist" + }, + "buffer-from": { + "link": "../~npm~buffer-from@1.1.2/node_modules/buffer-from" + }, + "caching-transform": { + "link": "../~npm~caching-transform@4.0.0/node_modules/caching-transform" + }, + "camelcase": { + "link": "../~npm~camelcase@5.3.1/node_modules/camelcase" + }, + "caniuse-lite": { + "link": "../~npm~caniuse-lite@1.0.30001812/node_modules/caniuse-lite" + }, + "caseless": { + "link": "../~npm~caseless@0.12.0/node_modules/caseless" + }, + "chokidar": { + "link": "../~npm~chokidar@3.6.0/node_modules/chokidar" + }, + "clean-stack": { + "link": "../~npm~clean-stack@2.2.0/node_modules/clean-stack" + }, + "cliui": { + "link": "../~npm~cliui@7.0.4/node_modules/cliui" + }, + "color-convert": { + "link": "../~npm~color-convert@2.0.1/node_modules/color-convert" + }, + "color-name": { + "link": "../~npm~color-name@1.1.4/node_modules/color-name" + }, + "color-support": { + "link": "../~npm~color-support@1.1.3/node_modules/color-support" + }, + "combined-stream": { + "link": "../~npm~combined-stream@1.0.8/node_modules/combined-stream" + }, + "commondir": { + "link": "../~npm~commondir@1.0.1/node_modules/commondir" + }, + "concat-map": { + "link": "../~npm~concat-map@0.0.1/node_modules/concat-map" + }, + "convert-source-map": { + "link": "../~npm~convert-source-map@2.0.0/node_modules/convert-source-map" + }, + "core-util-is": { + "link": "../~npm~core-util-is@1.0.2/node_modules/core-util-is" + }, + "coveralls": { + "link": "../~npm~coveralls@3.1.1/node_modules/coveralls" + }, + "cross-spawn": { + "link": "../~npm~cross-spawn@7.0.6/node_modules/cross-spawn" + }, + "dashdash": { + "link": "../~npm~dashdash@1.14.1/node_modules/dashdash" + }, + "debug": { + "link": "../~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms/node_modules/debug" + }, + "decamelize": { + "link": "../~npm~decamelize@1.2.0/node_modules/decamelize" + }, + "default-require-extensions": { + "link": "../~npm~default-require-extensions@3.0.1/node_modules/default-require-extensions" + }, + "delayed-stream": { + "link": "../~npm~delayed-stream@1.0.0/node_modules/delayed-stream" + }, + "diff": { + "link": "../~npm~diff@4.0.4/node_modules/diff" + }, + "ecc-jsbn": { + "link": "../~npm~ecc-jsbn@0.1.2/node_modules/ecc-jsbn" + }, + "electron-to-chromium": { + "link": "../~npm~electron-to-chromium@1.5.439/node_modules/electron-to-chromium" + }, + "emoji-regex": { + "link": "../~npm~emoji-regex@8.0.0/node_modules/emoji-regex" + }, + "es6-error": { + "link": "../~npm~es6-error@4.1.1/node_modules/es6-error" + }, + "escalade": { + "link": "../~npm~escalade@3.2.0/node_modules/escalade" + }, + "escape-string-regexp": { + "link": "../~npm~escape-string-regexp@2.0.0/node_modules/escape-string-regexp" + }, + "esprima": { + "link": "../~npm~esprima@4.0.1/node_modules/esprima" + }, + "events-to-array": { + "link": "../~npm~events-to-array@1.1.2/node_modules/events-to-array" + }, + "extend": { + "link": "../~npm~extend@3.0.2/node_modules/extend" + }, + "extsprintf": { + "link": "../~npm~extsprintf@1.3.0/node_modules/extsprintf" + }, + "fast-deep-equal": { + "link": "../~npm~fast-deep-equal@3.1.3/node_modules/fast-deep-equal" + }, + "fast-json-stable-stringify": { + "link": "../~npm~fast-json-stable-stringify@2.1.0/node_modules/fast-json-stable-stringify" + }, + "fill-range": { + "link": "../~npm~fill-range@7.1.1/node_modules/fill-range" + }, + "find-cache-dir": { + "link": "../~npm~find-cache-dir@3.3.2/node_modules/find-cache-dir" + }, + "find-up": { + "link": "../~npm~find-up@4.1.0/node_modules/find-up" + }, + "findit": { + "link": "../~npm~findit@2.0.0/node_modules/findit" + }, + "foreground-child": { + "link": "../~npm~foreground-child@2.0.0/node_modules/foreground-child" + }, + "forever-agent": { + "link": "../~npm~forever-agent@0.6.1/node_modules/forever-agent" + }, + "form-data": { + "link": "../~npm~form-data@2.3.3/node_modules/form-data" + }, + "fromentries": { + "link": "../~npm~fromentries@1.3.2/node_modules/fromentries" + }, + "fs-exists-cached": { + "link": "../~npm~fs-exists-cached@1.0.0/node_modules/fs-exists-cached" + }, + "fs.realpath": { + "link": "../~npm~fs.realpath@1.0.0/node_modules/fs.realpath" + }, + "fsevents": { + "link": "../~npm~fsevents@2.3.3/node_modules/fsevents" + }, + "function-loop": { + "link": "../~npm~function-loop@2.0.1/node_modules/function-loop" + }, + "gensync": { + "link": "../~npm~gensync@1.0.0-beta.2/node_modules/gensync" + }, + "get-caller-file": { + "link": "../~npm~get-caller-file@2.0.5/node_modules/get-caller-file" + }, + "get-package-type": { + "link": "../~npm~get-package-type@0.1.0/node_modules/get-package-type" + }, + "getpass": { + "link": "../~npm~getpass@0.1.7/node_modules/getpass" + }, + "glob": { + "link": "../~npm~glob@7.2.3/node_modules/glob" + }, + "glob-parent": { + "link": "../~npm~glob-parent@5.1.2/node_modules/glob-parent" + }, + "graceful-fs": { + "link": "../~npm~graceful-fs@4.2.11/node_modules/graceful-fs" + }, + "har-schema": { + "link": "../~npm~har-schema@2.0.0/node_modules/har-schema" + }, + "har-validator": { + "link": "../~npm~har-validator@5.1.5/node_modules/har-validator" + }, + "has-flag": { + "link": "../~npm~has-flag@4.0.0/node_modules/has-flag" + }, + "hasha": { + "link": "../~npm~hasha@5.2.2/node_modules/hasha" + }, + "html-escaper": { + "link": "../~npm~html-escaper@2.0.2/node_modules/html-escaper" + }, + "http-signature": { + "link": "../~npm~http-signature@1.2.0/node_modules/http-signature" + }, + "imurmurhash": { + "link": "../~npm~imurmurhash@0.1.4/node_modules/imurmurhash" + }, + "indent-string": { + "link": "../~npm~indent-string@4.0.0/node_modules/indent-string" + }, + "inflight": { + "link": "../~npm~inflight@1.0.6/node_modules/inflight" + }, + "inherits": { + "link": "../~npm~inherits@2.0.4/node_modules/inherits" + }, + "is-binary-path": { + "link": "../~npm~is-binary-path@2.1.0/node_modules/is-binary-path" + }, + "is-extglob": { + "link": "../~npm~is-extglob@2.1.1/node_modules/is-extglob" + }, + "is-fullwidth-code-point": { + "link": "../~npm~is-fullwidth-code-point@3.0.0/node_modules/is-fullwidth-code-point" + }, + "is-glob": { + "link": "../~npm~is-glob@4.0.3/node_modules/is-glob" + }, + "is-number": { + "link": "../~npm~is-number@7.0.0/node_modules/is-number" + }, + "is-stream": { + "link": "../~npm~is-stream@2.0.1/node_modules/is-stream" + }, + "is-typedarray": { + "link": "../~npm~is-typedarray@1.0.0/node_modules/is-typedarray" + }, + "is-windows": { + "link": "../~npm~is-windows@1.0.2/node_modules/is-windows" + }, + "isexe": { + "link": "../~npm~isexe@2.0.0/node_modules/isexe" + }, + "isstream": { + "link": "../~npm~isstream@0.1.2/node_modules/isstream" + }, + "istanbul-lib-coverage": { + "link": "../~npm~istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-hook": { + "link": "../~npm~istanbul-lib-hook@3.0.0/node_modules/istanbul-lib-hook" + }, + "istanbul-lib-instrument": { + "link": "../~npm~istanbul-lib-instrument@4.0.3/node_modules/istanbul-lib-instrument" + }, + "istanbul-lib-processinfo": { + "link": "../~npm~istanbul-lib-processinfo@2.0.3/node_modules/istanbul-lib-processinfo" + }, + "istanbul-lib-report": { + "link": "../~npm~istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report" + }, + "istanbul-lib-source-maps": { + "link": "../~npm~istanbul-lib-source-maps@4.0.1/node_modules/istanbul-lib-source-maps" + }, + "istanbul-reports": { + "link": "../~npm~istanbul-reports@3.2.0/node_modules/istanbul-reports" + }, + "jackspeak": { + "link": "../~npm~jackspeak@1.4.2/node_modules/jackspeak" + }, + "js-tokens": { + "link": "../~npm~js-tokens@4.0.0/node_modules/js-tokens" + }, + "js-yaml": { + "link": "../~npm~js-yaml@3.15.2/node_modules/js-yaml" + }, + "jsbn": { + "link": "../~npm~jsbn@0.1.1/node_modules/jsbn" + }, + "jsesc": { + "link": "../~npm~jsesc@3.1.0/node_modules/jsesc" + }, + "json-schema": { + "link": "../~npm~json-schema@0.4.0/node_modules/json-schema" + }, + "json-schema-traverse": { + "link": "../~npm~json-schema-traverse@0.4.1/node_modules/json-schema-traverse" + }, + "json-stringify-safe": { + "link": "../~npm~json-stringify-safe@5.0.1/node_modules/json-stringify-safe" + }, + "json5": { + "link": "../~npm~json5@2.2.3/node_modules/json5" + }, + "jsprim": { + "link": "../~npm~jsprim@1.4.2/node_modules/jsprim" + }, + "lcov-parse": { + "link": "../~npm~lcov-parse@1.0.0/node_modules/lcov-parse" + }, + "left-pad": { + "link": "../~npm~left-pad@1.3.0/node_modules/left-pad" + }, + "libtap": { + "link": "../~npm~libtap@1.4.1/node_modules/libtap" + }, + "locate-path": { + "link": "../~npm~locate-path@5.0.0/node_modules/locate-path" + }, + "lodash.flattendeep": { + "link": "../~npm~lodash.flattendeep@4.4.0/node_modules/lodash.flattendeep" + }, + "log-driver": { + "link": "../~npm~log-driver@1.2.7/node_modules/log-driver" + }, + "loose-envify": { + "link": "../~npm~loose-envify@1.4.0/node_modules/loose-envify" + }, + "lru-cache": { + "link": "../~npm~lru-cache@6.0.0/node_modules/lru-cache" + }, + "make-dir": { + "link": "../~npm~make-dir@4.0.0/node_modules/make-dir" + }, + "mime-db": { + "link": "../~npm~mime-db@1.52.0/node_modules/mime-db" + }, + "mime-types": { + "link": "../~npm~mime-types@2.1.35/node_modules/mime-types" + }, + "minimatch": { + "link": "../~npm~minimatch@3.1.5/node_modules/minimatch" + }, + "minimist": { + "link": "../~npm~minimist@1.2.8/node_modules/minimist" + }, + "minipass": { + "link": "../~npm~minipass@3.3.6/node_modules/minipass" + }, + "mkdirp": { + "link": "../~npm~mkdirp@1.0.4/node_modules/mkdirp" + }, + "ms-tgz": { + "link": "../file~vendor+ms-2.1.2.tgz/node_modules/ms" + }, + "node-preload": { + "link": "../~npm~node-preload@0.2.1/node_modules/node-preload" + }, + "node-releases": { + "link": "../~npm~node-releases@2.0.57/node_modules/node-releases" + }, + "normalize-path": { + "link": "../~npm~normalize-path@3.0.0/node_modules/normalize-path" + }, + "nyc": { + "link": "../~npm~nyc@15.1.0/node_modules/nyc" + }, + "oauth-sign": { + "link": "../~npm~oauth-sign@0.9.0/node_modules/oauth-sign" + }, + "once": { + "link": "../~npm~once@1.4.0/node_modules/once" + }, + "opener": { + "link": "../~npm~opener@1.5.2/node_modules/opener" + }, + "own-or": { + "link": "../~npm~own-or@1.0.0/node_modules/own-or" + }, + "own-or-env": { + "link": "../~npm~own-or-env@1.0.2/node_modules/own-or-env" + }, + "p-limit": { + "link": "../~npm~p-limit@2.3.0/node_modules/p-limit" + }, + "p-locate": { + "link": "../~npm~p-locate@4.1.0/node_modules/p-locate" + }, + "p-map": { + "link": "../~npm~p-map@3.0.0/node_modules/p-map" + }, + "p-try": { + "link": "../~npm~p-try@2.2.0/node_modules/p-try" + }, + "package-hash": { + "link": "../~npm~package-hash@4.0.0/node_modules/package-hash" + }, + "path-exists": { + "link": "../~npm~path-exists@4.0.0/node_modules/path-exists" + }, + "path-is-absolute": { + "link": "../~npm~path-is-absolute@1.0.1/node_modules/path-is-absolute" + }, + "path-key": { + "link": "../~npm~path-key@3.1.1/node_modules/path-key" + }, + "performance-now": { + "link": "../~npm~performance-now@2.1.0/node_modules/performance-now" + }, + "picocolors": { + "link": "../~npm~picocolors@1.1.1/node_modules/picocolors" + }, + "picomatch": { + "link": "../~npm~picomatch@2.3.2/node_modules/picomatch" + }, + "pkg-dir": { + "link": "../~npm~pkg-dir@4.2.0/node_modules/pkg-dir" + }, + "process-on-spawn": { + "link": "../~npm~process-on-spawn@1.1.0/node_modules/process-on-spawn" + }, + "psl": { + "link": "../~npm~psl@1.15.0/node_modules/psl" + }, + "punycode": { + "link": "../~npm~punycode@2.3.1/node_modules/punycode" + }, + "qs": { + "link": "../~npm~qs@6.5.5/node_modules/qs" + }, + "react": { + "link": "../~npm~react@18.2.0/node_modules/react" + }, + "readdirp": { + "link": "../~npm~readdirp@3.6.0/node_modules/readdirp" + }, + "release-zalgo": { + "link": "../~npm~release-zalgo@1.0.0/node_modules/release-zalgo" + }, + "request": { + "link": "../~npm~request@2.88.2/node_modules/request" + }, + "require-directory": { + "link": "../~npm~require-directory@2.1.1/node_modules/require-directory" + }, + "require-main-filename": { + "link": "../~npm~require-main-filename@2.0.0/node_modules/require-main-filename" + }, + "resolve-from": { + "link": "../~npm~resolve-from@5.0.0/node_modules/resolve-from" + }, + "rimraf": { + "link": "../~npm~rimraf@3.0.2/node_modules/rimraf" + }, + "safe-buffer": { + "link": "../~npm~safe-buffer@5.2.1/node_modules/safe-buffer" + }, + "safer-buffer": { + "link": "../~npm~safer-buffer@2.1.2/node_modules/safer-buffer" + }, + "semver_x": { + "link": "../~npm~semver@7.6.0/node_modules/semver" + }, + "set-blocking": { + "link": "../~npm~set-blocking@2.0.0/node_modules/set-blocking" + }, + "shebang-command": { + "link": "../~npm~shebang-command@2.0.0/node_modules/shebang-command" + }, + "shebang-regex": { + "link": "../~npm~shebang-regex@3.0.0/node_modules/shebang-regex" + }, + "signal-exit": { + "link": "../~npm~signal-exit@3.0.7/node_modules/signal-exit" + }, + "source-map": { + "link": "../~npm~source-map@0.6.1/node_modules/source-map" + }, + "source-map-support": { + "link": "../~npm~source-map-support@0.5.21/node_modules/source-map-support" + }, + "spawn-wrap": { + "link": "../~npm~spawn-wrap@2.0.0/node_modules/spawn-wrap" + }, + "sprintf-js": { + "link": "../~npm~sprintf-js@1.0.3/node_modules/sprintf-js" + }, + "sshpk": { + "link": "../~npm~sshpk@1.18.0/node_modules/sshpk" + }, + "stack-utils": { + "link": "../~npm~stack-utils@2.0.6/node_modules/stack-utils" + }, + "string-width": { + "link": "../~npm~string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../~npm~strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "strip-bom": { + "link": "../~npm~strip-bom@4.0.0/node_modules/strip-bom" + }, + "supports-color": { + "link": "../~npm~supports-color@7.2.0/node_modules/supports-color" + }, + "tap": { + "link": "../~npm~tap@15.2.3~peer.6f88d0ccf17dbbdc/node_modules/tap" + }, + "tap-mocha-reporter": { + "link": "../~npm~tap-mocha-reporter@5.0.4/node_modules/tap-mocha-reporter" + }, + "tap-parser": { + "link": "../~npm~tap-parser@11.0.2/node_modules/tap-parser" + }, + "tap-yaml": { + "link": "../~npm~tap-yaml@1.0.2/node_modules/tap-yaml" + }, + "tcompare": { + "link": "../~npm~tcompare@5.0.7/node_modules/tcompare" + }, + "test-exclude": { + "link": "../~npm~test-exclude@6.0.0/node_modules/test-exclude" + }, + "to-regex-range": { + "link": "../~npm~to-regex-range@5.0.1/node_modules/to-regex-range" + }, + "tough-cookie": { + "link": "../~npm~tough-cookie@2.5.0/node_modules/tough-cookie" + }, + "trivial-deferred": { + "link": "../~npm~trivial-deferred@1.1.2/node_modules/trivial-deferred" + }, + "tunnel-agent": { + "link": "../~npm~tunnel-agent@0.6.0/node_modules/tunnel-agent" + }, + "tweetnacl": { + "link": "../~npm~tweetnacl@0.14.5/node_modules/tweetnacl" + }, + "type-fest": { + "link": "../~npm~type-fest@0.8.1/node_modules/type-fest" + }, + "typedarray-to-buffer": { + "link": "../~npm~typedarray-to-buffer@3.1.5/node_modules/typedarray-to-buffer" + }, + "unicode-length": { + "link": "../~npm~unicode-length@2.1.0/node_modules/unicode-length" + }, + "update-browserslist-db": { + "link": "../~npm~update-browserslist-db@1.3.3~peer.27c7ade2f48570ae/node_modules/update-browserslist-db" + }, + "uri-js": { + "link": "../~npm~uri-js@4.4.1/node_modules/uri-js" + }, + "use-sync-external-store": { + "link": "../~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba/node_modules/use-sync-external-store" + }, + "uuid": { + "link": "../~npm~uuid@8.3.2/node_modules/uuid" + }, + "verror": { + "link": "../~npm~verror@1.10.0/node_modules/verror" + }, + "which": { + "link": "../~npm~which@2.0.2/node_modules/which" + }, + "which-module": { + "link": "../~npm~which-module@2.0.1/node_modules/which-module" + }, + "wrap-ansi": { + "link": "../~npm~wrap-ansi@7.0.0/node_modules/wrap-ansi" + }, + "wrappy": { + "link": "../~npm~wrappy@1.0.2/node_modules/wrappy" + }, + "write-file-atomic": { + "link": "../~npm~write-file-atomic@3.0.3/node_modules/write-file-atomic" + }, + "y18n": { + "link": "../~npm~y18n@4.0.3/node_modules/y18n" + }, + "yallist": { + "link": "../~npm~yallist@4.0.0/node_modules/yallist" + }, + "yaml": { + "link": "../~npm~yaml@1.10.3/node_modules/yaml" + }, + "yargs": { + "link": "../~npm~yargs@15.4.1/node_modules/yargs" + }, + "yargs-parser": { + "link": "../~npm~yargs-parser@18.1.3/node_modules/yargs-parser" + } + }, + "store": [ + { + "id": "file~vendor+ms-2.1.2.tgz", + "node_modules": { + "ms": { + "pkg": { + "name": "ms", + "version": "2.1.2" + } + } + } + }, + { + "id": "git~git_phttps_c++github.com+isaacs+string-locale-compare.git~v1.1.0", + "node_modules": { + ".bin": { + "dir": true + }, + "@isaacs/string-locale-compare": { + "pkg": { + "name": "@isaacs/string-locale-compare", + "version": "1.1.0" + } + }, + "tap": { + "link": "../../~npm~tap@15.2.3~peer.6f88d0ccf17dbbdc/node_modules/tap" + } + } + }, + { + "id": "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "node_modules": { + "left-pad": { + "pkg": { + "name": "left-pad", + "version": "1.2.0" + } + } + } + }, + { + "id": "~npm~@babel+code-frame@7.29.7", + "node_modules": { + "@babel/code-frame": { + "pkg": { + "name": "@babel/code-frame", + "version": "7.29.7" + } + }, + "@babel/helper-validator-identifier": { + "link": "../../../~npm~@babel+helper-validator-identifier@7.29.7/node_modules/@babel/helper-validator-identifier" + }, + "js-tokens": { + "link": "../../~npm~js-tokens@4.0.0/node_modules/js-tokens" + }, + "picocolors": { + "link": "../../~npm~picocolors@1.1.1/node_modules/picocolors" + } + } + }, + { + "id": "~npm~@babel+compat-data@7.29.7", + "node_modules": { + "@babel/compat-data": { + "pkg": { + "name": "@babel/compat-data", + "version": "7.29.7" + } + } + } + }, + { + "id": "~npm~@babel+core@7.29.7", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/code-frame": { + "link": "../../../~npm~@babel+code-frame@7.29.7/node_modules/@babel/code-frame" + }, + "@babel/core": { + "pkg": { + "name": "@babel/core", + "version": "7.29.7" + } + }, + "@babel/generator": { + "link": "../../../~npm~@babel+generator@7.29.8/node_modules/@babel/generator" + }, + "@babel/helper-compilation-targets": { + "link": "../../../~npm~@babel+helper-compilation-targets@7.29.7/node_modules/@babel/helper-compilation-targets" + }, + "@babel/helper-module-transforms": { + "link": "../../../~npm~@babel+helper-module-transforms@7.29.7~peer.a8a63a14dad8d5d8/node_modules/@babel/helper-module-transforms" + }, + "@babel/helpers": { + "link": "../../../~npm~@babel+helpers@7.29.7/node_modules/@babel/helpers" + }, + "@babel/parser": { + "link": "../../../~npm~@babel+parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/template": { + "link": "../../../~npm~@babel+template@7.29.7/node_modules/@babel/template" + }, + "@babel/traverse": { + "link": "../../../~npm~@babel+traverse@7.29.8/node_modules/@babel/traverse" + }, + "@babel/types": { + "link": "../../../~npm~@babel+types@7.29.8/node_modules/@babel/types" + }, + "@jridgewell/remapping": { + "link": "../../../~npm~@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping" + }, + "convert-source-map": { + "link": "../../~npm~convert-source-map@2.0.0/node_modules/convert-source-map" + }, + "debug": { + "link": "../../~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms/node_modules/debug" + }, + "gensync": { + "link": "../../~npm~gensync@1.0.0-beta.2/node_modules/gensync" + }, + "json5": { + "link": "../../~npm~json5@2.2.3/node_modules/json5" + }, + "semver": { + "link": "../../~npm~semver@6.3.1/node_modules/semver" + } + } + }, + { + "id": "~npm~@babel+generator@7.29.8", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/generator": { + "pkg": { + "name": "@babel/generator", + "version": "7.29.8" + } + }, + "@babel/parser": { + "link": "../../../~npm~@babel+parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/types": { + "link": "../../../~npm~@babel+types@7.29.8/node_modules/@babel/types" + }, + "@jridgewell/gen-mapping": { + "link": "../../../~npm~@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/gen-mapping" + }, + "@jridgewell/trace-mapping": { + "link": "../../../~npm~@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/trace-mapping" + }, + "jsesc": { + "link": "../../~npm~jsesc@3.1.0/node_modules/jsesc" + } + } + }, + { + "id": "~npm~@babel+helper-compilation-targets@7.29.7", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/compat-data": { + "link": "../../../~npm~@babel+compat-data@7.29.7/node_modules/@babel/compat-data" + }, + "@babel/helper-compilation-targets": { + "pkg": { + "name": "@babel/helper-compilation-targets", + "version": "7.29.7" + } + }, + "@babel/helper-validator-option": { + "link": "../../../~npm~@babel+helper-validator-option@7.29.7/node_modules/@babel/helper-validator-option" + }, + "browserslist": { + "link": "../../~npm~browserslist@4.29.1/node_modules/browserslist" + }, + "lru-cache": { + "link": "../../~npm~lru-cache@5.1.1/node_modules/lru-cache" + }, + "semver": { + "link": "../../~npm~semver@6.3.1/node_modules/semver" + } + } + }, + { + "id": "~npm~@babel+helper-globals@7.29.7", + "node_modules": { + "@babel/helper-globals": { + "pkg": { + "name": "@babel/helper-globals", + "version": "7.29.7" + } + } + } + }, + { + "id": "~npm~@babel+helper-module-imports@7.29.7", + "node_modules": { + "@babel/helper-module-imports": { + "pkg": { + "name": "@babel/helper-module-imports", + "version": "7.29.7" + } + }, + "@babel/traverse": { + "link": "../../../~npm~@babel+traverse@7.29.8/node_modules/@babel/traverse" + }, + "@babel/types": { + "link": "../../../~npm~@babel+types@7.29.8/node_modules/@babel/types" + } + } + }, + { + "id": "~npm~@babel+helper-module-transforms@7.29.7~peer.a8a63a14dad8d5d8", + "node_modules": { + "@babel/core": { + "link": "../../../~npm~@babel+core@7.29.7/node_modules/@babel/core" + }, + "@babel/helper-module-imports": { + "link": "../../../~npm~@babel+helper-module-imports@7.29.7/node_modules/@babel/helper-module-imports" + }, + "@babel/helper-module-transforms": { + "pkg": { + "name": "@babel/helper-module-transforms", + "version": "7.29.7" + } + }, + "@babel/helper-validator-identifier": { + "link": "../../../~npm~@babel+helper-validator-identifier@7.29.7/node_modules/@babel/helper-validator-identifier" + }, + "@babel/traverse": { + "link": "../../../~npm~@babel+traverse@7.29.8/node_modules/@babel/traverse" + } + } + }, + { + "id": "~npm~@babel+helper-string-parser@7.29.7", + "node_modules": { + "@babel/helper-string-parser": { + "pkg": { + "name": "@babel/helper-string-parser", + "version": "7.29.7" + } + } + } + }, + { + "id": "~npm~@babel+helper-validator-identifier@7.29.7", + "node_modules": { + "@babel/helper-validator-identifier": { + "pkg": { + "name": "@babel/helper-validator-identifier", + "version": "7.29.7" + } + } + } + }, + { + "id": "~npm~@babel+helper-validator-option@7.29.7", + "node_modules": { + "@babel/helper-validator-option": { + "pkg": { + "name": "@babel/helper-validator-option", + "version": "7.29.7" + } + } + } + }, + { + "id": "~npm~@babel+helpers@7.29.7", + "node_modules": { + "@babel/helpers": { + "pkg": { + "name": "@babel/helpers", + "version": "7.29.7" + } + }, + "@babel/template": { + "link": "../../../~npm~@babel+template@7.29.7/node_modules/@babel/template" + }, + "@babel/types": { + "link": "../../../~npm~@babel+types@7.29.8/node_modules/@babel/types" + } + } + }, + { + "id": "~npm~@babel+parser@7.29.9", + "node_modules": { + "@babel/parser": { + "pkg": { + "name": "@babel/parser", + "version": "7.29.9" + } + }, + "@babel/types": { + "link": "../../../~npm~@babel+types@7.29.8/node_modules/@babel/types" + } + } + }, + { + "id": "~npm~@babel+template@7.29.7", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/code-frame": { + "link": "../../../~npm~@babel+code-frame@7.29.7/node_modules/@babel/code-frame" + }, + "@babel/parser": { + "link": "../../../~npm~@babel+parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/template": { + "pkg": { + "name": "@babel/template", + "version": "7.29.7" + } + }, + "@babel/types": { + "link": "../../../~npm~@babel+types@7.29.8/node_modules/@babel/types" + } + } + }, + { + "id": "~npm~@babel+traverse@7.29.8", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/code-frame": { + "link": "../../../~npm~@babel+code-frame@7.29.7/node_modules/@babel/code-frame" + }, + "@babel/generator": { + "link": "../../../~npm~@babel+generator@7.29.8/node_modules/@babel/generator" + }, + "@babel/helper-globals": { + "link": "../../../~npm~@babel+helper-globals@7.29.7/node_modules/@babel/helper-globals" + }, + "@babel/parser": { + "link": "../../../~npm~@babel+parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/template": { + "link": "../../../~npm~@babel+template@7.29.7/node_modules/@babel/template" + }, + "@babel/traverse": { + "pkg": { + "name": "@babel/traverse", + "version": "7.29.8" + } + }, + "@babel/types": { + "link": "../../../~npm~@babel+types@7.29.8/node_modules/@babel/types" + }, + "debug": { + "link": "../../~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms/node_modules/debug" + } + } + }, + { + "id": "~npm~@babel+types@7.29.8", + "node_modules": { + "@babel/helper-string-parser": { + "link": "../../../~npm~@babel+helper-string-parser@7.29.7/node_modules/@babel/helper-string-parser" + }, + "@babel/helper-validator-identifier": { + "link": "../../../~npm~@babel+helper-validator-identifier@7.29.7/node_modules/@babel/helper-validator-identifier" + }, + "@babel/types": { + "pkg": { + "name": "@babel/types", + "version": "7.29.8" + } + } + } + }, + { + "id": "~npm~@isaacs+string-locale-compare@1.1.0", + "node_modules": { + "@isaacs/string-locale-compare": { + "pkg": { + "name": "@isaacs/string-locale-compare", + "version": "1.1.0" + } + } + } + }, + { + "id": "~npm~@istanbuljs+load-nyc-config@1.1.0", + "node_modules": { + ".bin": { + "dir": true + }, + "@istanbuljs/load-nyc-config": { + "pkg": { + "name": "@istanbuljs/load-nyc-config", + "version": "1.1.0" + } + }, + "camelcase": { + "link": "../../~npm~camelcase@5.3.1/node_modules/camelcase" + }, + "find-up": { + "link": "../../~npm~find-up@4.1.0/node_modules/find-up" + }, + "get-package-type": { + "link": "../../~npm~get-package-type@0.1.0/node_modules/get-package-type" + }, + "js-yaml": { + "link": "../../~npm~js-yaml@3.15.2/node_modules/js-yaml" + }, + "resolve-from": { + "link": "../../~npm~resolve-from@5.0.0/node_modules/resolve-from" + } + } + }, + { + "id": "~npm~@istanbuljs+schema@0.1.6", + "node_modules": { + "@istanbuljs/schema": { + "pkg": { + "name": "@istanbuljs/schema", + "version": "0.1.6" + } + } + } + }, + { + "id": "~npm~@jridgewell+gen-mapping@0.3.13", + "node_modules": { + "@jridgewell/gen-mapping": { + "pkg": { + "name": "@jridgewell/gen-mapping", + "version": "0.3.13" + } + }, + "@jridgewell/sourcemap-codec": { + "link": "../../../~npm~@jridgewell+sourcemap-codec@1.6.0/node_modules/@jridgewell/sourcemap-codec" + }, + "@jridgewell/trace-mapping": { + "link": "../../../~npm~@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/trace-mapping" + } + } + }, + { + "id": "~npm~@jridgewell+remapping@2.3.5", + "node_modules": { + "@jridgewell/gen-mapping": { + "link": "../../../~npm~@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/gen-mapping" + }, + "@jridgewell/remapping": { + "pkg": { + "name": "@jridgewell/remapping", + "version": "2.3.5" + } + }, + "@jridgewell/trace-mapping": { + "link": "../../../~npm~@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/trace-mapping" + } + } + }, + { + "id": "~npm~@jridgewell+resolve-uri@3.1.2", + "node_modules": { + "@jridgewell/resolve-uri": { + "pkg": { + "name": "@jridgewell/resolve-uri", + "version": "3.1.2" + } + } + } + }, + { + "id": "~npm~@jridgewell+sourcemap-codec@1.6.0", + "node_modules": { + "@jridgewell/sourcemap-codec": { + "pkg": { + "name": "@jridgewell/sourcemap-codec", + "version": "1.6.0" + } + } + } + }, + { + "id": "~npm~@jridgewell+trace-mapping@0.3.31", + "node_modules": { + "@jridgewell/resolve-uri": { + "link": "../../../~npm~@jridgewell+resolve-uri@3.1.2/node_modules/@jridgewell/resolve-uri" + }, + "@jridgewell/sourcemap-codec": { + "link": "../../../~npm~@jridgewell+sourcemap-codec@1.6.0/node_modules/@jridgewell/sourcemap-codec" + }, + "@jridgewell/trace-mapping": { + "pkg": { + "name": "@jridgewell/trace-mapping", + "version": "0.3.31" + } + } + } + }, + { + "id": "~npm~aggregate-error@3.1.0", + "node_modules": { + "aggregate-error": { + "pkg": { + "name": "aggregate-error", + "version": "3.1.0" + } + }, + "clean-stack": { + "link": "../../~npm~clean-stack@2.2.0/node_modules/clean-stack" + }, + "indent-string": { + "link": "../../~npm~indent-string@4.0.0/node_modules/indent-string" + } + } + }, + { + "id": "~npm~ajv@6.15.0", + "node_modules": { + "ajv": { + "pkg": { + "name": "ajv", + "version": "6.15.0" + } + }, + "fast-deep-equal": { + "link": "../../~npm~fast-deep-equal@3.1.3/node_modules/fast-deep-equal" + }, + "fast-json-stable-stringify": { + "link": "../../~npm~fast-json-stable-stringify@2.1.0/node_modules/fast-json-stable-stringify" + }, + "json-schema-traverse": { + "link": "../../~npm~json-schema-traverse@0.4.1/node_modules/json-schema-traverse" + }, + "uri-js": { + "link": "../../~npm~uri-js@4.4.1/node_modules/uri-js" + } + } + }, + { + "id": "~npm~ansi-regex@5.0.1", + "node_modules": { + "ansi-regex": { + "pkg": { + "name": "ansi-regex", + "version": "5.0.1" + } + } + } + }, + { + "id": "~npm~ansi-styles@4.3.0", + "node_modules": { + "ansi-styles": { + "pkg": { + "name": "ansi-styles", + "version": "4.3.0" + } + }, + "color-convert": { + "link": "../../~npm~color-convert@2.0.1/node_modules/color-convert" + } + } + }, + { + "id": "~npm~anymatch@3.1.3", + "node_modules": { + "anymatch": { + "pkg": { + "name": "anymatch", + "version": "3.1.3" + } + }, + "normalize-path": { + "link": "../../~npm~normalize-path@3.0.0/node_modules/normalize-path" + }, + "picomatch": { + "link": "../../~npm~picomatch@2.3.2/node_modules/picomatch" + } + } + }, + { + "id": "~npm~append-transform@2.0.0", + "node_modules": { + "append-transform": { + "pkg": { + "name": "append-transform", + "version": "2.0.0" + } + }, + "default-require-extensions": { + "link": "../../~npm~default-require-extensions@3.0.1/node_modules/default-require-extensions" + } + } + }, + { + "id": "~npm~archy@1.0.0", + "node_modules": { + "archy": { + "pkg": { + "name": "archy", + "version": "1.0.0" + } + } + } + }, + { + "id": "~npm~argparse@1.0.10", + "node_modules": { + "argparse": { + "pkg": { + "name": "argparse", + "version": "1.0.10" + } + }, + "sprintf-js": { + "link": "../../~npm~sprintf-js@1.0.3/node_modules/sprintf-js" + } + } + }, + { + "id": "~npm~asn1@0.2.6", + "node_modules": { + "asn1": { + "pkg": { + "name": "asn1", + "version": "0.2.6" + } + }, + "safer-buffer": { + "link": "../../~npm~safer-buffer@2.1.2/node_modules/safer-buffer" + } + } + }, + { + "id": "~npm~assert-plus@1.0.0", + "node_modules": { + "assert-plus": { + "pkg": { + "name": "assert-plus", + "version": "1.0.0" + } + } + } + }, + { + "id": "~npm~async-hook-domain@2.0.4", + "node_modules": { + "async-hook-domain": { + "pkg": { + "name": "async-hook-domain", + "version": "2.0.4" + } + } + } + }, + { + "id": "~npm~asynckit@0.4.0", + "node_modules": { + "asynckit": { + "pkg": { + "name": "asynckit", + "version": "0.4.0" + } + } + } + }, + { + "id": "~npm~aws-sign2@0.7.0", + "node_modules": { + "aws-sign2": { + "pkg": { + "name": "aws-sign2", + "version": "0.7.0" + } + } + } + }, + { + "id": "~npm~aws4@1.13.2", + "node_modules": { + "aws4": { + "pkg": { + "name": "aws4", + "version": "1.13.2" + } + } + } + }, + { + "id": "~npm~balanced-match@1.0.2", + "node_modules": { + "balanced-match": { + "pkg": { + "name": "balanced-match", + "version": "1.0.2" + } + } + } + }, + { + "id": "~npm~baseline-browser-mapping@2.11.26", + "node_modules": { + "baseline-browser-mapping": { + "pkg": { + "name": "baseline-browser-mapping", + "version": "2.11.26" + } + } + } + }, + { + "id": "~npm~bcrypt-pbkdf@1.0.2", + "node_modules": { + "bcrypt-pbkdf": { + "pkg": { + "name": "bcrypt-pbkdf", + "version": "1.0.2" + } + }, + "tweetnacl": { + "link": "../../~npm~tweetnacl@0.14.5/node_modules/tweetnacl" + } + } + }, + { + "id": "~npm~binary-extensions@2.3.0", + "node_modules": { + "binary-extensions": { + "pkg": { + "name": "binary-extensions", + "version": "2.3.0" + } + } + } + }, + { + "id": "~npm~bind-obj-methods@3.0.0", + "node_modules": { + "bind-obj-methods": { + "pkg": { + "name": "bind-obj-methods", + "version": "3.0.0" + } + } + } + }, + { + "id": "~npm~brace-expansion@1.1.21", + "node_modules": { + "balanced-match": { + "link": "../../~npm~balanced-match@1.0.2/node_modules/balanced-match" + }, + "brace-expansion": { + "pkg": { + "name": "brace-expansion", + "version": "1.1.21" + } + }, + "concat-map": { + "link": "../../~npm~concat-map@0.0.1/node_modules/concat-map" + } + } + }, + { + "id": "~npm~braces@3.0.3", + "node_modules": { + "braces": { + "pkg": { + "name": "braces", + "version": "3.0.3" + } + }, + "fill-range": { + "link": "../../~npm~fill-range@7.1.1/node_modules/fill-range" + } + } + }, + { + "id": "~npm~browserslist@4.29.1", + "node_modules": { + ".bin": { + "dir": true + }, + "baseline-browser-mapping": { + "link": "../../~npm~baseline-browser-mapping@2.11.26/node_modules/baseline-browser-mapping" + }, + "browserslist": { + "pkg": { + "name": "browserslist", + "version": "4.29.1" + } + }, + "caniuse-lite": { + "link": "../../~npm~caniuse-lite@1.0.30001812/node_modules/caniuse-lite" + }, + "electron-to-chromium": { + "link": "../../~npm~electron-to-chromium@1.5.439/node_modules/electron-to-chromium" + }, + "node-releases": { + "link": "../../~npm~node-releases@2.0.57/node_modules/node-releases" + }, + "update-browserslist-db": { + "link": "../../~npm~update-browserslist-db@1.3.3~peer.27c7ade2f48570ae/node_modules/update-browserslist-db" + } + } + }, + { + "id": "~npm~buffer-from@1.1.2", + "node_modules": { + "buffer-from": { + "pkg": { + "name": "buffer-from", + "version": "1.1.2" + } + } + } + }, + { + "id": "~npm~caching-transform@4.0.0", + "node_modules": { + "caching-transform": { + "pkg": { + "name": "caching-transform", + "version": "4.0.0" + } + }, + "hasha": { + "link": "../../~npm~hasha@5.2.2/node_modules/hasha" + }, + "make-dir": { + "link": "../../~npm~make-dir@3.1.0/node_modules/make-dir" + }, + "package-hash": { + "link": "../../~npm~package-hash@4.0.0/node_modules/package-hash" + }, + "write-file-atomic": { + "link": "../../~npm~write-file-atomic@3.0.3/node_modules/write-file-atomic" + } + } + }, + { + "id": "~npm~camelcase@5.3.1", + "node_modules": { + "camelcase": { + "pkg": { + "name": "camelcase", + "version": "5.3.1" + } + } + } + }, + { + "id": "~npm~caniuse-lite@1.0.30001812", + "node_modules": { + "caniuse-lite": { + "pkg": { + "name": "caniuse-lite", + "version": "1.0.30001812" + } + } + } + }, + { + "id": "~npm~caseless@0.12.0", + "node_modules": { + "caseless": { + "pkg": { + "name": "caseless", + "version": "0.12.0" + } + } + } + }, + { + "id": "~npm~chokidar@3.6.0", + "node_modules": { + "anymatch": { + "link": "../../~npm~anymatch@3.1.3/node_modules/anymatch" + }, + "braces": { + "link": "../../~npm~braces@3.0.3/node_modules/braces" + }, + "chokidar": { + "pkg": { + "name": "chokidar", + "version": "3.6.0" + } + }, + "fsevents": { + "link": "../../~npm~fsevents@2.3.3/node_modules/fsevents" + }, + "glob-parent": { + "link": "../../~npm~glob-parent@5.1.2/node_modules/glob-parent" + }, + "is-binary-path": { + "link": "../../~npm~is-binary-path@2.1.0/node_modules/is-binary-path" + }, + "is-glob": { + "link": "../../~npm~is-glob@4.0.3/node_modules/is-glob" + }, + "normalize-path": { + "link": "../../~npm~normalize-path@3.0.0/node_modules/normalize-path" + }, + "readdirp": { + "link": "../../~npm~readdirp@3.6.0/node_modules/readdirp" + } + } + }, + { + "id": "~npm~clean-stack@2.2.0", + "node_modules": { + "clean-stack": { + "pkg": { + "name": "clean-stack", + "version": "2.2.0" + } + } + } + }, + { + "id": "~npm~cliui@6.0.0", + "node_modules": { + "cliui": { + "pkg": { + "name": "cliui", + "version": "6.0.0" + } + }, + "string-width": { + "link": "../../~npm~string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../../~npm~strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "wrap-ansi": { + "link": "../../~npm~wrap-ansi@6.2.0/node_modules/wrap-ansi" + } + } + }, + { + "id": "~npm~cliui@7.0.4", + "node_modules": { + "cliui": { + "pkg": { + "name": "cliui", + "version": "7.0.4" + } + }, + "string-width": { + "link": "../../~npm~string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../../~npm~strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "wrap-ansi": { + "link": "../../~npm~wrap-ansi@7.0.0/node_modules/wrap-ansi" + } + } + }, + { + "id": "~npm~color-convert@2.0.1", + "node_modules": { + "color-convert": { + "pkg": { + "name": "color-convert", + "version": "2.0.1" + } + }, + "color-name": { + "link": "../../~npm~color-name@1.1.4/node_modules/color-name" + } + } + }, + { + "id": "~npm~color-name@1.1.4", + "node_modules": { + "color-name": { + "pkg": { + "name": "color-name", + "version": "1.1.4" + } + } + } + }, + { + "id": "~npm~color-support@1.1.3", + "node_modules": { + "color-support": { + "pkg": { + "name": "color-support", + "version": "1.1.3" + } + } + } + }, + { + "id": "~npm~combined-stream@1.0.8", + "node_modules": { + "combined-stream": { + "pkg": { + "name": "combined-stream", + "version": "1.0.8" + } + }, + "delayed-stream": { + "link": "../../~npm~delayed-stream@1.0.0/node_modules/delayed-stream" + } + } + }, + { + "id": "~npm~commondir@1.0.1", + "node_modules": { + "commondir": { + "pkg": { + "name": "commondir", + "version": "1.0.1" + } + } + } + }, + { + "id": "~npm~concat-map@0.0.1", + "node_modules": { + "concat-map": { + "pkg": { + "name": "concat-map", + "version": "0.0.1" + } + } + } + }, + { + "id": "~npm~convert-source-map@1.9.0", + "node_modules": { + "convert-source-map": { + "pkg": { + "name": "convert-source-map", + "version": "1.9.0" + } + } + } + }, + { + "id": "~npm~convert-source-map@2.0.0", + "node_modules": { + "convert-source-map": { + "pkg": { + "name": "convert-source-map", + "version": "2.0.0" + } + } + } + }, + { + "id": "~npm~core-util-is@1.0.2", + "node_modules": { + "core-util-is": { + "pkg": { + "name": "core-util-is", + "version": "1.0.2" + } + } + } + }, + { + "id": "~npm~coveralls@3.1.1", + "node_modules": { + ".bin": { + "dir": true + }, + "coveralls": { + "pkg": { + "name": "coveralls", + "version": "3.1.1" + } + }, + "js-yaml": { + "link": "../../~npm~js-yaml@3.15.2/node_modules/js-yaml" + }, + "lcov-parse": { + "link": "../../~npm~lcov-parse@1.0.0/node_modules/lcov-parse" + }, + "log-driver": { + "link": "../../~npm~log-driver@1.2.7/node_modules/log-driver" + }, + "minimist": { + "link": "../../~npm~minimist@1.2.8/node_modules/minimist" + }, + "request": { + "link": "../../~npm~request@2.88.2/node_modules/request" + } + } + }, + { + "id": "~npm~cross-spawn@7.0.6", + "node_modules": { + ".bin": { + "dir": true + }, + "cross-spawn": { + "pkg": { + "name": "cross-spawn", + "version": "7.0.6" + } + }, + "path-key": { + "link": "../../~npm~path-key@3.1.1/node_modules/path-key" + }, + "shebang-command": { + "link": "../../~npm~shebang-command@2.0.0/node_modules/shebang-command" + }, + "which": { + "link": "../../~npm~which@2.0.2/node_modules/which" + } + } + }, + { + "id": "~npm~dashdash@1.14.1", + "node_modules": { + "assert-plus": { + "link": "../../~npm~assert-plus@1.0.0/node_modules/assert-plus" + }, + "dashdash": { + "pkg": { + "name": "dashdash", + "version": "1.14.1" + } + } + } + }, + { + "id": "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "node_modules": { + "debug": { + "pkg": { + "name": "debug", + "version": "4.3.4" + } + }, + "ms": { + "link": "../../~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms/node_modules/ms" + } + } + }, + { + "id": "~npm~decamelize@1.2.0", + "node_modules": { + "decamelize": { + "pkg": { + "name": "decamelize", + "version": "1.2.0" + } + } + } + }, + { + "id": "~npm~default-require-extensions@3.0.1", + "node_modules": { + "default-require-extensions": { + "pkg": { + "name": "default-require-extensions", + "version": "3.0.1" + } + }, + "strip-bom": { + "link": "../../~npm~strip-bom@4.0.0/node_modules/strip-bom" + } + } + }, + { + "id": "~npm~delayed-stream@1.0.0", + "node_modules": { + "delayed-stream": { + "pkg": { + "name": "delayed-stream", + "version": "1.0.0" + } + } + } + }, + { + "id": "~npm~diff@4.0.4", + "node_modules": { + "diff": { + "pkg": { + "name": "diff", + "version": "4.0.4" + } + } + } + }, + { + "id": "~npm~ecc-jsbn@0.1.2", + "node_modules": { + "ecc-jsbn": { + "pkg": { + "name": "ecc-jsbn", + "version": "0.1.2" + } + }, + "jsbn": { + "link": "../../~npm~jsbn@0.1.1/node_modules/jsbn" + }, + "safer-buffer": { + "link": "../../~npm~safer-buffer@2.1.2/node_modules/safer-buffer" + } + } + }, + { + "id": "~npm~electron-to-chromium@1.5.439", + "node_modules": { + "electron-to-chromium": { + "pkg": { + "name": "electron-to-chromium", + "version": "1.5.439" + } + } + } + }, + { + "id": "~npm~emoji-regex@8.0.0", + "node_modules": { + "emoji-regex": { + "pkg": { + "name": "emoji-regex", + "version": "8.0.0" + } + } + } + }, + { + "id": "~npm~es6-error@4.1.1", + "node_modules": { + "es6-error": { + "pkg": { + "name": "es6-error", + "version": "4.1.1" + } + } + } + }, + { + "id": "~npm~escalade@3.2.0", + "node_modules": { + "escalade": { + "pkg": { + "name": "escalade", + "version": "3.2.0" + } + } + } + }, + { + "id": "~npm~escape-string-regexp@2.0.0", + "node_modules": { + "escape-string-regexp": { + "pkg": { + "name": "escape-string-regexp", + "version": "2.0.0" + } + } + } + }, + { + "id": "~npm~esprima@4.0.1", + "node_modules": { + "esprima": { + "pkg": { + "name": "esprima", + "version": "4.0.1" + } + } + } + }, + { + "id": "~npm~events-to-array@1.1.2", + "node_modules": { + "events-to-array": { + "pkg": { + "name": "events-to-array", + "version": "1.1.2" + } + } + } + }, + { + "id": "~npm~extend@3.0.2", + "node_modules": { + "extend": { + "pkg": { + "name": "extend", + "version": "3.0.2" + } + } + } + }, + { + "id": "~npm~extsprintf@1.3.0", + "node_modules": { + "extsprintf": { + "pkg": { + "name": "extsprintf", + "version": "1.3.0" + } + } + } + }, + { + "id": "~npm~fast-deep-equal@3.1.3", + "node_modules": { + "fast-deep-equal": { + "pkg": { + "name": "fast-deep-equal", + "version": "3.1.3" + } + } + } + }, + { + "id": "~npm~fast-json-stable-stringify@2.1.0", + "node_modules": { + "fast-json-stable-stringify": { + "pkg": { + "name": "fast-json-stable-stringify", + "version": "2.1.0" + } + } + } + }, + { + "id": "~npm~fill-range@7.1.1", + "node_modules": { + "fill-range": { + "pkg": { + "name": "fill-range", + "version": "7.1.1" + } + }, + "to-regex-range": { + "link": "../../~npm~to-regex-range@5.0.1/node_modules/to-regex-range" + } + } + }, + { + "id": "~npm~find-cache-dir@3.3.2", + "node_modules": { + "commondir": { + "link": "../../~npm~commondir@1.0.1/node_modules/commondir" + }, + "find-cache-dir": { + "pkg": { + "name": "find-cache-dir", + "version": "3.3.2" + } + }, + "make-dir": { + "link": "../../~npm~make-dir@3.1.0/node_modules/make-dir" + }, + "pkg-dir": { + "link": "../../~npm~pkg-dir@4.2.0/node_modules/pkg-dir" + } + } + }, + { + "id": "~npm~find-up@4.1.0", + "node_modules": { + "find-up": { + "pkg": { + "name": "find-up", + "version": "4.1.0" + } + }, + "locate-path": { + "link": "../../~npm~locate-path@5.0.0/node_modules/locate-path" + }, + "path-exists": { + "link": "../../~npm~path-exists@4.0.0/node_modules/path-exists" + } + } + }, + { + "id": "~npm~findit@2.0.0", + "node_modules": { + "findit": { + "pkg": { + "name": "findit", + "version": "2.0.0" + } + } + } + }, + { + "id": "~npm~foreground-child@2.0.0", + "node_modules": { + "cross-spawn": { + "link": "../../~npm~cross-spawn@7.0.6/node_modules/cross-spawn" + }, + "foreground-child": { + "pkg": { + "name": "foreground-child", + "version": "2.0.0" + } + }, + "signal-exit": { + "link": "../../~npm~signal-exit@3.0.7/node_modules/signal-exit" + } + } + }, + { + "id": "~npm~forever-agent@0.6.1", + "node_modules": { + "forever-agent": { + "pkg": { + "name": "forever-agent", + "version": "0.6.1" + } + } + } + }, + { + "id": "~npm~form-data@2.3.3", + "node_modules": { + "asynckit": { + "link": "../../~npm~asynckit@0.4.0/node_modules/asynckit" + }, + "combined-stream": { + "link": "../../~npm~combined-stream@1.0.8/node_modules/combined-stream" + }, + "form-data": { + "pkg": { + "name": "form-data", + "version": "2.3.3" + } + }, + "mime-types": { + "link": "../../~npm~mime-types@2.1.35/node_modules/mime-types" + } + } + }, + { + "id": "~npm~fromentries@1.3.2", + "node_modules": { + "fromentries": { + "pkg": { + "name": "fromentries", + "version": "1.3.2" + } + } + } + }, + { + "id": "~npm~fs-exists-cached@1.0.0", + "node_modules": { + "fs-exists-cached": { + "pkg": { + "name": "fs-exists-cached", + "version": "1.0.0" + } + } + } + }, + { + "id": "~npm~fs.realpath@1.0.0", + "node_modules": { + "fs.realpath": { + "pkg": { + "name": "fs.realpath", + "version": "1.0.0" + } + } + } + }, + { + "id": "~npm~fsevents@2.3.3", + "node_modules": { + "fsevents": { + "pkg": { + "name": "fsevents", + "version": "2.3.3" + } + } + } + }, + { + "id": "~npm~function-loop@2.0.1", + "node_modules": { + "function-loop": { + "pkg": { + "name": "function-loop", + "version": "2.0.1" + } + } + } + }, + { + "id": "~npm~gensync@1.0.0-beta.2", + "node_modules": { + "gensync": { + "pkg": { + "name": "gensync", + "version": "1.0.0-beta.2" + } + } + } + }, + { + "id": "~npm~get-caller-file@2.0.5", + "node_modules": { + "get-caller-file": { + "pkg": { + "name": "get-caller-file", + "version": "2.0.5" + } + } + } + }, + { + "id": "~npm~get-package-type@0.1.0", + "node_modules": { + "get-package-type": { + "pkg": { + "name": "get-package-type", + "version": "0.1.0" + } + } + } + }, + { + "id": "~npm~getpass@0.1.7", + "node_modules": { + "assert-plus": { + "link": "../../~npm~assert-plus@1.0.0/node_modules/assert-plus" + }, + "getpass": { + "pkg": { + "name": "getpass", + "version": "0.1.7" + } + } + } + }, + { + "id": "~npm~glob-parent@5.1.2", + "node_modules": { + "glob-parent": { + "pkg": { + "name": "glob-parent", + "version": "5.1.2" + } + }, + "is-glob": { + "link": "../../~npm~is-glob@4.0.3/node_modules/is-glob" + } + } + }, + { + "id": "~npm~glob@7.2.3", + "node_modules": { + "fs.realpath": { + "link": "../../~npm~fs.realpath@1.0.0/node_modules/fs.realpath" + }, + "glob": { + "pkg": { + "name": "glob", + "version": "7.2.3" + } + }, + "inflight": { + "link": "../../~npm~inflight@1.0.6/node_modules/inflight" + }, + "inherits": { + "link": "../../~npm~inherits@2.0.4/node_modules/inherits" + }, + "minimatch": { + "link": "../../~npm~minimatch@3.1.5/node_modules/minimatch" + }, + "once": { + "link": "../../~npm~once@1.4.0/node_modules/once" + }, + "path-is-absolute": { + "link": "../../~npm~path-is-absolute@1.0.1/node_modules/path-is-absolute" + } + } + }, + { + "id": "~npm~graceful-fs@4.2.11", + "node_modules": { + "graceful-fs": { + "pkg": { + "name": "graceful-fs", + "version": "4.2.11" + } + } + } + }, + { + "id": "~npm~har-schema@2.0.0", + "node_modules": { + "har-schema": { + "pkg": { + "name": "har-schema", + "version": "2.0.0" + } + } + } + }, + { + "id": "~npm~har-validator@5.1.5", + "node_modules": { + "ajv": { + "link": "../../~npm~ajv@6.15.0/node_modules/ajv" + }, + "har-schema": { + "link": "../../~npm~har-schema@2.0.0/node_modules/har-schema" + }, + "har-validator": { + "pkg": { + "name": "har-validator", + "version": "5.1.5" + } + } + } + }, + { + "id": "~npm~has-flag@4.0.0", + "node_modules": { + "has-flag": { + "pkg": { + "name": "has-flag", + "version": "4.0.0" + } + } + } + }, + { + "id": "~npm~hasha@5.2.2", + "node_modules": { + "hasha": { + "pkg": { + "name": "hasha", + "version": "5.2.2" + } + }, + "is-stream": { + "link": "../../~npm~is-stream@2.0.1/node_modules/is-stream" + }, + "type-fest": { + "link": "../../~npm~type-fest@0.8.1/node_modules/type-fest" + } + } + }, + { + "id": "~npm~html-escaper@2.0.2", + "node_modules": { + "html-escaper": { + "pkg": { + "name": "html-escaper", + "version": "2.0.2" + } + } + } + }, + { + "id": "~npm~http-signature@1.2.0", + "node_modules": { + ".bin": { + "dir": true + }, + "assert-plus": { + "link": "../../~npm~assert-plus@1.0.0/node_modules/assert-plus" + }, + "http-signature": { + "pkg": { + "name": "http-signature", + "version": "1.2.0" + } + }, + "jsprim": { + "link": "../../~npm~jsprim@1.4.2/node_modules/jsprim" + }, + "sshpk": { + "link": "../../~npm~sshpk@1.18.0/node_modules/sshpk" + } + } + }, + { + "id": "~npm~imurmurhash@0.1.4", + "node_modules": { + "imurmurhash": { + "pkg": { + "name": "imurmurhash", + "version": "0.1.4" + } + } + } + }, + { + "id": "~npm~indent-string@4.0.0", + "node_modules": { + "indent-string": { + "pkg": { + "name": "indent-string", + "version": "4.0.0" + } + } + } + }, + { + "id": "~npm~inflight@1.0.6", + "node_modules": { + "inflight": { + "pkg": { + "name": "inflight", + "version": "1.0.6" + } + }, + "once": { + "link": "../../~npm~once@1.4.0/node_modules/once" + }, + "wrappy": { + "link": "../../~npm~wrappy@1.0.2/node_modules/wrappy" + } + } + }, + { + "id": "~npm~inherits@2.0.4", + "node_modules": { + "inherits": { + "pkg": { + "name": "inherits", + "version": "2.0.4" + } + } + } + }, + { + "id": "~npm~is-binary-path@2.1.0", + "node_modules": { + "binary-extensions": { + "link": "../../~npm~binary-extensions@2.3.0/node_modules/binary-extensions" + }, + "is-binary-path": { + "pkg": { + "name": "is-binary-path", + "version": "2.1.0" + } + } + } + }, + { + "id": "~npm~is-extglob@2.1.1", + "node_modules": { + "is-extglob": { + "pkg": { + "name": "is-extglob", + "version": "2.1.1" + } + } + } + }, + { + "id": "~npm~is-fullwidth-code-point@3.0.0", + "node_modules": { + "is-fullwidth-code-point": { + "pkg": { + "name": "is-fullwidth-code-point", + "version": "3.0.0" + } + } + } + }, + { + "id": "~npm~is-glob@4.0.3", + "node_modules": { + "is-extglob": { + "link": "../../~npm~is-extglob@2.1.1/node_modules/is-extglob" + }, + "is-glob": { + "pkg": { + "name": "is-glob", + "version": "4.0.3" + } + } + } + }, + { + "id": "~npm~is-number@7.0.0", + "node_modules": { + "is-number": { + "pkg": { + "name": "is-number", + "version": "7.0.0" + } + } + } + }, + { + "id": "~npm~is-stream@2.0.1", + "node_modules": { + "is-stream": { + "pkg": { + "name": "is-stream", + "version": "2.0.1" + } + } + } + }, + { + "id": "~npm~is-typedarray@1.0.0", + "node_modules": { + "is-typedarray": { + "pkg": { + "name": "is-typedarray", + "version": "1.0.0" + } + } + } + }, + { + "id": "~npm~is-windows@1.0.2", + "node_modules": { + "is-windows": { + "pkg": { + "name": "is-windows", + "version": "1.0.2" + } + } + } + }, + { + "id": "~npm~isexe@2.0.0", + "node_modules": { + "isexe": { + "pkg": { + "name": "isexe", + "version": "2.0.0" + } + } + } + }, + { + "id": "~npm~isstream@0.1.2", + "node_modules": { + "isstream": { + "pkg": { + "name": "isstream", + "version": "0.1.2" + } + } + } + }, + { + "id": "~npm~istanbul-lib-coverage@3.2.2", + "node_modules": { + "istanbul-lib-coverage": { + "pkg": { + "name": "istanbul-lib-coverage", + "version": "3.2.2" + } + } + } + }, + { + "id": "~npm~istanbul-lib-hook@3.0.0", + "node_modules": { + "append-transform": { + "link": "../../~npm~append-transform@2.0.0/node_modules/append-transform" + }, + "istanbul-lib-hook": { + "pkg": { + "name": "istanbul-lib-hook", + "version": "3.0.0" + } + } + } + }, + { + "id": "~npm~istanbul-lib-instrument@4.0.3", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/core": { + "link": "../../../~npm~@babel+core@7.29.7/node_modules/@babel/core" + }, + "@istanbuljs/schema": { + "link": "../../../~npm~@istanbuljs+schema@0.1.6/node_modules/@istanbuljs/schema" + }, + "istanbul-lib-coverage": { + "link": "../../~npm~istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-instrument": { + "pkg": { + "name": "istanbul-lib-instrument", + "version": "4.0.3" + } + }, + "semver": { + "link": "../../~npm~semver@6.3.1/node_modules/semver" + } + } + }, + { + "id": "~npm~istanbul-lib-processinfo@2.0.3", + "node_modules": { + ".bin": { + "dir": true + }, + "archy": { + "link": "../../~npm~archy@1.0.0/node_modules/archy" + }, + "cross-spawn": { + "link": "../../~npm~cross-spawn@7.0.6/node_modules/cross-spawn" + }, + "istanbul-lib-coverage": { + "link": "../../~npm~istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-processinfo": { + "pkg": { + "name": "istanbul-lib-processinfo", + "version": "2.0.3" + } + }, + "p-map": { + "link": "../../~npm~p-map@3.0.0/node_modules/p-map" + }, + "rimraf": { + "link": "../../~npm~rimraf@3.0.2/node_modules/rimraf" + }, + "uuid": { + "link": "../../~npm~uuid@8.3.2/node_modules/uuid" + } + } + }, + { + "id": "~npm~istanbul-lib-report@3.0.1", + "node_modules": { + "istanbul-lib-coverage": { + "link": "../../~npm~istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-report": { + "pkg": { + "name": "istanbul-lib-report", + "version": "3.0.1" + } + }, + "make-dir": { + "link": "../../~npm~make-dir@4.0.0/node_modules/make-dir" + }, + "supports-color": { + "link": "../../~npm~supports-color@7.2.0/node_modules/supports-color" + } + } + }, + { + "id": "~npm~istanbul-lib-source-maps@4.0.1", + "node_modules": { + "debug": { + "link": "../../~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms/node_modules/debug" + }, + "istanbul-lib-coverage": { + "link": "../../~npm~istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-source-maps": { + "pkg": { + "name": "istanbul-lib-source-maps", + "version": "4.0.1" + } + }, + "source-map": { + "link": "../../~npm~source-map@0.6.1/node_modules/source-map" + } + } + }, + { + "id": "~npm~istanbul-reports@3.2.0", + "node_modules": { + "html-escaper": { + "link": "../../~npm~html-escaper@2.0.2/node_modules/html-escaper" + }, + "istanbul-lib-report": { + "link": "../../~npm~istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report" + }, + "istanbul-reports": { + "pkg": { + "name": "istanbul-reports", + "version": "3.2.0" + } + } + } + }, + { + "id": "~npm~jackspeak@1.4.2", + "node_modules": { + "cliui": { + "link": "../../~npm~cliui@7.0.4/node_modules/cliui" + }, + "jackspeak": { + "pkg": { + "name": "jackspeak", + "version": "1.4.2" + } + } + } + }, + { + "id": "~npm~js-tokens@4.0.0", + "node_modules": { + "js-tokens": { + "pkg": { + "name": "js-tokens", + "version": "4.0.0" + } + } + } + }, + { + "id": "~npm~js-yaml@3.15.2", + "node_modules": { + ".bin": { + "dir": true + }, + "argparse": { + "link": "../../~npm~argparse@1.0.10/node_modules/argparse" + }, + "esprima": { + "link": "../../~npm~esprima@4.0.1/node_modules/esprima" + }, + "js-yaml": { + "pkg": { + "name": "js-yaml", + "version": "3.15.2" + } + } + } + }, + { + "id": "~npm~jsbn@0.1.1", + "node_modules": { + "jsbn": { + "pkg": { + "name": "jsbn", + "version": "0.1.1" + } + } + } + }, + { + "id": "~npm~jsesc@3.1.0", + "node_modules": { + "jsesc": { + "pkg": { + "name": "jsesc", + "version": "3.1.0" + } + } + } + }, + { + "id": "~npm~json-schema-traverse@0.4.1", + "node_modules": { + "json-schema-traverse": { + "pkg": { + "name": "json-schema-traverse", + "version": "0.4.1" + } + } + } + }, + { + "id": "~npm~json-schema@0.4.0", + "node_modules": { + "json-schema": { + "pkg": { + "name": "json-schema", + "version": "0.4.0" + } + } + } + }, + { + "id": "~npm~json-stringify-safe@5.0.1", + "node_modules": { + "json-stringify-safe": { + "pkg": { + "name": "json-stringify-safe", + "version": "5.0.1" + } + } + } + }, + { + "id": "~npm~json5@2.2.3", + "node_modules": { + "json5": { + "pkg": { + "name": "json5", + "version": "2.2.3" + } + } + } + }, + { + "id": "~npm~jsprim@1.4.2", + "node_modules": { + "assert-plus": { + "link": "../../~npm~assert-plus@1.0.0/node_modules/assert-plus" + }, + "extsprintf": { + "link": "../../~npm~extsprintf@1.3.0/node_modules/extsprintf" + }, + "json-schema": { + "link": "../../~npm~json-schema@0.4.0/node_modules/json-schema" + }, + "jsprim": { + "pkg": { + "name": "jsprim", + "version": "1.4.2" + } + }, + "verror": { + "link": "../../~npm~verror@1.10.0/node_modules/verror" + } + } + }, + { + "id": "~npm~lcov-parse@1.0.0", + "node_modules": { + "lcov-parse": { + "pkg": { + "name": "lcov-parse", + "version": "1.0.0" + } + } + } + }, + { + "id": "~npm~left-pad@1.1.3", + "node_modules": { + "left-pad": { + "pkg": { + "name": "left-pad", + "version": "1.1.3" + } + } + } + }, + { + "id": "~npm~left-pad@1.3.0", + "node_modules": { + "left-pad": { + "pkg": { + "name": "left-pad", + "version": "1.3.0" + } + } + } + }, + { + "id": "~npm~libtap@1.4.1", + "node_modules": { + ".bin": { + "dir": true + }, + "async-hook-domain": { + "link": "../../~npm~async-hook-domain@2.0.4/node_modules/async-hook-domain" + }, + "bind-obj-methods": { + "link": "../../~npm~bind-obj-methods@3.0.0/node_modules/bind-obj-methods" + }, + "diff": { + "link": "../../~npm~diff@4.0.4/node_modules/diff" + }, + "function-loop": { + "link": "../../~npm~function-loop@2.0.1/node_modules/function-loop" + }, + "libtap": { + "pkg": { + "name": "libtap", + "version": "1.4.1" + } + }, + "minipass": { + "link": "../../~npm~minipass@3.3.6/node_modules/minipass" + }, + "own-or": { + "link": "../../~npm~own-or@1.0.0/node_modules/own-or" + }, + "own-or-env": { + "link": "../../~npm~own-or-env@1.0.2/node_modules/own-or-env" + }, + "signal-exit": { + "link": "../../~npm~signal-exit@3.0.7/node_modules/signal-exit" + }, + "stack-utils": { + "link": "../../~npm~stack-utils@2.0.6/node_modules/stack-utils" + }, + "tap-parser": { + "link": "../../~npm~tap-parser@11.0.2/node_modules/tap-parser" + }, + "tap-yaml": { + "link": "../../~npm~tap-yaml@1.0.2/node_modules/tap-yaml" + }, + "tcompare": { + "link": "../../~npm~tcompare@5.0.7/node_modules/tcompare" + }, + "trivial-deferred": { + "link": "../../~npm~trivial-deferred@1.1.2/node_modules/trivial-deferred" + } + } + }, + { + "id": "~npm~locate-path@5.0.0", + "node_modules": { + "locate-path": { + "pkg": { + "name": "locate-path", + "version": "5.0.0" + } + }, + "p-locate": { + "link": "../../~npm~p-locate@4.1.0/node_modules/p-locate" + } + } + }, + { + "id": "~npm~lodash.flattendeep@4.4.0", + "node_modules": { + "lodash.flattendeep": { + "pkg": { + "name": "lodash.flattendeep", + "version": "4.4.0" + } + } + } + }, + { + "id": "~npm~log-driver@1.2.7", + "node_modules": { + "log-driver": { + "pkg": { + "name": "log-driver", + "version": "1.2.7" + } + } + } + }, + { + "id": "~npm~loose-envify@1.4.0", + "node_modules": { + "js-tokens": { + "link": "../../~npm~js-tokens@4.0.0/node_modules/js-tokens" + }, + "loose-envify": { + "pkg": { + "name": "loose-envify", + "version": "1.4.0" + } + } + } + }, + { + "id": "~npm~lru-cache@5.1.1", + "node_modules": { + "lru-cache": { + "pkg": { + "name": "lru-cache", + "version": "5.1.1" + } + }, + "yallist": { + "link": "../../~npm~yallist@3.1.1/node_modules/yallist" + } + } + }, + { + "id": "~npm~lru-cache@6.0.0", + "node_modules": { + "lru-cache": { + "pkg": { + "name": "lru-cache", + "version": "6.0.0" + } + }, + "yallist": { + "link": "../../~npm~yallist@4.0.0/node_modules/yallist" + } + } + }, + { + "id": "~npm~make-dir@3.1.0", + "node_modules": { + ".bin": { + "dir": true + }, + "make-dir": { + "pkg": { + "name": "make-dir", + "version": "3.1.0" + } + }, + "semver": { + "link": "../../~npm~semver@6.3.1/node_modules/semver" + } + } + }, + { + "id": "~npm~make-dir@4.0.0", + "node_modules": { + ".bin": { + "dir": true + }, + "make-dir": { + "pkg": { + "name": "make-dir", + "version": "4.0.0" + } + }, + "semver": { + "link": "../../~npm~semver@7.6.0/node_modules/semver" + } + } + }, + { + "id": "~npm~mime-db@1.52.0", + "node_modules": { + "mime-db": { + "pkg": { + "name": "mime-db", + "version": "1.52.0" + } + } + } + }, + { + "id": "~npm~mime-types@2.1.35", + "node_modules": { + "mime-db": { + "link": "../../~npm~mime-db@1.52.0/node_modules/mime-db" + }, + "mime-types": { + "pkg": { + "name": "mime-types", + "version": "2.1.35" + } + } + } + }, + { + "id": "~npm~minimatch@3.1.5", + "node_modules": { + "brace-expansion": { + "link": "../../~npm~brace-expansion@1.1.21/node_modules/brace-expansion" + }, + "minimatch": { + "pkg": { + "name": "minimatch", + "version": "3.1.5" + } + } + } + }, + { + "id": "~npm~minimist@1.2.8", + "node_modules": { + "minimist": { + "pkg": { + "name": "minimist", + "version": "1.2.8" + } + } + } + }, + { + "id": "~npm~minipass@3.3.6", + "node_modules": { + "minipass": { + "pkg": { + "name": "minipass", + "version": "3.3.6" + } + }, + "yallist": { + "link": "../../~npm~yallist@4.0.0/node_modules/yallist" + } + } + }, + { + "id": "~npm~mkdirp@1.0.4", + "node_modules": { + "mkdirp": { + "pkg": { + "name": "mkdirp", + "version": "1.0.4" + } + } + } + }, + { + "id": "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "node_modules": { + "ms": { + "pkg": { + "name": "ms", + "version": "2.1.3" + } + } + } + }, + { + "id": "~npm~node-preload@0.2.1", + "node_modules": { + "node-preload": { + "pkg": { + "name": "node-preload", + "version": "0.2.1" + } + }, + "process-on-spawn": { + "link": "../../~npm~process-on-spawn@1.1.0/node_modules/process-on-spawn" + } + } + }, + { + "id": "~npm~node-releases@2.0.57", + "node_modules": { + "node-releases": { + "pkg": { + "name": "node-releases", + "version": "2.0.57" + } + } + } + }, + { + "id": "~npm~normalize-path@3.0.0", + "node_modules": { + "normalize-path": { + "pkg": { + "name": "normalize-path", + "version": "3.0.0" + } + } + } + }, + { + "id": "~npm~nyc@15.1.0", + "node_modules": { + ".bin": { + "dir": true + }, + "@istanbuljs/load-nyc-config": { + "link": "../../../~npm~@istanbuljs+load-nyc-config@1.1.0/node_modules/@istanbuljs/load-nyc-config" + }, + "@istanbuljs/schema": { + "link": "../../../~npm~@istanbuljs+schema@0.1.6/node_modules/@istanbuljs/schema" + }, + "caching-transform": { + "link": "../../~npm~caching-transform@4.0.0/node_modules/caching-transform" + }, + "convert-source-map": { + "link": "../../~npm~convert-source-map@1.9.0/node_modules/convert-source-map" + }, + "decamelize": { + "link": "../../~npm~decamelize@1.2.0/node_modules/decamelize" + }, + "find-cache-dir": { + "link": "../../~npm~find-cache-dir@3.3.2/node_modules/find-cache-dir" + }, + "find-up": { + "link": "../../~npm~find-up@4.1.0/node_modules/find-up" + }, + "foreground-child": { + "link": "../../~npm~foreground-child@2.0.0/node_modules/foreground-child" + }, + "get-package-type": { + "link": "../../~npm~get-package-type@0.1.0/node_modules/get-package-type" + }, + "glob": { + "link": "../../~npm~glob@7.2.3/node_modules/glob" + }, + "istanbul-lib-coverage": { + "link": "../../~npm~istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-hook": { + "link": "../../~npm~istanbul-lib-hook@3.0.0/node_modules/istanbul-lib-hook" + }, + "istanbul-lib-instrument": { + "link": "../../~npm~istanbul-lib-instrument@4.0.3/node_modules/istanbul-lib-instrument" + }, + "istanbul-lib-processinfo": { + "link": "../../~npm~istanbul-lib-processinfo@2.0.3/node_modules/istanbul-lib-processinfo" + }, + "istanbul-lib-report": { + "link": "../../~npm~istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report" + }, + "istanbul-lib-source-maps": { + "link": "../../~npm~istanbul-lib-source-maps@4.0.1/node_modules/istanbul-lib-source-maps" + }, + "istanbul-reports": { + "link": "../../~npm~istanbul-reports@3.2.0/node_modules/istanbul-reports" + }, + "make-dir": { + "link": "../../~npm~make-dir@3.1.0/node_modules/make-dir" + }, + "node-preload": { + "link": "../../~npm~node-preload@0.2.1/node_modules/node-preload" + }, + "nyc": { + "pkg": { + "name": "nyc", + "version": "15.1.0" + } + }, + "p-map": { + "link": "../../~npm~p-map@3.0.0/node_modules/p-map" + }, + "process-on-spawn": { + "link": "../../~npm~process-on-spawn@1.1.0/node_modules/process-on-spawn" + }, + "resolve-from": { + "link": "../../~npm~resolve-from@5.0.0/node_modules/resolve-from" + }, + "rimraf": { + "link": "../../~npm~rimraf@3.0.2/node_modules/rimraf" + }, + "signal-exit": { + "link": "../../~npm~signal-exit@3.0.7/node_modules/signal-exit" + }, + "spawn-wrap": { + "link": "../../~npm~spawn-wrap@2.0.0/node_modules/spawn-wrap" + }, + "test-exclude": { + "link": "../../~npm~test-exclude@6.0.0/node_modules/test-exclude" + }, + "yargs": { + "link": "../../~npm~yargs@15.4.1/node_modules/yargs" + } + } + }, + { + "id": "~npm~oauth-sign@0.9.0", + "node_modules": { + "oauth-sign": { + "pkg": { + "name": "oauth-sign", + "version": "0.9.0" + } + } + } + }, + { + "id": "~npm~once@1.4.0", + "node_modules": { + "once": { + "pkg": { + "name": "once", + "version": "1.4.0" + } + }, + "wrappy": { + "link": "../../~npm~wrappy@1.0.2/node_modules/wrappy" + } + } + }, + { + "id": "~npm~opener@1.5.2", + "node_modules": { + "opener": { + "pkg": { + "name": "opener", + "version": "1.5.2" + } + } + } + }, + { + "id": "~npm~own-or-env@1.0.2", + "node_modules": { + "own-or": { + "link": "../../~npm~own-or@1.0.0/node_modules/own-or" + }, + "own-or-env": { + "pkg": { + "name": "own-or-env", + "version": "1.0.2" + } + } + } + }, + { + "id": "~npm~own-or@1.0.0", + "node_modules": { + "own-or": { + "pkg": { + "name": "own-or", + "version": "1.0.0" + } + } + } + }, + { + "id": "~npm~p-limit@2.3.0", + "node_modules": { + "p-limit": { + "pkg": { + "name": "p-limit", + "version": "2.3.0" + } + }, + "p-try": { + "link": "../../~npm~p-try@2.2.0/node_modules/p-try" + } + } + }, + { + "id": "~npm~p-locate@4.1.0", + "node_modules": { + "p-limit": { + "link": "../../~npm~p-limit@2.3.0/node_modules/p-limit" + }, + "p-locate": { + "pkg": { + "name": "p-locate", + "version": "4.1.0" + } + } + } + }, + { + "id": "~npm~p-map@3.0.0", + "node_modules": { + "aggregate-error": { + "link": "../../~npm~aggregate-error@3.1.0/node_modules/aggregate-error" + }, + "p-map": { + "pkg": { + "name": "p-map", + "version": "3.0.0" + } + } + } + }, + { + "id": "~npm~p-try@2.2.0", + "node_modules": { + "p-try": { + "pkg": { + "name": "p-try", + "version": "2.2.0" + } + } + } + }, + { + "id": "~npm~package-hash@4.0.0", + "node_modules": { + "graceful-fs": { + "link": "../../~npm~graceful-fs@4.2.11/node_modules/graceful-fs" + }, + "hasha": { + "link": "../../~npm~hasha@5.2.2/node_modules/hasha" + }, + "lodash.flattendeep": { + "link": "../../~npm~lodash.flattendeep@4.4.0/node_modules/lodash.flattendeep" + }, + "package-hash": { + "pkg": { + "name": "package-hash", + "version": "4.0.0" + } + }, + "release-zalgo": { + "link": "../../~npm~release-zalgo@1.0.0/node_modules/release-zalgo" + } + } + }, + { + "id": "~npm~path-exists@4.0.0", + "node_modules": { + "path-exists": { + "pkg": { + "name": "path-exists", + "version": "4.0.0" + } + } + } + }, + { + "id": "~npm~path-is-absolute@1.0.1", + "node_modules": { + "path-is-absolute": { + "pkg": { + "name": "path-is-absolute", + "version": "1.0.1" + } + } + } + }, + { + "id": "~npm~path-key@3.1.1", + "node_modules": { + "path-key": { + "pkg": { + "name": "path-key", + "version": "3.1.1" + } + } + } + }, + { + "id": "~npm~performance-now@2.1.0", + "node_modules": { + "performance-now": { + "pkg": { + "name": "performance-now", + "version": "2.1.0" + } + } + } + }, + { + "id": "~npm~picocolors@1.1.1", + "node_modules": { + "picocolors": { + "pkg": { + "name": "picocolors", + "version": "1.1.1" + } + } + } + }, + { + "id": "~npm~picomatch@2.3.2", + "node_modules": { + "picomatch": { + "pkg": { + "name": "picomatch", + "version": "2.3.2" + } + } + } + }, + { + "id": "~npm~pkg-dir@4.2.0", + "node_modules": { + "find-up": { + "link": "../../~npm~find-up@4.1.0/node_modules/find-up" + }, + "pkg-dir": { + "pkg": { + "name": "pkg-dir", + "version": "4.2.0" + } + } + } + }, + { + "id": "~npm~process-on-spawn@1.1.0", + "node_modules": { + "fromentries": { + "link": "../../~npm~fromentries@1.3.2/node_modules/fromentries" + }, + "process-on-spawn": { + "pkg": { + "name": "process-on-spawn", + "version": "1.1.0" + } + } + } + }, + { + "id": "~npm~psl@1.15.0", + "node_modules": { + "psl": { + "pkg": { + "name": "psl", + "version": "1.15.0" + } + }, + "punycode": { + "link": "../../~npm~punycode@2.3.1/node_modules/punycode" + } + } + }, + { + "id": "~npm~punycode@2.3.1", + "node_modules": { + "punycode": { + "pkg": { + "name": "punycode", + "version": "2.3.1" + } + } + } + }, + { + "id": "~npm~qs@6.5.5", + "node_modules": { + "qs": { + "pkg": { + "name": "qs", + "version": "6.5.5" + } + } + } + }, + { + "id": "~npm~react@18.2.0", + "node_modules": { + ".bin": { + "dir": true + }, + "loose-envify": { + "link": "../../~npm~loose-envify@1.4.0/node_modules/loose-envify" + }, + "react": { + "pkg": { + "name": "react", + "version": "18.2.0" + } + } + } + }, + { + "id": "~npm~readdirp@3.6.0", + "node_modules": { + "picomatch": { + "link": "../../~npm~picomatch@2.3.2/node_modules/picomatch" + }, + "readdirp": { + "pkg": { + "name": "readdirp", + "version": "3.6.0" + } + } + } + }, + { + "id": "~npm~release-zalgo@1.0.0", + "node_modules": { + "es6-error": { + "link": "../../~npm~es6-error@4.1.1/node_modules/es6-error" + }, + "release-zalgo": { + "pkg": { + "name": "release-zalgo", + "version": "1.0.0" + } + } + } + }, + { + "id": "~npm~request@2.88.2", + "node_modules": { + ".bin": { + "dir": true + }, + "aws-sign2": { + "link": "../../~npm~aws-sign2@0.7.0/node_modules/aws-sign2" + }, + "aws4": { + "link": "../../~npm~aws4@1.13.2/node_modules/aws4" + }, + "caseless": { + "link": "../../~npm~caseless@0.12.0/node_modules/caseless" + }, + "combined-stream": { + "link": "../../~npm~combined-stream@1.0.8/node_modules/combined-stream" + }, + "extend": { + "link": "../../~npm~extend@3.0.2/node_modules/extend" + }, + "forever-agent": { + "link": "../../~npm~forever-agent@0.6.1/node_modules/forever-agent" + }, + "form-data": { + "link": "../../~npm~form-data@2.3.3/node_modules/form-data" + }, + "har-validator": { + "link": "../../~npm~har-validator@5.1.5/node_modules/har-validator" + }, + "http-signature": { + "link": "../../~npm~http-signature@1.2.0/node_modules/http-signature" + }, + "is-typedarray": { + "link": "../../~npm~is-typedarray@1.0.0/node_modules/is-typedarray" + }, + "isstream": { + "link": "../../~npm~isstream@0.1.2/node_modules/isstream" + }, + "json-stringify-safe": { + "link": "../../~npm~json-stringify-safe@5.0.1/node_modules/json-stringify-safe" + }, + "mime-types": { + "link": "../../~npm~mime-types@2.1.35/node_modules/mime-types" + }, + "oauth-sign": { + "link": "../../~npm~oauth-sign@0.9.0/node_modules/oauth-sign" + }, + "performance-now": { + "link": "../../~npm~performance-now@2.1.0/node_modules/performance-now" + }, + "qs": { + "link": "../../~npm~qs@6.5.5/node_modules/qs" + }, + "request": { + "pkg": { + "name": "request", + "version": "2.88.2" + } + }, + "safe-buffer": { + "link": "../../~npm~safe-buffer@5.2.1/node_modules/safe-buffer" + }, + "tough-cookie": { + "link": "../../~npm~tough-cookie@2.5.0/node_modules/tough-cookie" + }, + "tunnel-agent": { + "link": "../../~npm~tunnel-agent@0.6.0/node_modules/tunnel-agent" + }, + "uuid": { + "link": "../../~npm~uuid@3.4.0/node_modules/uuid" + } + } + }, + { + "id": "~npm~require-directory@2.1.1", + "node_modules": { + "require-directory": { + "pkg": { + "name": "require-directory", + "version": "2.1.1" + } + } + } + }, + { + "id": "~npm~require-main-filename@2.0.0", + "node_modules": { + "require-main-filename": { + "pkg": { + "name": "require-main-filename", + "version": "2.0.0" + } + } + } + }, + { + "id": "~npm~resolve-from@5.0.0", + "node_modules": { + "resolve-from": { + "pkg": { + "name": "resolve-from", + "version": "5.0.0" + } + } + } + }, + { + "id": "~npm~rimraf@3.0.2", + "node_modules": { + "glob": { + "link": "../../~npm~glob@7.2.3/node_modules/glob" + }, + "rimraf": { + "pkg": { + "name": "rimraf", + "version": "3.0.2" + } + } + } + }, + { + "id": "~npm~safe-buffer@5.2.1", + "node_modules": { + "safe-buffer": { + "pkg": { + "name": "safe-buffer", + "version": "5.2.1" + } + } + } + }, + { + "id": "~npm~safer-buffer@2.1.2", + "node_modules": { + "safer-buffer": { + "pkg": { + "name": "safer-buffer", + "version": "2.1.2" + } + } + } + }, + { + "id": "~npm~semver@6.3.1", + "node_modules": { + "semver": { + "pkg": { + "name": "semver", + "version": "6.3.1" + } + } + } + }, + { + "id": "~npm~semver@7.6.0", + "node_modules": { + "lru-cache": { + "link": "../../~npm~lru-cache@6.0.0/node_modules/lru-cache" + }, + "semver": { + "pkg": { + "name": "semver", + "version": "7.6.0" + } + } + } + }, + { + "id": "~npm~set-blocking@2.0.0", + "node_modules": { + "set-blocking": { + "pkg": { + "name": "set-blocking", + "version": "2.0.0" + } + } + } + }, + { + "id": "~npm~shebang-command@2.0.0", + "node_modules": { + "shebang-command": { + "pkg": { + "name": "shebang-command", + "version": "2.0.0" + } + }, + "shebang-regex": { + "link": "../../~npm~shebang-regex@3.0.0/node_modules/shebang-regex" + } + } + }, + { + "id": "~npm~shebang-regex@3.0.0", + "node_modules": { + "shebang-regex": { + "pkg": { + "name": "shebang-regex", + "version": "3.0.0" + } + } + } + }, + { + "id": "~npm~signal-exit@3.0.7", + "node_modules": { + "signal-exit": { + "pkg": { + "name": "signal-exit", + "version": "3.0.7" + } + } + } + }, + { + "id": "~npm~source-map-support@0.5.21", + "node_modules": { + "buffer-from": { + "link": "../../~npm~buffer-from@1.1.2/node_modules/buffer-from" + }, + "source-map": { + "link": "../../~npm~source-map@0.6.1/node_modules/source-map" + }, + "source-map-support": { + "pkg": { + "name": "source-map-support", + "version": "0.5.21" + } + } + } + }, + { + "id": "~npm~source-map@0.6.1", + "node_modules": { + "source-map": { + "pkg": { + "name": "source-map", + "version": "0.6.1" + } + } + } + }, + { + "id": "~npm~spawn-wrap@2.0.0", + "node_modules": { + ".bin": { + "dir": true + }, + "foreground-child": { + "link": "../../~npm~foreground-child@2.0.0/node_modules/foreground-child" + }, + "is-windows": { + "link": "../../~npm~is-windows@1.0.2/node_modules/is-windows" + }, + "make-dir": { + "link": "../../~npm~make-dir@3.1.0/node_modules/make-dir" + }, + "rimraf": { + "link": "../../~npm~rimraf@3.0.2/node_modules/rimraf" + }, + "signal-exit": { + "link": "../../~npm~signal-exit@3.0.7/node_modules/signal-exit" + }, + "spawn-wrap": { + "pkg": { + "name": "spawn-wrap", + "version": "2.0.0" + } + }, + "which": { + "link": "../../~npm~which@2.0.2/node_modules/which" + } + } + }, + { + "id": "~npm~sprintf-js@1.0.3", + "node_modules": { + "sprintf-js": { + "pkg": { + "name": "sprintf-js", + "version": "1.0.3" + } + } + } + }, + { + "id": "~npm~sshpk@1.18.0", + "node_modules": { + "asn1": { + "link": "../../~npm~asn1@0.2.6/node_modules/asn1" + }, + "assert-plus": { + "link": "../../~npm~assert-plus@1.0.0/node_modules/assert-plus" + }, + "bcrypt-pbkdf": { + "link": "../../~npm~bcrypt-pbkdf@1.0.2/node_modules/bcrypt-pbkdf" + }, + "dashdash": { + "link": "../../~npm~dashdash@1.14.1/node_modules/dashdash" + }, + "ecc-jsbn": { + "link": "../../~npm~ecc-jsbn@0.1.2/node_modules/ecc-jsbn" + }, + "getpass": { + "link": "../../~npm~getpass@0.1.7/node_modules/getpass" + }, + "jsbn": { + "link": "../../~npm~jsbn@0.1.1/node_modules/jsbn" + }, + "safer-buffer": { + "link": "../../~npm~safer-buffer@2.1.2/node_modules/safer-buffer" + }, + "sshpk": { + "pkg": { + "name": "sshpk", + "version": "1.18.0" + } + }, + "tweetnacl": { + "link": "../../~npm~tweetnacl@0.14.5/node_modules/tweetnacl" + } + } + }, + { + "id": "~npm~stack-utils@2.0.6", + "node_modules": { + "escape-string-regexp": { + "link": "../../~npm~escape-string-regexp@2.0.0/node_modules/escape-string-regexp" + }, + "stack-utils": { + "pkg": { + "name": "stack-utils", + "version": "2.0.6" + } + } + } + }, + { + "id": "~npm~string-width@4.2.3", + "node_modules": { + "emoji-regex": { + "link": "../../~npm~emoji-regex@8.0.0/node_modules/emoji-regex" + }, + "is-fullwidth-code-point": { + "link": "../../~npm~is-fullwidth-code-point@3.0.0/node_modules/is-fullwidth-code-point" + }, + "string-width": { + "pkg": { + "name": "string-width", + "version": "4.2.3" + } + }, + "strip-ansi": { + "link": "../../~npm~strip-ansi@6.0.1/node_modules/strip-ansi" + } + } + }, + { + "id": "~npm~strip-ansi@6.0.1", + "node_modules": { + "ansi-regex": { + "link": "../../~npm~ansi-regex@5.0.1/node_modules/ansi-regex" + }, + "strip-ansi": { + "pkg": { + "name": "strip-ansi", + "version": "6.0.1" + } + } + } + }, + { + "id": "~npm~strip-bom@4.0.0", + "node_modules": { + "strip-bom": { + "pkg": { + "name": "strip-bom", + "version": "4.0.0" + } + } + } + }, + { + "id": "~npm~supports-color@7.2.0", + "node_modules": { + "has-flag": { + "link": "../../~npm~has-flag@4.0.0/node_modules/has-flag" + }, + "supports-color": { + "pkg": { + "name": "supports-color", + "version": "7.2.0" + } + } + } + }, + { + "id": "~npm~tap-mocha-reporter@5.0.4", + "node_modules": { + ".bin": { + "dir": true + }, + "color-support": { + "link": "../../~npm~color-support@1.1.3/node_modules/color-support" + }, + "debug": { + "link": "../../~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms/node_modules/debug" + }, + "diff": { + "link": "../../~npm~diff@4.0.4/node_modules/diff" + }, + "escape-string-regexp": { + "link": "../../~npm~escape-string-regexp@2.0.0/node_modules/escape-string-regexp" + }, + "glob": { + "link": "../../~npm~glob@7.2.3/node_modules/glob" + }, + "tap-mocha-reporter": { + "pkg": { + "name": "tap-mocha-reporter", + "version": "5.0.4" + } + }, + "tap-parser": { + "link": "../../~npm~tap-parser@11.0.2/node_modules/tap-parser" + }, + "tap-yaml": { + "link": "../../~npm~tap-yaml@1.0.2/node_modules/tap-yaml" + }, + "unicode-length": { + "link": "../../~npm~unicode-length@2.1.0/node_modules/unicode-length" + } + } + }, + { + "id": "~npm~tap-parser@11.0.2", + "node_modules": { + "events-to-array": { + "link": "../../~npm~events-to-array@1.1.2/node_modules/events-to-array" + }, + "minipass": { + "link": "../../~npm~minipass@3.3.6/node_modules/minipass" + }, + "tap-parser": { + "pkg": { + "name": "tap-parser", + "version": "11.0.2" + } + }, + "tap-yaml": { + "link": "../../~npm~tap-yaml@1.0.2/node_modules/tap-yaml" + } + } + }, + { + "id": "~npm~tap-yaml@1.0.2", + "node_modules": { + "tap-yaml": { + "pkg": { + "name": "tap-yaml", + "version": "1.0.2" + } + }, + "yaml": { + "link": "../../~npm~yaml@1.10.3/node_modules/yaml" + } + } + }, + { + "id": "~npm~tap@15.2.3~peer.6f88d0ccf17dbbdc", + "node_modules": { + ".bin": { + "dir": true + }, + "chokidar": { + "link": "../../~npm~chokidar@3.6.0/node_modules/chokidar" + }, + "coveralls": { + "link": "../../~npm~coveralls@3.1.1/node_modules/coveralls" + }, + "findit": { + "link": "../../~npm~findit@2.0.0/node_modules/findit" + }, + "foreground-child": { + "link": "../../~npm~foreground-child@2.0.0/node_modules/foreground-child" + }, + "fs-exists-cached": { + "link": "../../~npm~fs-exists-cached@1.0.0/node_modules/fs-exists-cached" + }, + "glob": { + "link": "../../~npm~glob@7.2.3/node_modules/glob" + }, + "isexe": { + "link": "../../~npm~isexe@2.0.0/node_modules/isexe" + }, + "istanbul-lib-processinfo": { + "link": "../../~npm~istanbul-lib-processinfo@2.0.3/node_modules/istanbul-lib-processinfo" + }, + "jackspeak": { + "link": "../../~npm~jackspeak@1.4.2/node_modules/jackspeak" + }, + "libtap": { + "link": "../../~npm~libtap@1.4.1/node_modules/libtap" + }, + "minipass": { + "link": "../../~npm~minipass@3.3.6/node_modules/minipass" + }, + "mkdirp": { + "link": "../../~npm~mkdirp@1.0.4/node_modules/mkdirp" + }, + "nyc": { + "link": "../../~npm~nyc@15.1.0/node_modules/nyc" + }, + "opener": { + "link": "../../~npm~opener@1.5.2/node_modules/opener" + }, + "rimraf": { + "link": "../../~npm~rimraf@3.0.2/node_modules/rimraf" + }, + "signal-exit": { + "link": "../../~npm~signal-exit@3.0.7/node_modules/signal-exit" + }, + "source-map-support": { + "link": "../../~npm~source-map-support@0.5.21/node_modules/source-map-support" + }, + "tap": { + "pkg": { + "name": "tap", + "version": "15.2.3" + } + }, + "tap-mocha-reporter": { + "link": "../../~npm~tap-mocha-reporter@5.0.4/node_modules/tap-mocha-reporter" + }, + "tap-parser": { + "link": "../../~npm~tap-parser@11.0.2/node_modules/tap-parser" + }, + "tap-yaml": { + "link": "../../~npm~tap-yaml@1.0.2/node_modules/tap-yaml" + }, + "tcompare": { + "link": "../../~npm~tcompare@5.0.7/node_modules/tcompare" + }, + "which": { + "link": "../../~npm~which@2.0.2/node_modules/which" + } + } + }, + { + "id": "~npm~tcompare@5.0.7", + "node_modules": { + "diff": { + "link": "../../~npm~diff@4.0.4/node_modules/diff" + }, + "tcompare": { + "pkg": { + "name": "tcompare", + "version": "5.0.7" + } + } + } + }, + { + "id": "~npm~test-exclude@6.0.0", + "node_modules": { + "@istanbuljs/schema": { + "link": "../../../~npm~@istanbuljs+schema@0.1.6/node_modules/@istanbuljs/schema" + }, + "glob": { + "link": "../../~npm~glob@7.2.3/node_modules/glob" + }, + "minimatch": { + "link": "../../~npm~minimatch@3.1.5/node_modules/minimatch" + }, + "test-exclude": { + "pkg": { + "name": "test-exclude", + "version": "6.0.0" + } + } + } + }, + { + "id": "~npm~to-regex-range@5.0.1", + "node_modules": { + "is-number": { + "link": "../../~npm~is-number@7.0.0/node_modules/is-number" + }, + "to-regex-range": { + "pkg": { + "name": "to-regex-range", + "version": "5.0.1" + } + } + } + }, + { + "id": "~npm~tough-cookie@2.5.0", + "node_modules": { + "psl": { + "link": "../../~npm~psl@1.15.0/node_modules/psl" + }, + "punycode": { + "link": "../../~npm~punycode@2.3.1/node_modules/punycode" + }, + "tough-cookie": { + "pkg": { + "name": "tough-cookie", + "version": "2.5.0" + } + } + } + }, + { + "id": "~npm~trivial-deferred@1.1.2", + "node_modules": { + "trivial-deferred": { + "pkg": { + "name": "trivial-deferred", + "version": "1.1.2" + } + } + } + }, + { + "id": "~npm~tunnel-agent@0.6.0", + "node_modules": { + "safe-buffer": { + "link": "../../~npm~safe-buffer@5.2.1/node_modules/safe-buffer" + }, + "tunnel-agent": { + "pkg": { + "name": "tunnel-agent", + "version": "0.6.0" + } + } + } + }, + { + "id": "~npm~tweetnacl@0.14.5", + "node_modules": { + "tweetnacl": { + "pkg": { + "name": "tweetnacl", + "version": "0.14.5" + } + } + } + }, + { + "id": "~npm~type-fest@0.8.1", + "node_modules": { + "type-fest": { + "pkg": { + "name": "type-fest", + "version": "0.8.1" + } + } + } + }, + { + "id": "~npm~typedarray-to-buffer@3.1.5", + "node_modules": { + "is-typedarray": { + "link": "../../~npm~is-typedarray@1.0.0/node_modules/is-typedarray" + }, + "typedarray-to-buffer": { + "pkg": { + "name": "typedarray-to-buffer", + "version": "3.1.5" + } + } + } + }, + { + "id": "~npm~unicode-length@2.1.0", + "node_modules": { + "punycode": { + "link": "../../~npm~punycode@2.3.1/node_modules/punycode" + }, + "unicode-length": { + "pkg": { + "name": "unicode-length", + "version": "2.1.0" + } + } + } + }, + { + "id": "~npm~update-browserslist-db@1.3.3~peer.27c7ade2f48570ae", + "node_modules": { + ".bin": { + "dir": true + }, + "browserslist": { + "link": "../../~npm~browserslist@4.29.1/node_modules/browserslist" + }, + "escalade": { + "link": "../../~npm~escalade@3.2.0/node_modules/escalade" + }, + "picocolors": { + "link": "../../~npm~picocolors@1.1.1/node_modules/picocolors" + }, + "update-browserslist-db": { + "pkg": { + "name": "update-browserslist-db", + "version": "1.3.3" + } + } + } + }, + { + "id": "~npm~uri-js@4.4.1", + "node_modules": { + "punycode": { + "link": "../../~npm~punycode@2.3.1/node_modules/punycode" + }, + "uri-js": { + "pkg": { + "name": "uri-js", + "version": "4.4.1" + } + } + } + }, + { + "id": "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "node_modules": { + "react": { + "link": "../../~npm~react@18.2.0/node_modules/react" + }, + "use-sync-external-store": { + "pkg": { + "name": "use-sync-external-store", + "version": "1.2.0" + } + } + } + }, + { + "id": "~npm~uuid@3.4.0", + "node_modules": { + "uuid": { + "pkg": { + "name": "uuid", + "version": "3.4.0" + } + } + } + }, + { + "id": "~npm~uuid@8.3.2", + "node_modules": { + "uuid": { + "pkg": { + "name": "uuid", + "version": "8.3.2" + } + } + } + }, + { + "id": "~npm~verror@1.10.0", + "node_modules": { + "assert-plus": { + "link": "../../~npm~assert-plus@1.0.0/node_modules/assert-plus" + }, + "core-util-is": { + "link": "../../~npm~core-util-is@1.0.2/node_modules/core-util-is" + }, + "extsprintf": { + "link": "../../~npm~extsprintf@1.3.0/node_modules/extsprintf" + }, + "verror": { + "pkg": { + "name": "verror", + "version": "1.10.0" + } + } + } + }, + { + "id": "~npm~which-module@2.0.1", + "node_modules": { + "which-module": { + "pkg": { + "name": "which-module", + "version": "2.0.1" + } + } + } + }, + { + "id": "~npm~which@2.0.2", + "node_modules": { + "isexe": { + "link": "../../~npm~isexe@2.0.0/node_modules/isexe" + }, + "which": { + "pkg": { + "name": "which", + "version": "2.0.2" + } + } + } + }, + { + "id": "~npm~wrap-ansi@6.2.0", + "node_modules": { + "ansi-styles": { + "link": "../../~npm~ansi-styles@4.3.0/node_modules/ansi-styles" + }, + "string-width": { + "link": "../../~npm~string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../../~npm~strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "wrap-ansi": { + "pkg": { + "name": "wrap-ansi", + "version": "6.2.0" + } + } + } + }, + { + "id": "~npm~wrap-ansi@7.0.0", + "node_modules": { + "ansi-styles": { + "link": "../../~npm~ansi-styles@4.3.0/node_modules/ansi-styles" + }, + "string-width": { + "link": "../../~npm~string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../../~npm~strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "wrap-ansi": { + "pkg": { + "name": "wrap-ansi", + "version": "7.0.0" + } + } + } + }, + { + "id": "~npm~wrappy@1.0.2", + "node_modules": { + "wrappy": { + "pkg": { + "name": "wrappy", + "version": "1.0.2" + } + } + } + }, + { + "id": "~npm~write-file-atomic@3.0.3", + "node_modules": { + "imurmurhash": { + "link": "../../~npm~imurmurhash@0.1.4/node_modules/imurmurhash" + }, + "is-typedarray": { + "link": "../../~npm~is-typedarray@1.0.0/node_modules/is-typedarray" + }, + "signal-exit": { + "link": "../../~npm~signal-exit@3.0.7/node_modules/signal-exit" + }, + "typedarray-to-buffer": { + "link": "../../~npm~typedarray-to-buffer@3.1.5/node_modules/typedarray-to-buffer" + }, + "write-file-atomic": { + "pkg": { + "name": "write-file-atomic", + "version": "3.0.3" + } + } + } + }, + { + "id": "~npm~y18n@4.0.3", + "node_modules": { + "y18n": { + "pkg": { + "name": "y18n", + "version": "4.0.3" + } + } + } + }, + { + "id": "~npm~yallist@3.1.1", + "node_modules": { + "yallist": { + "pkg": { + "name": "yallist", + "version": "3.1.1" + } + } + } + }, + { + "id": "~npm~yallist@4.0.0", + "node_modules": { + "yallist": { + "pkg": { + "name": "yallist", + "version": "4.0.0" + } + } + } + }, + { + "id": "~npm~yaml@1.10.3", + "node_modules": { + "yaml": { + "pkg": { + "name": "yaml", + "version": "1.10.3" + } + } + } + }, + { + "id": "~npm~yargs-parser@18.1.3", + "node_modules": { + "camelcase": { + "link": "../../~npm~camelcase@5.3.1/node_modules/camelcase" + }, + "decamelize": { + "link": "../../~npm~decamelize@1.2.0/node_modules/decamelize" + }, + "yargs-parser": { + "pkg": { + "name": "yargs-parser", + "version": "18.1.3" + } + } + } + }, + { + "id": "~npm~yargs@15.4.1", + "node_modules": { + "cliui": { + "link": "../../~npm~cliui@6.0.0/node_modules/cliui" + }, + "decamelize": { + "link": "../../~npm~decamelize@1.2.0/node_modules/decamelize" + }, + "find-up": { + "link": "../../~npm~find-up@4.1.0/node_modules/find-up" + }, + "get-caller-file": { + "link": "../../~npm~get-caller-file@2.0.5/node_modules/get-caller-file" + }, + "require-directory": { + "link": "../../~npm~require-directory@2.1.1/node_modules/require-directory" + }, + "require-main-filename": { + "link": "../../~npm~require-main-filename@2.0.0/node_modules/require-main-filename" + }, + "set-blocking": { + "link": "../../~npm~set-blocking@2.0.0/node_modules/set-blocking" + }, + "string-width": { + "link": "../../~npm~string-width@4.2.3/node_modules/string-width" + }, + "which-module": { + "link": "../../~npm~which-module@2.0.1/node_modules/which-module" + }, + "y18n": { + "link": "../../~npm~y18n@4.0.3/node_modules/y18n" + }, + "yargs": { + "pkg": { + "name": "yargs", + "version": "15.4.1" + } + }, + "yargs-parser": { + "link": "../../~npm~yargs-parser@18.1.3/node_modules/yargs-parser" + } + } + } + ], + "importers": { + ".bin": { + "dir": true + }, + "@isaacs/string-locale-compare": { + "link": "../.vlt/~npm~@isaacs+string-locale-compare@1.1.0/node_modules/@isaacs/string-locale-compare" + }, + "debug": { + "link": ".vlt/~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms/node_modules/debug" + }, + "left-pad": { + "link": ".vlt/~npm~left-pad@1.3.0/node_modules/left-pad" + }, + "localdir": { + "link": "../vendor/localdir" + }, + "lp-alias": { + "link": ".vlt/~npm~left-pad@1.1.3/node_modules/left-pad" + }, + "lp-remote": { + "link": ".vlt/remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz/node_modules/left-pad" + }, + "ms-tgz": { + "link": ".vlt/file~vendor+ms-2.1.2.tgz/node_modules/ms" + }, + "react": { + "link": ".vlt/~npm~react@18.2.0/node_modules/react" + }, + "semver_x": { + "link": ".vlt/~npm~semver@7.6.0/node_modules/semver" + }, + "slc-git": { + "link": ".vlt/git~git_phttps_c++github.com+isaacs+string-locale-compare.git~v1.1.0/node_modules/@isaacs/string-locale-compare" + }, + "use-sync-external-store": { + "link": ".vlt/~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba/node_modules/use-sync-external-store" + } + }, + "members": {}, + "linkTargets": { + "vendor/localdir": { + "name": "localdir", + "version": "1.3.0" + } + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-trees/1.2.0/README.md b/crates/socket-patch-core/tests/fixtures/vlt-trees/1.2.0/README.md new file mode 100644 index 00000000..813f0958 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-trees/1.2.0/README.md @@ -0,0 +1,21 @@ +# vlt 1.2.0 installed layout + +`listing.json` is the on-disk layout real vlt 1.2.0 produced, captured with +`scripts/capture-vlt-tree.mjs` right after a cold `vlt install` (isolated +XDG dirs and VLT_CACHE, VLT_TELEMETRY=0, LANG=C, no lockfile). Store entry +names are byte-exact; the crawler tests in `crawler_npm_e2e.rs` stage the +listing as real directories, package.json files and relative symlinks. + +Project (`package.json` dependencies): + +- "left-pad": "1.3.0", "debug": "4.3.4", "@isaacs/string-locale-compare": "1.1.0" +- "react": "18.2.0", "use-sync-external-store": "1.2.0" +- "lp-alias": "npm:left-pad@1.1.3", "semver_x": "npm:semver@7.6.0" +- "slc-git": "github:isaacs/string-locale-compare#v1.1.0" +- "lp-remote": "https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz" +- "ms-tgz": "file:./vendor/ms-2.1.2.tgz" +- "localdir": "file:./vendor/localdir" (a left-pad@1.3.0 copy renamed localdir) + +`vlt.json`: `{"config": {"registries": {"npm": "https://registry.npmjs.org/"}}, "modifiers": {":root > #debug > #ms": "2.1.3"}}`. + +The git dependency's devDependencies account for most store entries. diff --git a/crates/socket-patch-core/tests/fixtures/vlt-trees/1.2.0/listing.json b/crates/socket-patch-core/tests/fixtures/vlt-trees/1.2.0/listing.json new file mode 100644 index 00000000..fb88c724 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-trees/1.2.0/listing.json @@ -0,0 +1,4596 @@ +{ + "vlt": "1.2.0", + "lockfileVersion": 1, + "storeFiles": [ + "vlt.json" + ], + "hoist": { + "@babel/code-frame": { + "link": "../../~npm~@babel+code-frame@7.29.7/node_modules/@babel/code-frame" + }, + "@babel/compat-data": { + "link": "../../~npm~@babel+compat-data@7.29.7/node_modules/@babel/compat-data" + }, + "@babel/core": { + "link": "../../~npm~@babel+core@7.29.7/node_modules/@babel/core" + }, + "@babel/generator": { + "link": "../../~npm~@babel+generator@7.29.8/node_modules/@babel/generator" + }, + "@babel/helper-compilation-targets": { + "link": "../../~npm~@babel+helper-compilation-targets@7.29.7/node_modules/@babel/helper-compilation-targets" + }, + "@babel/helper-globals": { + "link": "../../~npm~@babel+helper-globals@7.29.7/node_modules/@babel/helper-globals" + }, + "@babel/helper-module-imports": { + "link": "../../~npm~@babel+helper-module-imports@7.29.7/node_modules/@babel/helper-module-imports" + }, + "@babel/helper-module-transforms": { + "link": "../../~npm~@babel+helper-module-transforms@7.29.7~peer.a8a63a14dad8d5d8/node_modules/@babel/helper-module-transforms" + }, + "@babel/helper-string-parser": { + "link": "../../~npm~@babel+helper-string-parser@7.29.7/node_modules/@babel/helper-string-parser" + }, + "@babel/helper-validator-identifier": { + "link": "../../~npm~@babel+helper-validator-identifier@7.29.7/node_modules/@babel/helper-validator-identifier" + }, + "@babel/helper-validator-option": { + "link": "../../~npm~@babel+helper-validator-option@7.29.7/node_modules/@babel/helper-validator-option" + }, + "@babel/helpers": { + "link": "../../~npm~@babel+helpers@7.29.7/node_modules/@babel/helpers" + }, + "@babel/parser": { + "link": "../../~npm~@babel+parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/template": { + "link": "../../~npm~@babel+template@7.29.7/node_modules/@babel/template" + }, + "@babel/traverse": { + "link": "../../~npm~@babel+traverse@7.29.8/node_modules/@babel/traverse" + }, + "@babel/types": { + "link": "../../~npm~@babel+types@7.29.8/node_modules/@babel/types" + }, + "@isaacs/string-locale-compare": { + "link": "../../~npm~@isaacs+string-locale-compare@1.1.0/node_modules/@isaacs/string-locale-compare" + }, + "@istanbuljs/load-nyc-config": { + "link": "../../~npm~@istanbuljs+load-nyc-config@1.1.0/node_modules/@istanbuljs/load-nyc-config" + }, + "@istanbuljs/schema": { + "link": "../../~npm~@istanbuljs+schema@0.1.6/node_modules/@istanbuljs/schema" + }, + "@jridgewell/gen-mapping": { + "link": "../../~npm~@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/gen-mapping" + }, + "@jridgewell/remapping": { + "link": "../../~npm~@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping" + }, + "@jridgewell/resolve-uri": { + "link": "../../~npm~@jridgewell+resolve-uri@3.1.2/node_modules/@jridgewell/resolve-uri" + }, + "@jridgewell/sourcemap-codec": { + "link": "../../~npm~@jridgewell+sourcemap-codec@1.6.0/node_modules/@jridgewell/sourcemap-codec" + }, + "@jridgewell/trace-mapping": { + "link": "../../~npm~@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/trace-mapping" + }, + "aggregate-error": { + "link": "../~npm~aggregate-error@3.1.0/node_modules/aggregate-error" + }, + "ajv": { + "link": "../~npm~ajv@6.15.0/node_modules/ajv" + }, + "ansi-regex": { + "link": "../~npm~ansi-regex@5.0.1/node_modules/ansi-regex" + }, + "ansi-styles": { + "link": "../~npm~ansi-styles@4.3.0/node_modules/ansi-styles" + }, + "anymatch": { + "link": "../~npm~anymatch@3.1.3/node_modules/anymatch" + }, + "append-transform": { + "link": "../~npm~append-transform@2.0.0/node_modules/append-transform" + }, + "archy": { + "link": "../~npm~archy@1.0.0/node_modules/archy" + }, + "argparse": { + "link": "../~npm~argparse@1.0.10/node_modules/argparse" + }, + "asn1": { + "link": "../~npm~asn1@0.2.6/node_modules/asn1" + }, + "assert-plus": { + "link": "../~npm~assert-plus@1.0.0/node_modules/assert-plus" + }, + "async-hook-domain": { + "link": "../~npm~async-hook-domain@2.0.4/node_modules/async-hook-domain" + }, + "asynckit": { + "link": "../~npm~asynckit@0.4.0/node_modules/asynckit" + }, + "aws-sign2": { + "link": "../~npm~aws-sign2@0.7.0/node_modules/aws-sign2" + }, + "aws4": { + "link": "../~npm~aws4@1.13.2/node_modules/aws4" + }, + "balanced-match": { + "link": "../~npm~balanced-match@1.0.2/node_modules/balanced-match" + }, + "baseline-browser-mapping": { + "link": "../~npm~baseline-browser-mapping@2.11.26/node_modules/baseline-browser-mapping" + }, + "bcrypt-pbkdf": { + "link": "../~npm~bcrypt-pbkdf@1.0.2/node_modules/bcrypt-pbkdf" + }, + "binary-extensions": { + "link": "../~npm~binary-extensions@2.3.0/node_modules/binary-extensions" + }, + "bind-obj-methods": { + "link": "../~npm~bind-obj-methods@3.0.0/node_modules/bind-obj-methods" + }, + "brace-expansion": { + "link": "../~npm~brace-expansion@1.1.21/node_modules/brace-expansion" + }, + "braces": { + "link": "../~npm~braces@3.0.3/node_modules/braces" + }, + "browserslist": { + "link": "../~npm~browserslist@4.29.1/node_modules/browserslist" + }, + "buffer-from": { + "link": "../~npm~buffer-from@1.1.2/node_modules/buffer-from" + }, + "caching-transform": { + "link": "../~npm~caching-transform@4.0.0/node_modules/caching-transform" + }, + "camelcase": { + "link": "../~npm~camelcase@5.3.1/node_modules/camelcase" + }, + "caniuse-lite": { + "link": "../~npm~caniuse-lite@1.0.30001812/node_modules/caniuse-lite" + }, + "caseless": { + "link": "../~npm~caseless@0.12.0/node_modules/caseless" + }, + "chokidar": { + "link": "../~npm~chokidar@3.6.0/node_modules/chokidar" + }, + "clean-stack": { + "link": "../~npm~clean-stack@2.2.0/node_modules/clean-stack" + }, + "cliui": { + "link": "../~npm~cliui@7.0.4/node_modules/cliui" + }, + "color-convert": { + "link": "../~npm~color-convert@2.0.1/node_modules/color-convert" + }, + "color-name": { + "link": "../~npm~color-name@1.1.4/node_modules/color-name" + }, + "color-support": { + "link": "../~npm~color-support@1.1.3/node_modules/color-support" + }, + "combined-stream": { + "link": "../~npm~combined-stream@1.0.8/node_modules/combined-stream" + }, + "commondir": { + "link": "../~npm~commondir@1.0.1/node_modules/commondir" + }, + "concat-map": { + "link": "../~npm~concat-map@0.0.1/node_modules/concat-map" + }, + "convert-source-map": { + "link": "../~npm~convert-source-map@2.0.0/node_modules/convert-source-map" + }, + "core-util-is": { + "link": "../~npm~core-util-is@1.0.2/node_modules/core-util-is" + }, + "coveralls": { + "link": "../~npm~coveralls@3.1.1/node_modules/coveralls" + }, + "cross-spawn": { + "link": "../~npm~cross-spawn@7.0.6/node_modules/cross-spawn" + }, + "dashdash": { + "link": "../~npm~dashdash@1.14.1/node_modules/dashdash" + }, + "debug": { + "link": "../~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms/node_modules/debug" + }, + "decamelize": { + "link": "../~npm~decamelize@1.2.0/node_modules/decamelize" + }, + "default-require-extensions": { + "link": "../~npm~default-require-extensions@3.0.1/node_modules/default-require-extensions" + }, + "delayed-stream": { + "link": "../~npm~delayed-stream@1.0.0/node_modules/delayed-stream" + }, + "diff": { + "link": "../~npm~diff@4.0.4/node_modules/diff" + }, + "ecc-jsbn": { + "link": "../~npm~ecc-jsbn@0.1.2/node_modules/ecc-jsbn" + }, + "electron-to-chromium": { + "link": "../~npm~electron-to-chromium@1.5.439/node_modules/electron-to-chromium" + }, + "emoji-regex": { + "link": "../~npm~emoji-regex@8.0.0/node_modules/emoji-regex" + }, + "es6-error": { + "link": "../~npm~es6-error@4.1.1/node_modules/es6-error" + }, + "escalade": { + "link": "../~npm~escalade@3.2.0/node_modules/escalade" + }, + "escape-string-regexp": { + "link": "../~npm~escape-string-regexp@2.0.0/node_modules/escape-string-regexp" + }, + "esprima": { + "link": "../~npm~esprima@4.0.1/node_modules/esprima" + }, + "events-to-array": { + "link": "../~npm~events-to-array@1.1.2/node_modules/events-to-array" + }, + "extend": { + "link": "../~npm~extend@3.0.2/node_modules/extend" + }, + "extsprintf": { + "link": "../~npm~extsprintf@1.3.0/node_modules/extsprintf" + }, + "fast-deep-equal": { + "link": "../~npm~fast-deep-equal@3.1.3/node_modules/fast-deep-equal" + }, + "fast-json-stable-stringify": { + "link": "../~npm~fast-json-stable-stringify@2.1.0/node_modules/fast-json-stable-stringify" + }, + "fill-range": { + "link": "../~npm~fill-range@7.1.1/node_modules/fill-range" + }, + "find-cache-dir": { + "link": "../~npm~find-cache-dir@3.3.2/node_modules/find-cache-dir" + }, + "find-up": { + "link": "../~npm~find-up@4.1.0/node_modules/find-up" + }, + "findit": { + "link": "../~npm~findit@2.0.0/node_modules/findit" + }, + "foreground-child": { + "link": "../~npm~foreground-child@2.0.0/node_modules/foreground-child" + }, + "forever-agent": { + "link": "../~npm~forever-agent@0.6.1/node_modules/forever-agent" + }, + "form-data": { + "link": "../~npm~form-data@2.3.3/node_modules/form-data" + }, + "fromentries": { + "link": "../~npm~fromentries@1.3.2/node_modules/fromentries" + }, + "fs-exists-cached": { + "link": "../~npm~fs-exists-cached@1.0.0/node_modules/fs-exists-cached" + }, + "fs.realpath": { + "link": "../~npm~fs.realpath@1.0.0/node_modules/fs.realpath" + }, + "fsevents": { + "link": "../~npm~fsevents@2.3.3/node_modules/fsevents" + }, + "function-loop": { + "link": "../~npm~function-loop@2.0.1/node_modules/function-loop" + }, + "gensync": { + "link": "../~npm~gensync@1.0.0-beta.2/node_modules/gensync" + }, + "get-caller-file": { + "link": "../~npm~get-caller-file@2.0.5/node_modules/get-caller-file" + }, + "get-package-type": { + "link": "../~npm~get-package-type@0.1.0/node_modules/get-package-type" + }, + "getpass": { + "link": "../~npm~getpass@0.1.7/node_modules/getpass" + }, + "glob": { + "link": "../~npm~glob@7.2.3/node_modules/glob" + }, + "glob-parent": { + "link": "../~npm~glob-parent@5.1.2/node_modules/glob-parent" + }, + "graceful-fs": { + "link": "../~npm~graceful-fs@4.2.11/node_modules/graceful-fs" + }, + "har-schema": { + "link": "../~npm~har-schema@2.0.0/node_modules/har-schema" + }, + "har-validator": { + "link": "../~npm~har-validator@5.1.5/node_modules/har-validator" + }, + "has-flag": { + "link": "../~npm~has-flag@4.0.0/node_modules/has-flag" + }, + "hasha": { + "link": "../~npm~hasha@5.2.2/node_modules/hasha" + }, + "html-escaper": { + "link": "../~npm~html-escaper@2.0.2/node_modules/html-escaper" + }, + "http-signature": { + "link": "../~npm~http-signature@1.2.0/node_modules/http-signature" + }, + "imurmurhash": { + "link": "../~npm~imurmurhash@0.1.4/node_modules/imurmurhash" + }, + "indent-string": { + "link": "../~npm~indent-string@4.0.0/node_modules/indent-string" + }, + "inflight": { + "link": "../~npm~inflight@1.0.6/node_modules/inflight" + }, + "inherits": { + "link": "../~npm~inherits@2.0.4/node_modules/inherits" + }, + "is-binary-path": { + "link": "../~npm~is-binary-path@2.1.0/node_modules/is-binary-path" + }, + "is-extglob": { + "link": "../~npm~is-extglob@2.1.1/node_modules/is-extglob" + }, + "is-fullwidth-code-point": { + "link": "../~npm~is-fullwidth-code-point@3.0.0/node_modules/is-fullwidth-code-point" + }, + "is-glob": { + "link": "../~npm~is-glob@4.0.3/node_modules/is-glob" + }, + "is-number": { + "link": "../~npm~is-number@7.0.0/node_modules/is-number" + }, + "is-stream": { + "link": "../~npm~is-stream@2.0.1/node_modules/is-stream" + }, + "is-typedarray": { + "link": "../~npm~is-typedarray@1.0.0/node_modules/is-typedarray" + }, + "is-windows": { + "link": "../~npm~is-windows@1.0.2/node_modules/is-windows" + }, + "isexe": { + "link": "../~npm~isexe@2.0.0/node_modules/isexe" + }, + "isstream": { + "link": "../~npm~isstream@0.1.2/node_modules/isstream" + }, + "istanbul-lib-coverage": { + "link": "../~npm~istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-hook": { + "link": "../~npm~istanbul-lib-hook@3.0.0/node_modules/istanbul-lib-hook" + }, + "istanbul-lib-instrument": { + "link": "../~npm~istanbul-lib-instrument@4.0.3/node_modules/istanbul-lib-instrument" + }, + "istanbul-lib-processinfo": { + "link": "../~npm~istanbul-lib-processinfo@2.0.3/node_modules/istanbul-lib-processinfo" + }, + "istanbul-lib-report": { + "link": "../~npm~istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report" + }, + "istanbul-lib-source-maps": { + "link": "../~npm~istanbul-lib-source-maps@4.0.1/node_modules/istanbul-lib-source-maps" + }, + "istanbul-reports": { + "link": "../~npm~istanbul-reports@3.2.0/node_modules/istanbul-reports" + }, + "jackspeak": { + "link": "../~npm~jackspeak@1.4.2/node_modules/jackspeak" + }, + "js-tokens": { + "link": "../~npm~js-tokens@4.0.0/node_modules/js-tokens" + }, + "js-yaml": { + "link": "../~npm~js-yaml@3.15.2/node_modules/js-yaml" + }, + "jsbn": { + "link": "../~npm~jsbn@0.1.1/node_modules/jsbn" + }, + "jsesc": { + "link": "../~npm~jsesc@3.1.0/node_modules/jsesc" + }, + "json-schema": { + "link": "../~npm~json-schema@0.4.0/node_modules/json-schema" + }, + "json-schema-traverse": { + "link": "../~npm~json-schema-traverse@0.4.1/node_modules/json-schema-traverse" + }, + "json-stringify-safe": { + "link": "../~npm~json-stringify-safe@5.0.1/node_modules/json-stringify-safe" + }, + "json5": { + "link": "../~npm~json5@2.2.3/node_modules/json5" + }, + "jsprim": { + "link": "../~npm~jsprim@1.4.2/node_modules/jsprim" + }, + "lcov-parse": { + "link": "../~npm~lcov-parse@1.0.0/node_modules/lcov-parse" + }, + "left-pad": { + "link": "../~npm~left-pad@1.3.0/node_modules/left-pad" + }, + "libtap": { + "link": "../~npm~libtap@1.4.1/node_modules/libtap" + }, + "locate-path": { + "link": "../~npm~locate-path@5.0.0/node_modules/locate-path" + }, + "lodash.flattendeep": { + "link": "../~npm~lodash.flattendeep@4.4.0/node_modules/lodash.flattendeep" + }, + "log-driver": { + "link": "../~npm~log-driver@1.2.7/node_modules/log-driver" + }, + "loose-envify": { + "link": "../~npm~loose-envify@1.4.0/node_modules/loose-envify" + }, + "lru-cache": { + "link": "../~npm~lru-cache@6.0.0/node_modules/lru-cache" + }, + "make-dir": { + "link": "../~npm~make-dir@4.0.0/node_modules/make-dir" + }, + "mime-db": { + "link": "../~npm~mime-db@1.52.0/node_modules/mime-db" + }, + "mime-types": { + "link": "../~npm~mime-types@2.1.35/node_modules/mime-types" + }, + "minimatch": { + "link": "../~npm~minimatch@3.1.5/node_modules/minimatch" + }, + "minimist": { + "link": "../~npm~minimist@1.2.8/node_modules/minimist" + }, + "minipass": { + "link": "../~npm~minipass@3.3.6/node_modules/minipass" + }, + "mkdirp": { + "link": "../~npm~mkdirp@1.0.4/node_modules/mkdirp" + }, + "ms-tgz": { + "link": "../file~vendor+ms-2.1.2.tgz/node_modules/ms" + }, + "node-preload": { + "link": "../~npm~node-preload@0.2.1/node_modules/node-preload" + }, + "node-releases": { + "link": "../~npm~node-releases@2.0.57/node_modules/node-releases" + }, + "normalize-path": { + "link": "../~npm~normalize-path@3.0.0/node_modules/normalize-path" + }, + "nyc": { + "link": "../~npm~nyc@15.1.0/node_modules/nyc" + }, + "oauth-sign": { + "link": "../~npm~oauth-sign@0.9.0/node_modules/oauth-sign" + }, + "once": { + "link": "../~npm~once@1.4.0/node_modules/once" + }, + "opener": { + "link": "../~npm~opener@1.5.2/node_modules/opener" + }, + "own-or": { + "link": "../~npm~own-or@1.0.0/node_modules/own-or" + }, + "own-or-env": { + "link": "../~npm~own-or-env@1.0.2/node_modules/own-or-env" + }, + "p-limit": { + "link": "../~npm~p-limit@2.3.0/node_modules/p-limit" + }, + "p-locate": { + "link": "../~npm~p-locate@4.1.0/node_modules/p-locate" + }, + "p-map": { + "link": "../~npm~p-map@3.0.0/node_modules/p-map" + }, + "p-try": { + "link": "../~npm~p-try@2.2.0/node_modules/p-try" + }, + "package-hash": { + "link": "../~npm~package-hash@4.0.0/node_modules/package-hash" + }, + "path-exists": { + "link": "../~npm~path-exists@4.0.0/node_modules/path-exists" + }, + "path-is-absolute": { + "link": "../~npm~path-is-absolute@1.0.1/node_modules/path-is-absolute" + }, + "path-key": { + "link": "../~npm~path-key@3.1.1/node_modules/path-key" + }, + "performance-now": { + "link": "../~npm~performance-now@2.1.0/node_modules/performance-now" + }, + "picocolors": { + "link": "../~npm~picocolors@1.1.1/node_modules/picocolors" + }, + "picomatch": { + "link": "../~npm~picomatch@2.3.2/node_modules/picomatch" + }, + "pkg-dir": { + "link": "../~npm~pkg-dir@4.2.0/node_modules/pkg-dir" + }, + "process-on-spawn": { + "link": "../~npm~process-on-spawn@1.1.0/node_modules/process-on-spawn" + }, + "psl": { + "link": "../~npm~psl@1.15.0/node_modules/psl" + }, + "punycode": { + "link": "../~npm~punycode@2.3.1/node_modules/punycode" + }, + "qs": { + "link": "../~npm~qs@6.5.5/node_modules/qs" + }, + "react": { + "link": "../~npm~react@18.2.0/node_modules/react" + }, + "readdirp": { + "link": "../~npm~readdirp@3.6.0/node_modules/readdirp" + }, + "release-zalgo": { + "link": "../~npm~release-zalgo@1.0.0/node_modules/release-zalgo" + }, + "request": { + "link": "../~npm~request@2.88.2/node_modules/request" + }, + "require-directory": { + "link": "../~npm~require-directory@2.1.1/node_modules/require-directory" + }, + "require-main-filename": { + "link": "../~npm~require-main-filename@2.0.0/node_modules/require-main-filename" + }, + "resolve-from": { + "link": "../~npm~resolve-from@5.0.0/node_modules/resolve-from" + }, + "rimraf": { + "link": "../~npm~rimraf@3.0.2/node_modules/rimraf" + }, + "safe-buffer": { + "link": "../~npm~safe-buffer@5.2.1/node_modules/safe-buffer" + }, + "safer-buffer": { + "link": "../~npm~safer-buffer@2.1.2/node_modules/safer-buffer" + }, + "semver_x": { + "link": "../~npm~semver@7.6.0/node_modules/semver" + }, + "set-blocking": { + "link": "../~npm~set-blocking@2.0.0/node_modules/set-blocking" + }, + "shebang-command": { + "link": "../~npm~shebang-command@2.0.0/node_modules/shebang-command" + }, + "shebang-regex": { + "link": "../~npm~shebang-regex@3.0.0/node_modules/shebang-regex" + }, + "signal-exit": { + "link": "../~npm~signal-exit@3.0.7/node_modules/signal-exit" + }, + "source-map": { + "link": "../~npm~source-map@0.6.1/node_modules/source-map" + }, + "source-map-support": { + "link": "../~npm~source-map-support@0.5.21/node_modules/source-map-support" + }, + "spawn-wrap": { + "link": "../~npm~spawn-wrap@2.0.0/node_modules/spawn-wrap" + }, + "sprintf-js": { + "link": "../~npm~sprintf-js@1.0.3/node_modules/sprintf-js" + }, + "sshpk": { + "link": "../~npm~sshpk@1.18.0/node_modules/sshpk" + }, + "stack-utils": { + "link": "../~npm~stack-utils@2.0.6/node_modules/stack-utils" + }, + "string-width": { + "link": "../~npm~string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../~npm~strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "strip-bom": { + "link": "../~npm~strip-bom@4.0.0/node_modules/strip-bom" + }, + "supports-color": { + "link": "../~npm~supports-color@7.2.0/node_modules/supports-color" + }, + "tap": { + "link": "../~npm~tap@15.2.3~peer.6f88d0ccf17dbbdc/node_modules/tap" + }, + "tap-mocha-reporter": { + "link": "../~npm~tap-mocha-reporter@5.0.4/node_modules/tap-mocha-reporter" + }, + "tap-parser": { + "link": "../~npm~tap-parser@11.0.2/node_modules/tap-parser" + }, + "tap-yaml": { + "link": "../~npm~tap-yaml@1.0.2/node_modules/tap-yaml" + }, + "tcompare": { + "link": "../~npm~tcompare@5.0.7/node_modules/tcompare" + }, + "test-exclude": { + "link": "../~npm~test-exclude@6.0.0/node_modules/test-exclude" + }, + "to-regex-range": { + "link": "../~npm~to-regex-range@5.0.1/node_modules/to-regex-range" + }, + "tough-cookie": { + "link": "../~npm~tough-cookie@2.5.0/node_modules/tough-cookie" + }, + "trivial-deferred": { + "link": "../~npm~trivial-deferred@1.1.2/node_modules/trivial-deferred" + }, + "tunnel-agent": { + "link": "../~npm~tunnel-agent@0.6.0/node_modules/tunnel-agent" + }, + "tweetnacl": { + "link": "../~npm~tweetnacl@0.14.5/node_modules/tweetnacl" + }, + "type-fest": { + "link": "../~npm~type-fest@0.8.1/node_modules/type-fest" + }, + "typedarray-to-buffer": { + "link": "../~npm~typedarray-to-buffer@3.1.5/node_modules/typedarray-to-buffer" + }, + "unicode-length": { + "link": "../~npm~unicode-length@2.1.0/node_modules/unicode-length" + }, + "update-browserslist-db": { + "link": "../~npm~update-browserslist-db@1.3.3~peer.27c7ade2f48570ae/node_modules/update-browserslist-db" + }, + "uri-js": { + "link": "../~npm~uri-js@4.4.1/node_modules/uri-js" + }, + "use-sync-external-store": { + "link": "../~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba/node_modules/use-sync-external-store" + }, + "uuid": { + "link": "../~npm~uuid@8.3.2/node_modules/uuid" + }, + "verror": { + "link": "../~npm~verror@1.10.0/node_modules/verror" + }, + "which": { + "link": "../~npm~which@2.0.2/node_modules/which" + }, + "which-module": { + "link": "../~npm~which-module@2.0.1/node_modules/which-module" + }, + "wrap-ansi": { + "link": "../~npm~wrap-ansi@7.0.0/node_modules/wrap-ansi" + }, + "wrappy": { + "link": "../~npm~wrappy@1.0.2/node_modules/wrappy" + }, + "write-file-atomic": { + "link": "../~npm~write-file-atomic@3.0.3/node_modules/write-file-atomic" + }, + "y18n": { + "link": "../~npm~y18n@4.0.3/node_modules/y18n" + }, + "yallist": { + "link": "../~npm~yallist@4.0.0/node_modules/yallist" + }, + "yaml": { + "link": "../~npm~yaml@1.10.3/node_modules/yaml" + }, + "yargs": { + "link": "../~npm~yargs@15.4.1/node_modules/yargs" + }, + "yargs-parser": { + "link": "../~npm~yargs-parser@18.1.3/node_modules/yargs-parser" + } + }, + "store": [ + { + "id": "file~vendor+ms-2.1.2.tgz", + "node_modules": { + "ms": { + "pkg": { + "name": "ms", + "version": "2.1.2" + } + } + } + }, + { + "id": "git~github_cisaacs+string-locale-compare~v1.1.0", + "node_modules": { + ".bin": { + "dir": true + }, + "@isaacs/string-locale-compare": { + "pkg": { + "name": "@isaacs/string-locale-compare", + "version": "1.1.0" + } + }, + "tap": { + "link": "../../~npm~tap@15.2.3~peer.6f88d0ccf17dbbdc/node_modules/tap" + } + } + }, + { + "id": "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "node_modules": { + "left-pad": { + "pkg": { + "name": "left-pad", + "version": "1.2.0" + } + } + } + }, + { + "id": "~npm~@babel+code-frame@7.29.7", + "node_modules": { + "@babel/code-frame": { + "pkg": { + "name": "@babel/code-frame", + "version": "7.29.7" + } + }, + "@babel/helper-validator-identifier": { + "link": "../../../~npm~@babel+helper-validator-identifier@7.29.7/node_modules/@babel/helper-validator-identifier" + }, + "js-tokens": { + "link": "../../~npm~js-tokens@4.0.0/node_modules/js-tokens" + }, + "picocolors": { + "link": "../../~npm~picocolors@1.1.1/node_modules/picocolors" + } + } + }, + { + "id": "~npm~@babel+compat-data@7.29.7", + "node_modules": { + "@babel/compat-data": { + "pkg": { + "name": "@babel/compat-data", + "version": "7.29.7" + } + } + } + }, + { + "id": "~npm~@babel+core@7.29.7", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/code-frame": { + "link": "../../../~npm~@babel+code-frame@7.29.7/node_modules/@babel/code-frame" + }, + "@babel/core": { + "pkg": { + "name": "@babel/core", + "version": "7.29.7" + } + }, + "@babel/generator": { + "link": "../../../~npm~@babel+generator@7.29.8/node_modules/@babel/generator" + }, + "@babel/helper-compilation-targets": { + "link": "../../../~npm~@babel+helper-compilation-targets@7.29.7/node_modules/@babel/helper-compilation-targets" + }, + "@babel/helper-module-transforms": { + "link": "../../../~npm~@babel+helper-module-transforms@7.29.7~peer.a8a63a14dad8d5d8/node_modules/@babel/helper-module-transforms" + }, + "@babel/helpers": { + "link": "../../../~npm~@babel+helpers@7.29.7/node_modules/@babel/helpers" + }, + "@babel/parser": { + "link": "../../../~npm~@babel+parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/template": { + "link": "../../../~npm~@babel+template@7.29.7/node_modules/@babel/template" + }, + "@babel/traverse": { + "link": "../../../~npm~@babel+traverse@7.29.8/node_modules/@babel/traverse" + }, + "@babel/types": { + "link": "../../../~npm~@babel+types@7.29.8/node_modules/@babel/types" + }, + "@jridgewell/remapping": { + "link": "../../../~npm~@jridgewell+remapping@2.3.5/node_modules/@jridgewell/remapping" + }, + "convert-source-map": { + "link": "../../~npm~convert-source-map@2.0.0/node_modules/convert-source-map" + }, + "debug": { + "link": "../../~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms/node_modules/debug" + }, + "gensync": { + "link": "../../~npm~gensync@1.0.0-beta.2/node_modules/gensync" + }, + "json5": { + "link": "../../~npm~json5@2.2.3/node_modules/json5" + }, + "semver": { + "link": "../../~npm~semver@6.3.1/node_modules/semver" + } + } + }, + { + "id": "~npm~@babel+generator@7.29.8", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/generator": { + "pkg": { + "name": "@babel/generator", + "version": "7.29.8" + } + }, + "@babel/parser": { + "link": "../../../~npm~@babel+parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/types": { + "link": "../../../~npm~@babel+types@7.29.8/node_modules/@babel/types" + }, + "@jridgewell/gen-mapping": { + "link": "../../../~npm~@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/gen-mapping" + }, + "@jridgewell/trace-mapping": { + "link": "../../../~npm~@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/trace-mapping" + }, + "jsesc": { + "link": "../../~npm~jsesc@3.1.0/node_modules/jsesc" + } + } + }, + { + "id": "~npm~@babel+helper-compilation-targets@7.29.7", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/compat-data": { + "link": "../../../~npm~@babel+compat-data@7.29.7/node_modules/@babel/compat-data" + }, + "@babel/helper-compilation-targets": { + "pkg": { + "name": "@babel/helper-compilation-targets", + "version": "7.29.7" + } + }, + "@babel/helper-validator-option": { + "link": "../../../~npm~@babel+helper-validator-option@7.29.7/node_modules/@babel/helper-validator-option" + }, + "browserslist": { + "link": "../../~npm~browserslist@4.29.1/node_modules/browserslist" + }, + "lru-cache": { + "link": "../../~npm~lru-cache@5.1.1/node_modules/lru-cache" + }, + "semver": { + "link": "../../~npm~semver@6.3.1/node_modules/semver" + } + } + }, + { + "id": "~npm~@babel+helper-globals@7.29.7", + "node_modules": { + "@babel/helper-globals": { + "pkg": { + "name": "@babel/helper-globals", + "version": "7.29.7" + } + } + } + }, + { + "id": "~npm~@babel+helper-module-imports@7.29.7", + "node_modules": { + "@babel/helper-module-imports": { + "pkg": { + "name": "@babel/helper-module-imports", + "version": "7.29.7" + } + }, + "@babel/traverse": { + "link": "../../../~npm~@babel+traverse@7.29.8/node_modules/@babel/traverse" + }, + "@babel/types": { + "link": "../../../~npm~@babel+types@7.29.8/node_modules/@babel/types" + } + } + }, + { + "id": "~npm~@babel+helper-module-transforms@7.29.7~peer.a8a63a14dad8d5d8", + "node_modules": { + "@babel/core": { + "link": "../../../~npm~@babel+core@7.29.7/node_modules/@babel/core" + }, + "@babel/helper-module-imports": { + "link": "../../../~npm~@babel+helper-module-imports@7.29.7/node_modules/@babel/helper-module-imports" + }, + "@babel/helper-module-transforms": { + "pkg": { + "name": "@babel/helper-module-transforms", + "version": "7.29.7" + } + }, + "@babel/helper-validator-identifier": { + "link": "../../../~npm~@babel+helper-validator-identifier@7.29.7/node_modules/@babel/helper-validator-identifier" + }, + "@babel/traverse": { + "link": "../../../~npm~@babel+traverse@7.29.8/node_modules/@babel/traverse" + } + } + }, + { + "id": "~npm~@babel+helper-string-parser@7.29.7", + "node_modules": { + "@babel/helper-string-parser": { + "pkg": { + "name": "@babel/helper-string-parser", + "version": "7.29.7" + } + } + } + }, + { + "id": "~npm~@babel+helper-validator-identifier@7.29.7", + "node_modules": { + "@babel/helper-validator-identifier": { + "pkg": { + "name": "@babel/helper-validator-identifier", + "version": "7.29.7" + } + } + } + }, + { + "id": "~npm~@babel+helper-validator-option@7.29.7", + "node_modules": { + "@babel/helper-validator-option": { + "pkg": { + "name": "@babel/helper-validator-option", + "version": "7.29.7" + } + } + } + }, + { + "id": "~npm~@babel+helpers@7.29.7", + "node_modules": { + "@babel/helpers": { + "pkg": { + "name": "@babel/helpers", + "version": "7.29.7" + } + }, + "@babel/template": { + "link": "../../../~npm~@babel+template@7.29.7/node_modules/@babel/template" + }, + "@babel/types": { + "link": "../../../~npm~@babel+types@7.29.8/node_modules/@babel/types" + } + } + }, + { + "id": "~npm~@babel+parser@7.29.9", + "node_modules": { + "@babel/parser": { + "pkg": { + "name": "@babel/parser", + "version": "7.29.9" + } + }, + "@babel/types": { + "link": "../../../~npm~@babel+types@7.29.8/node_modules/@babel/types" + } + } + }, + { + "id": "~npm~@babel+template@7.29.7", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/code-frame": { + "link": "../../../~npm~@babel+code-frame@7.29.7/node_modules/@babel/code-frame" + }, + "@babel/parser": { + "link": "../../../~npm~@babel+parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/template": { + "pkg": { + "name": "@babel/template", + "version": "7.29.7" + } + }, + "@babel/types": { + "link": "../../../~npm~@babel+types@7.29.8/node_modules/@babel/types" + } + } + }, + { + "id": "~npm~@babel+traverse@7.29.8", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/code-frame": { + "link": "../../../~npm~@babel+code-frame@7.29.7/node_modules/@babel/code-frame" + }, + "@babel/generator": { + "link": "../../../~npm~@babel+generator@7.29.8/node_modules/@babel/generator" + }, + "@babel/helper-globals": { + "link": "../../../~npm~@babel+helper-globals@7.29.7/node_modules/@babel/helper-globals" + }, + "@babel/parser": { + "link": "../../../~npm~@babel+parser@7.29.9/node_modules/@babel/parser" + }, + "@babel/template": { + "link": "../../../~npm~@babel+template@7.29.7/node_modules/@babel/template" + }, + "@babel/traverse": { + "pkg": { + "name": "@babel/traverse", + "version": "7.29.8" + } + }, + "@babel/types": { + "link": "../../../~npm~@babel+types@7.29.8/node_modules/@babel/types" + }, + "debug": { + "link": "../../~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms/node_modules/debug" + } + } + }, + { + "id": "~npm~@babel+types@7.29.8", + "node_modules": { + "@babel/helper-string-parser": { + "link": "../../../~npm~@babel+helper-string-parser@7.29.7/node_modules/@babel/helper-string-parser" + }, + "@babel/helper-validator-identifier": { + "link": "../../../~npm~@babel+helper-validator-identifier@7.29.7/node_modules/@babel/helper-validator-identifier" + }, + "@babel/types": { + "pkg": { + "name": "@babel/types", + "version": "7.29.8" + } + } + } + }, + { + "id": "~npm~@isaacs+string-locale-compare@1.1.0", + "node_modules": { + "@isaacs/string-locale-compare": { + "pkg": { + "name": "@isaacs/string-locale-compare", + "version": "1.1.0" + } + } + } + }, + { + "id": "~npm~@istanbuljs+load-nyc-config@1.1.0", + "node_modules": { + ".bin": { + "dir": true + }, + "@istanbuljs/load-nyc-config": { + "pkg": { + "name": "@istanbuljs/load-nyc-config", + "version": "1.1.0" + } + }, + "camelcase": { + "link": "../../~npm~camelcase@5.3.1/node_modules/camelcase" + }, + "find-up": { + "link": "../../~npm~find-up@4.1.0/node_modules/find-up" + }, + "get-package-type": { + "link": "../../~npm~get-package-type@0.1.0/node_modules/get-package-type" + }, + "js-yaml": { + "link": "../../~npm~js-yaml@3.15.2/node_modules/js-yaml" + }, + "resolve-from": { + "link": "../../~npm~resolve-from@5.0.0/node_modules/resolve-from" + } + } + }, + { + "id": "~npm~@istanbuljs+schema@0.1.6", + "node_modules": { + "@istanbuljs/schema": { + "pkg": { + "name": "@istanbuljs/schema", + "version": "0.1.6" + } + } + } + }, + { + "id": "~npm~@jridgewell+gen-mapping@0.3.13", + "node_modules": { + "@jridgewell/gen-mapping": { + "pkg": { + "name": "@jridgewell/gen-mapping", + "version": "0.3.13" + } + }, + "@jridgewell/sourcemap-codec": { + "link": "../../../~npm~@jridgewell+sourcemap-codec@1.6.0/node_modules/@jridgewell/sourcemap-codec" + }, + "@jridgewell/trace-mapping": { + "link": "../../../~npm~@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/trace-mapping" + } + } + }, + { + "id": "~npm~@jridgewell+remapping@2.3.5", + "node_modules": { + "@jridgewell/gen-mapping": { + "link": "../../../~npm~@jridgewell+gen-mapping@0.3.13/node_modules/@jridgewell/gen-mapping" + }, + "@jridgewell/remapping": { + "pkg": { + "name": "@jridgewell/remapping", + "version": "2.3.5" + } + }, + "@jridgewell/trace-mapping": { + "link": "../../../~npm~@jridgewell+trace-mapping@0.3.31/node_modules/@jridgewell/trace-mapping" + } + } + }, + { + "id": "~npm~@jridgewell+resolve-uri@3.1.2", + "node_modules": { + "@jridgewell/resolve-uri": { + "pkg": { + "name": "@jridgewell/resolve-uri", + "version": "3.1.2" + } + } + } + }, + { + "id": "~npm~@jridgewell+sourcemap-codec@1.6.0", + "node_modules": { + "@jridgewell/sourcemap-codec": { + "pkg": { + "name": "@jridgewell/sourcemap-codec", + "version": "1.6.0" + } + } + } + }, + { + "id": "~npm~@jridgewell+trace-mapping@0.3.31", + "node_modules": { + "@jridgewell/resolve-uri": { + "link": "../../../~npm~@jridgewell+resolve-uri@3.1.2/node_modules/@jridgewell/resolve-uri" + }, + "@jridgewell/sourcemap-codec": { + "link": "../../../~npm~@jridgewell+sourcemap-codec@1.6.0/node_modules/@jridgewell/sourcemap-codec" + }, + "@jridgewell/trace-mapping": { + "pkg": { + "name": "@jridgewell/trace-mapping", + "version": "0.3.31" + } + } + } + }, + { + "id": "~npm~aggregate-error@3.1.0", + "node_modules": { + "aggregate-error": { + "pkg": { + "name": "aggregate-error", + "version": "3.1.0" + } + }, + "clean-stack": { + "link": "../../~npm~clean-stack@2.2.0/node_modules/clean-stack" + }, + "indent-string": { + "link": "../../~npm~indent-string@4.0.0/node_modules/indent-string" + } + } + }, + { + "id": "~npm~ajv@6.15.0", + "node_modules": { + "ajv": { + "pkg": { + "name": "ajv", + "version": "6.15.0" + } + }, + "fast-deep-equal": { + "link": "../../~npm~fast-deep-equal@3.1.3/node_modules/fast-deep-equal" + }, + "fast-json-stable-stringify": { + "link": "../../~npm~fast-json-stable-stringify@2.1.0/node_modules/fast-json-stable-stringify" + }, + "json-schema-traverse": { + "link": "../../~npm~json-schema-traverse@0.4.1/node_modules/json-schema-traverse" + }, + "uri-js": { + "link": "../../~npm~uri-js@4.4.1/node_modules/uri-js" + } + } + }, + { + "id": "~npm~ansi-regex@5.0.1", + "node_modules": { + "ansi-regex": { + "pkg": { + "name": "ansi-regex", + "version": "5.0.1" + } + } + } + }, + { + "id": "~npm~ansi-styles@4.3.0", + "node_modules": { + "ansi-styles": { + "pkg": { + "name": "ansi-styles", + "version": "4.3.0" + } + }, + "color-convert": { + "link": "../../~npm~color-convert@2.0.1/node_modules/color-convert" + } + } + }, + { + "id": "~npm~anymatch@3.1.3", + "node_modules": { + "anymatch": { + "pkg": { + "name": "anymatch", + "version": "3.1.3" + } + }, + "normalize-path": { + "link": "../../~npm~normalize-path@3.0.0/node_modules/normalize-path" + }, + "picomatch": { + "link": "../../~npm~picomatch@2.3.2/node_modules/picomatch" + } + } + }, + { + "id": "~npm~append-transform@2.0.0", + "node_modules": { + "append-transform": { + "pkg": { + "name": "append-transform", + "version": "2.0.0" + } + }, + "default-require-extensions": { + "link": "../../~npm~default-require-extensions@3.0.1/node_modules/default-require-extensions" + } + } + }, + { + "id": "~npm~archy@1.0.0", + "node_modules": { + "archy": { + "pkg": { + "name": "archy", + "version": "1.0.0" + } + } + } + }, + { + "id": "~npm~argparse@1.0.10", + "node_modules": { + "argparse": { + "pkg": { + "name": "argparse", + "version": "1.0.10" + } + }, + "sprintf-js": { + "link": "../../~npm~sprintf-js@1.0.3/node_modules/sprintf-js" + } + } + }, + { + "id": "~npm~asn1@0.2.6", + "node_modules": { + "asn1": { + "pkg": { + "name": "asn1", + "version": "0.2.6" + } + }, + "safer-buffer": { + "link": "../../~npm~safer-buffer@2.1.2/node_modules/safer-buffer" + } + } + }, + { + "id": "~npm~assert-plus@1.0.0", + "node_modules": { + "assert-plus": { + "pkg": { + "name": "assert-plus", + "version": "1.0.0" + } + } + } + }, + { + "id": "~npm~async-hook-domain@2.0.4", + "node_modules": { + "async-hook-domain": { + "pkg": { + "name": "async-hook-domain", + "version": "2.0.4" + } + } + } + }, + { + "id": "~npm~asynckit@0.4.0", + "node_modules": { + "asynckit": { + "pkg": { + "name": "asynckit", + "version": "0.4.0" + } + } + } + }, + { + "id": "~npm~aws-sign2@0.7.0", + "node_modules": { + "aws-sign2": { + "pkg": { + "name": "aws-sign2", + "version": "0.7.0" + } + } + } + }, + { + "id": "~npm~aws4@1.13.2", + "node_modules": { + "aws4": { + "pkg": { + "name": "aws4", + "version": "1.13.2" + } + } + } + }, + { + "id": "~npm~balanced-match@1.0.2", + "node_modules": { + "balanced-match": { + "pkg": { + "name": "balanced-match", + "version": "1.0.2" + } + } + } + }, + { + "id": "~npm~baseline-browser-mapping@2.11.26", + "node_modules": { + "baseline-browser-mapping": { + "pkg": { + "name": "baseline-browser-mapping", + "version": "2.11.26" + } + } + } + }, + { + "id": "~npm~bcrypt-pbkdf@1.0.2", + "node_modules": { + "bcrypt-pbkdf": { + "pkg": { + "name": "bcrypt-pbkdf", + "version": "1.0.2" + } + }, + "tweetnacl": { + "link": "../../~npm~tweetnacl@0.14.5/node_modules/tweetnacl" + } + } + }, + { + "id": "~npm~binary-extensions@2.3.0", + "node_modules": { + "binary-extensions": { + "pkg": { + "name": "binary-extensions", + "version": "2.3.0" + } + } + } + }, + { + "id": "~npm~bind-obj-methods@3.0.0", + "node_modules": { + "bind-obj-methods": { + "pkg": { + "name": "bind-obj-methods", + "version": "3.0.0" + } + } + } + }, + { + "id": "~npm~brace-expansion@1.1.21", + "node_modules": { + "balanced-match": { + "link": "../../~npm~balanced-match@1.0.2/node_modules/balanced-match" + }, + "brace-expansion": { + "pkg": { + "name": "brace-expansion", + "version": "1.1.21" + } + }, + "concat-map": { + "link": "../../~npm~concat-map@0.0.1/node_modules/concat-map" + } + } + }, + { + "id": "~npm~braces@3.0.3", + "node_modules": { + "braces": { + "pkg": { + "name": "braces", + "version": "3.0.3" + } + }, + "fill-range": { + "link": "../../~npm~fill-range@7.1.1/node_modules/fill-range" + } + } + }, + { + "id": "~npm~browserslist@4.29.1", + "node_modules": { + ".bin": { + "dir": true + }, + "baseline-browser-mapping": { + "link": "../../~npm~baseline-browser-mapping@2.11.26/node_modules/baseline-browser-mapping" + }, + "browserslist": { + "pkg": { + "name": "browserslist", + "version": "4.29.1" + } + }, + "caniuse-lite": { + "link": "../../~npm~caniuse-lite@1.0.30001812/node_modules/caniuse-lite" + }, + "electron-to-chromium": { + "link": "../../~npm~electron-to-chromium@1.5.439/node_modules/electron-to-chromium" + }, + "node-releases": { + "link": "../../~npm~node-releases@2.0.57/node_modules/node-releases" + }, + "update-browserslist-db": { + "link": "../../~npm~update-browserslist-db@1.3.3~peer.27c7ade2f48570ae/node_modules/update-browserslist-db" + } + } + }, + { + "id": "~npm~buffer-from@1.1.2", + "node_modules": { + "buffer-from": { + "pkg": { + "name": "buffer-from", + "version": "1.1.2" + } + } + } + }, + { + "id": "~npm~caching-transform@4.0.0", + "node_modules": { + "caching-transform": { + "pkg": { + "name": "caching-transform", + "version": "4.0.0" + } + }, + "hasha": { + "link": "../../~npm~hasha@5.2.2/node_modules/hasha" + }, + "make-dir": { + "link": "../../~npm~make-dir@3.1.0/node_modules/make-dir" + }, + "package-hash": { + "link": "../../~npm~package-hash@4.0.0/node_modules/package-hash" + }, + "write-file-atomic": { + "link": "../../~npm~write-file-atomic@3.0.3/node_modules/write-file-atomic" + } + } + }, + { + "id": "~npm~camelcase@5.3.1", + "node_modules": { + "camelcase": { + "pkg": { + "name": "camelcase", + "version": "5.3.1" + } + } + } + }, + { + "id": "~npm~caniuse-lite@1.0.30001812", + "node_modules": { + "caniuse-lite": { + "pkg": { + "name": "caniuse-lite", + "version": "1.0.30001812" + } + } + } + }, + { + "id": "~npm~caseless@0.12.0", + "node_modules": { + "caseless": { + "pkg": { + "name": "caseless", + "version": "0.12.0" + } + } + } + }, + { + "id": "~npm~chokidar@3.6.0", + "node_modules": { + "anymatch": { + "link": "../../~npm~anymatch@3.1.3/node_modules/anymatch" + }, + "braces": { + "link": "../../~npm~braces@3.0.3/node_modules/braces" + }, + "chokidar": { + "pkg": { + "name": "chokidar", + "version": "3.6.0" + } + }, + "fsevents": { + "link": "../../~npm~fsevents@2.3.3/node_modules/fsevents" + }, + "glob-parent": { + "link": "../../~npm~glob-parent@5.1.2/node_modules/glob-parent" + }, + "is-binary-path": { + "link": "../../~npm~is-binary-path@2.1.0/node_modules/is-binary-path" + }, + "is-glob": { + "link": "../../~npm~is-glob@4.0.3/node_modules/is-glob" + }, + "normalize-path": { + "link": "../../~npm~normalize-path@3.0.0/node_modules/normalize-path" + }, + "readdirp": { + "link": "../../~npm~readdirp@3.6.0/node_modules/readdirp" + } + } + }, + { + "id": "~npm~clean-stack@2.2.0", + "node_modules": { + "clean-stack": { + "pkg": { + "name": "clean-stack", + "version": "2.2.0" + } + } + } + }, + { + "id": "~npm~cliui@6.0.0", + "node_modules": { + "cliui": { + "pkg": { + "name": "cliui", + "version": "6.0.0" + } + }, + "string-width": { + "link": "../../~npm~string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../../~npm~strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "wrap-ansi": { + "link": "../../~npm~wrap-ansi@6.2.0/node_modules/wrap-ansi" + } + } + }, + { + "id": "~npm~cliui@7.0.4", + "node_modules": { + "cliui": { + "pkg": { + "name": "cliui", + "version": "7.0.4" + } + }, + "string-width": { + "link": "../../~npm~string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../../~npm~strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "wrap-ansi": { + "link": "../../~npm~wrap-ansi@7.0.0/node_modules/wrap-ansi" + } + } + }, + { + "id": "~npm~color-convert@2.0.1", + "node_modules": { + "color-convert": { + "pkg": { + "name": "color-convert", + "version": "2.0.1" + } + }, + "color-name": { + "link": "../../~npm~color-name@1.1.4/node_modules/color-name" + } + } + }, + { + "id": "~npm~color-name@1.1.4", + "node_modules": { + "color-name": { + "pkg": { + "name": "color-name", + "version": "1.1.4" + } + } + } + }, + { + "id": "~npm~color-support@1.1.3", + "node_modules": { + "color-support": { + "pkg": { + "name": "color-support", + "version": "1.1.3" + } + } + } + }, + { + "id": "~npm~combined-stream@1.0.8", + "node_modules": { + "combined-stream": { + "pkg": { + "name": "combined-stream", + "version": "1.0.8" + } + }, + "delayed-stream": { + "link": "../../~npm~delayed-stream@1.0.0/node_modules/delayed-stream" + } + } + }, + { + "id": "~npm~commondir@1.0.1", + "node_modules": { + "commondir": { + "pkg": { + "name": "commondir", + "version": "1.0.1" + } + } + } + }, + { + "id": "~npm~concat-map@0.0.1", + "node_modules": { + "concat-map": { + "pkg": { + "name": "concat-map", + "version": "0.0.1" + } + } + } + }, + { + "id": "~npm~convert-source-map@1.9.0", + "node_modules": { + "convert-source-map": { + "pkg": { + "name": "convert-source-map", + "version": "1.9.0" + } + } + } + }, + { + "id": "~npm~convert-source-map@2.0.0", + "node_modules": { + "convert-source-map": { + "pkg": { + "name": "convert-source-map", + "version": "2.0.0" + } + } + } + }, + { + "id": "~npm~core-util-is@1.0.2", + "node_modules": { + "core-util-is": { + "pkg": { + "name": "core-util-is", + "version": "1.0.2" + } + } + } + }, + { + "id": "~npm~coveralls@3.1.1", + "node_modules": { + ".bin": { + "dir": true + }, + "coveralls": { + "pkg": { + "name": "coveralls", + "version": "3.1.1" + } + }, + "js-yaml": { + "link": "../../~npm~js-yaml@3.15.2/node_modules/js-yaml" + }, + "lcov-parse": { + "link": "../../~npm~lcov-parse@1.0.0/node_modules/lcov-parse" + }, + "log-driver": { + "link": "../../~npm~log-driver@1.2.7/node_modules/log-driver" + }, + "minimist": { + "link": "../../~npm~minimist@1.2.8/node_modules/minimist" + }, + "request": { + "link": "../../~npm~request@2.88.2/node_modules/request" + } + } + }, + { + "id": "~npm~cross-spawn@7.0.6", + "node_modules": { + ".bin": { + "dir": true + }, + "cross-spawn": { + "pkg": { + "name": "cross-spawn", + "version": "7.0.6" + } + }, + "path-key": { + "link": "../../~npm~path-key@3.1.1/node_modules/path-key" + }, + "shebang-command": { + "link": "../../~npm~shebang-command@2.0.0/node_modules/shebang-command" + }, + "which": { + "link": "../../~npm~which@2.0.2/node_modules/which" + } + } + }, + { + "id": "~npm~dashdash@1.14.1", + "node_modules": { + "assert-plus": { + "link": "../../~npm~assert-plus@1.0.0/node_modules/assert-plus" + }, + "dashdash": { + "pkg": { + "name": "dashdash", + "version": "1.14.1" + } + } + } + }, + { + "id": "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "node_modules": { + "debug": { + "pkg": { + "name": "debug", + "version": "4.3.4" + } + }, + "ms": { + "link": "../../~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms/node_modules/ms" + } + } + }, + { + "id": "~npm~decamelize@1.2.0", + "node_modules": { + "decamelize": { + "pkg": { + "name": "decamelize", + "version": "1.2.0" + } + } + } + }, + { + "id": "~npm~default-require-extensions@3.0.1", + "node_modules": { + "default-require-extensions": { + "pkg": { + "name": "default-require-extensions", + "version": "3.0.1" + } + }, + "strip-bom": { + "link": "../../~npm~strip-bom@4.0.0/node_modules/strip-bom" + } + } + }, + { + "id": "~npm~delayed-stream@1.0.0", + "node_modules": { + "delayed-stream": { + "pkg": { + "name": "delayed-stream", + "version": "1.0.0" + } + } + } + }, + { + "id": "~npm~diff@4.0.4", + "node_modules": { + "diff": { + "pkg": { + "name": "diff", + "version": "4.0.4" + } + } + } + }, + { + "id": "~npm~ecc-jsbn@0.1.2", + "node_modules": { + "ecc-jsbn": { + "pkg": { + "name": "ecc-jsbn", + "version": "0.1.2" + } + }, + "jsbn": { + "link": "../../~npm~jsbn@0.1.1/node_modules/jsbn" + }, + "safer-buffer": { + "link": "../../~npm~safer-buffer@2.1.2/node_modules/safer-buffer" + } + } + }, + { + "id": "~npm~electron-to-chromium@1.5.439", + "node_modules": { + "electron-to-chromium": { + "pkg": { + "name": "electron-to-chromium", + "version": "1.5.439" + } + } + } + }, + { + "id": "~npm~emoji-regex@8.0.0", + "node_modules": { + "emoji-regex": { + "pkg": { + "name": "emoji-regex", + "version": "8.0.0" + } + } + } + }, + { + "id": "~npm~es6-error@4.1.1", + "node_modules": { + "es6-error": { + "pkg": { + "name": "es6-error", + "version": "4.1.1" + } + } + } + }, + { + "id": "~npm~escalade@3.2.0", + "node_modules": { + "escalade": { + "pkg": { + "name": "escalade", + "version": "3.2.0" + } + } + } + }, + { + "id": "~npm~escape-string-regexp@2.0.0", + "node_modules": { + "escape-string-regexp": { + "pkg": { + "name": "escape-string-regexp", + "version": "2.0.0" + } + } + } + }, + { + "id": "~npm~esprima@4.0.1", + "node_modules": { + "esprima": { + "pkg": { + "name": "esprima", + "version": "4.0.1" + } + } + } + }, + { + "id": "~npm~events-to-array@1.1.2", + "node_modules": { + "events-to-array": { + "pkg": { + "name": "events-to-array", + "version": "1.1.2" + } + } + } + }, + { + "id": "~npm~extend@3.0.2", + "node_modules": { + "extend": { + "pkg": { + "name": "extend", + "version": "3.0.2" + } + } + } + }, + { + "id": "~npm~extsprintf@1.3.0", + "node_modules": { + "extsprintf": { + "pkg": { + "name": "extsprintf", + "version": "1.3.0" + } + } + } + }, + { + "id": "~npm~fast-deep-equal@3.1.3", + "node_modules": { + "fast-deep-equal": { + "pkg": { + "name": "fast-deep-equal", + "version": "3.1.3" + } + } + } + }, + { + "id": "~npm~fast-json-stable-stringify@2.1.0", + "node_modules": { + "fast-json-stable-stringify": { + "pkg": { + "name": "fast-json-stable-stringify", + "version": "2.1.0" + } + } + } + }, + { + "id": "~npm~fill-range@7.1.1", + "node_modules": { + "fill-range": { + "pkg": { + "name": "fill-range", + "version": "7.1.1" + } + }, + "to-regex-range": { + "link": "../../~npm~to-regex-range@5.0.1/node_modules/to-regex-range" + } + } + }, + { + "id": "~npm~find-cache-dir@3.3.2", + "node_modules": { + "commondir": { + "link": "../../~npm~commondir@1.0.1/node_modules/commondir" + }, + "find-cache-dir": { + "pkg": { + "name": "find-cache-dir", + "version": "3.3.2" + } + }, + "make-dir": { + "link": "../../~npm~make-dir@3.1.0/node_modules/make-dir" + }, + "pkg-dir": { + "link": "../../~npm~pkg-dir@4.2.0/node_modules/pkg-dir" + } + } + }, + { + "id": "~npm~find-up@4.1.0", + "node_modules": { + "find-up": { + "pkg": { + "name": "find-up", + "version": "4.1.0" + } + }, + "locate-path": { + "link": "../../~npm~locate-path@5.0.0/node_modules/locate-path" + }, + "path-exists": { + "link": "../../~npm~path-exists@4.0.0/node_modules/path-exists" + } + } + }, + { + "id": "~npm~findit@2.0.0", + "node_modules": { + "findit": { + "pkg": { + "name": "findit", + "version": "2.0.0" + } + } + } + }, + { + "id": "~npm~foreground-child@2.0.0", + "node_modules": { + "cross-spawn": { + "link": "../../~npm~cross-spawn@7.0.6/node_modules/cross-spawn" + }, + "foreground-child": { + "pkg": { + "name": "foreground-child", + "version": "2.0.0" + } + }, + "signal-exit": { + "link": "../../~npm~signal-exit@3.0.7/node_modules/signal-exit" + } + } + }, + { + "id": "~npm~forever-agent@0.6.1", + "node_modules": { + "forever-agent": { + "pkg": { + "name": "forever-agent", + "version": "0.6.1" + } + } + } + }, + { + "id": "~npm~form-data@2.3.3", + "node_modules": { + "asynckit": { + "link": "../../~npm~asynckit@0.4.0/node_modules/asynckit" + }, + "combined-stream": { + "link": "../../~npm~combined-stream@1.0.8/node_modules/combined-stream" + }, + "form-data": { + "pkg": { + "name": "form-data", + "version": "2.3.3" + } + }, + "mime-types": { + "link": "../../~npm~mime-types@2.1.35/node_modules/mime-types" + } + } + }, + { + "id": "~npm~fromentries@1.3.2", + "node_modules": { + "fromentries": { + "pkg": { + "name": "fromentries", + "version": "1.3.2" + } + } + } + }, + { + "id": "~npm~fs-exists-cached@1.0.0", + "node_modules": { + "fs-exists-cached": { + "pkg": { + "name": "fs-exists-cached", + "version": "1.0.0" + } + } + } + }, + { + "id": "~npm~fs.realpath@1.0.0", + "node_modules": { + "fs.realpath": { + "pkg": { + "name": "fs.realpath", + "version": "1.0.0" + } + } + } + }, + { + "id": "~npm~fsevents@2.3.3", + "node_modules": { + "fsevents": { + "pkg": { + "name": "fsevents", + "version": "2.3.3" + } + } + } + }, + { + "id": "~npm~function-loop@2.0.1", + "node_modules": { + "function-loop": { + "pkg": { + "name": "function-loop", + "version": "2.0.1" + } + } + } + }, + { + "id": "~npm~gensync@1.0.0-beta.2", + "node_modules": { + "gensync": { + "pkg": { + "name": "gensync", + "version": "1.0.0-beta.2" + } + } + } + }, + { + "id": "~npm~get-caller-file@2.0.5", + "node_modules": { + "get-caller-file": { + "pkg": { + "name": "get-caller-file", + "version": "2.0.5" + } + } + } + }, + { + "id": "~npm~get-package-type@0.1.0", + "node_modules": { + "get-package-type": { + "pkg": { + "name": "get-package-type", + "version": "0.1.0" + } + } + } + }, + { + "id": "~npm~getpass@0.1.7", + "node_modules": { + "assert-plus": { + "link": "../../~npm~assert-plus@1.0.0/node_modules/assert-plus" + }, + "getpass": { + "pkg": { + "name": "getpass", + "version": "0.1.7" + } + } + } + }, + { + "id": "~npm~glob-parent@5.1.2", + "node_modules": { + "glob-parent": { + "pkg": { + "name": "glob-parent", + "version": "5.1.2" + } + }, + "is-glob": { + "link": "../../~npm~is-glob@4.0.3/node_modules/is-glob" + } + } + }, + { + "id": "~npm~glob@7.2.3", + "node_modules": { + "fs.realpath": { + "link": "../../~npm~fs.realpath@1.0.0/node_modules/fs.realpath" + }, + "glob": { + "pkg": { + "name": "glob", + "version": "7.2.3" + } + }, + "inflight": { + "link": "../../~npm~inflight@1.0.6/node_modules/inflight" + }, + "inherits": { + "link": "../../~npm~inherits@2.0.4/node_modules/inherits" + }, + "minimatch": { + "link": "../../~npm~minimatch@3.1.5/node_modules/minimatch" + }, + "once": { + "link": "../../~npm~once@1.4.0/node_modules/once" + }, + "path-is-absolute": { + "link": "../../~npm~path-is-absolute@1.0.1/node_modules/path-is-absolute" + } + } + }, + { + "id": "~npm~graceful-fs@4.2.11", + "node_modules": { + "graceful-fs": { + "pkg": { + "name": "graceful-fs", + "version": "4.2.11" + } + } + } + }, + { + "id": "~npm~har-schema@2.0.0", + "node_modules": { + "har-schema": { + "pkg": { + "name": "har-schema", + "version": "2.0.0" + } + } + } + }, + { + "id": "~npm~har-validator@5.1.5", + "node_modules": { + "ajv": { + "link": "../../~npm~ajv@6.15.0/node_modules/ajv" + }, + "har-schema": { + "link": "../../~npm~har-schema@2.0.0/node_modules/har-schema" + }, + "har-validator": { + "pkg": { + "name": "har-validator", + "version": "5.1.5" + } + } + } + }, + { + "id": "~npm~has-flag@4.0.0", + "node_modules": { + "has-flag": { + "pkg": { + "name": "has-flag", + "version": "4.0.0" + } + } + } + }, + { + "id": "~npm~hasha@5.2.2", + "node_modules": { + "hasha": { + "pkg": { + "name": "hasha", + "version": "5.2.2" + } + }, + "is-stream": { + "link": "../../~npm~is-stream@2.0.1/node_modules/is-stream" + }, + "type-fest": { + "link": "../../~npm~type-fest@0.8.1/node_modules/type-fest" + } + } + }, + { + "id": "~npm~html-escaper@2.0.2", + "node_modules": { + "html-escaper": { + "pkg": { + "name": "html-escaper", + "version": "2.0.2" + } + } + } + }, + { + "id": "~npm~http-signature@1.2.0", + "node_modules": { + ".bin": { + "dir": true + }, + "assert-plus": { + "link": "../../~npm~assert-plus@1.0.0/node_modules/assert-plus" + }, + "http-signature": { + "pkg": { + "name": "http-signature", + "version": "1.2.0" + } + }, + "jsprim": { + "link": "../../~npm~jsprim@1.4.2/node_modules/jsprim" + }, + "sshpk": { + "link": "../../~npm~sshpk@1.18.0/node_modules/sshpk" + } + } + }, + { + "id": "~npm~imurmurhash@0.1.4", + "node_modules": { + "imurmurhash": { + "pkg": { + "name": "imurmurhash", + "version": "0.1.4" + } + } + } + }, + { + "id": "~npm~indent-string@4.0.0", + "node_modules": { + "indent-string": { + "pkg": { + "name": "indent-string", + "version": "4.0.0" + } + } + } + }, + { + "id": "~npm~inflight@1.0.6", + "node_modules": { + "inflight": { + "pkg": { + "name": "inflight", + "version": "1.0.6" + } + }, + "once": { + "link": "../../~npm~once@1.4.0/node_modules/once" + }, + "wrappy": { + "link": "../../~npm~wrappy@1.0.2/node_modules/wrappy" + } + } + }, + { + "id": "~npm~inherits@2.0.4", + "node_modules": { + "inherits": { + "pkg": { + "name": "inherits", + "version": "2.0.4" + } + } + } + }, + { + "id": "~npm~is-binary-path@2.1.0", + "node_modules": { + "binary-extensions": { + "link": "../../~npm~binary-extensions@2.3.0/node_modules/binary-extensions" + }, + "is-binary-path": { + "pkg": { + "name": "is-binary-path", + "version": "2.1.0" + } + } + } + }, + { + "id": "~npm~is-extglob@2.1.1", + "node_modules": { + "is-extglob": { + "pkg": { + "name": "is-extglob", + "version": "2.1.1" + } + } + } + }, + { + "id": "~npm~is-fullwidth-code-point@3.0.0", + "node_modules": { + "is-fullwidth-code-point": { + "pkg": { + "name": "is-fullwidth-code-point", + "version": "3.0.0" + } + } + } + }, + { + "id": "~npm~is-glob@4.0.3", + "node_modules": { + "is-extglob": { + "link": "../../~npm~is-extglob@2.1.1/node_modules/is-extglob" + }, + "is-glob": { + "pkg": { + "name": "is-glob", + "version": "4.0.3" + } + } + } + }, + { + "id": "~npm~is-number@7.0.0", + "node_modules": { + "is-number": { + "pkg": { + "name": "is-number", + "version": "7.0.0" + } + } + } + }, + { + "id": "~npm~is-stream@2.0.1", + "node_modules": { + "is-stream": { + "pkg": { + "name": "is-stream", + "version": "2.0.1" + } + } + } + }, + { + "id": "~npm~is-typedarray@1.0.0", + "node_modules": { + "is-typedarray": { + "pkg": { + "name": "is-typedarray", + "version": "1.0.0" + } + } + } + }, + { + "id": "~npm~is-windows@1.0.2", + "node_modules": { + "is-windows": { + "pkg": { + "name": "is-windows", + "version": "1.0.2" + } + } + } + }, + { + "id": "~npm~isexe@2.0.0", + "node_modules": { + "isexe": { + "pkg": { + "name": "isexe", + "version": "2.0.0" + } + } + } + }, + { + "id": "~npm~isstream@0.1.2", + "node_modules": { + "isstream": { + "pkg": { + "name": "isstream", + "version": "0.1.2" + } + } + } + }, + { + "id": "~npm~istanbul-lib-coverage@3.2.2", + "node_modules": { + "istanbul-lib-coverage": { + "pkg": { + "name": "istanbul-lib-coverage", + "version": "3.2.2" + } + } + } + }, + { + "id": "~npm~istanbul-lib-hook@3.0.0", + "node_modules": { + "append-transform": { + "link": "../../~npm~append-transform@2.0.0/node_modules/append-transform" + }, + "istanbul-lib-hook": { + "pkg": { + "name": "istanbul-lib-hook", + "version": "3.0.0" + } + } + } + }, + { + "id": "~npm~istanbul-lib-instrument@4.0.3", + "node_modules": { + ".bin": { + "dir": true + }, + "@babel/core": { + "link": "../../../~npm~@babel+core@7.29.7/node_modules/@babel/core" + }, + "@istanbuljs/schema": { + "link": "../../../~npm~@istanbuljs+schema@0.1.6/node_modules/@istanbuljs/schema" + }, + "istanbul-lib-coverage": { + "link": "../../~npm~istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-instrument": { + "pkg": { + "name": "istanbul-lib-instrument", + "version": "4.0.3" + } + }, + "semver": { + "link": "../../~npm~semver@6.3.1/node_modules/semver" + } + } + }, + { + "id": "~npm~istanbul-lib-processinfo@2.0.3", + "node_modules": { + ".bin": { + "dir": true + }, + "archy": { + "link": "../../~npm~archy@1.0.0/node_modules/archy" + }, + "cross-spawn": { + "link": "../../~npm~cross-spawn@7.0.6/node_modules/cross-spawn" + }, + "istanbul-lib-coverage": { + "link": "../../~npm~istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-processinfo": { + "pkg": { + "name": "istanbul-lib-processinfo", + "version": "2.0.3" + } + }, + "p-map": { + "link": "../../~npm~p-map@3.0.0/node_modules/p-map" + }, + "rimraf": { + "link": "../../~npm~rimraf@3.0.2/node_modules/rimraf" + }, + "uuid": { + "link": "../../~npm~uuid@8.3.2/node_modules/uuid" + } + } + }, + { + "id": "~npm~istanbul-lib-report@3.0.1", + "node_modules": { + "istanbul-lib-coverage": { + "link": "../../~npm~istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-report": { + "pkg": { + "name": "istanbul-lib-report", + "version": "3.0.1" + } + }, + "make-dir": { + "link": "../../~npm~make-dir@4.0.0/node_modules/make-dir" + }, + "supports-color": { + "link": "../../~npm~supports-color@7.2.0/node_modules/supports-color" + } + } + }, + { + "id": "~npm~istanbul-lib-source-maps@4.0.1", + "node_modules": { + "debug": { + "link": "../../~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms/node_modules/debug" + }, + "istanbul-lib-coverage": { + "link": "../../~npm~istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-source-maps": { + "pkg": { + "name": "istanbul-lib-source-maps", + "version": "4.0.1" + } + }, + "source-map": { + "link": "../../~npm~source-map@0.6.1/node_modules/source-map" + } + } + }, + { + "id": "~npm~istanbul-reports@3.2.0", + "node_modules": { + "html-escaper": { + "link": "../../~npm~html-escaper@2.0.2/node_modules/html-escaper" + }, + "istanbul-lib-report": { + "link": "../../~npm~istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report" + }, + "istanbul-reports": { + "pkg": { + "name": "istanbul-reports", + "version": "3.2.0" + } + } + } + }, + { + "id": "~npm~jackspeak@1.4.2", + "node_modules": { + "cliui": { + "link": "../../~npm~cliui@7.0.4/node_modules/cliui" + }, + "jackspeak": { + "pkg": { + "name": "jackspeak", + "version": "1.4.2" + } + } + } + }, + { + "id": "~npm~js-tokens@4.0.0", + "node_modules": { + "js-tokens": { + "pkg": { + "name": "js-tokens", + "version": "4.0.0" + } + } + } + }, + { + "id": "~npm~js-yaml@3.15.2", + "node_modules": { + ".bin": { + "dir": true + }, + "argparse": { + "link": "../../~npm~argparse@1.0.10/node_modules/argparse" + }, + "esprima": { + "link": "../../~npm~esprima@4.0.1/node_modules/esprima" + }, + "js-yaml": { + "pkg": { + "name": "js-yaml", + "version": "3.15.2" + } + } + } + }, + { + "id": "~npm~jsbn@0.1.1", + "node_modules": { + "jsbn": { + "pkg": { + "name": "jsbn", + "version": "0.1.1" + } + } + } + }, + { + "id": "~npm~jsesc@3.1.0", + "node_modules": { + "jsesc": { + "pkg": { + "name": "jsesc", + "version": "3.1.0" + } + } + } + }, + { + "id": "~npm~json-schema-traverse@0.4.1", + "node_modules": { + "json-schema-traverse": { + "pkg": { + "name": "json-schema-traverse", + "version": "0.4.1" + } + } + } + }, + { + "id": "~npm~json-schema@0.4.0", + "node_modules": { + "json-schema": { + "pkg": { + "name": "json-schema", + "version": "0.4.0" + } + } + } + }, + { + "id": "~npm~json-stringify-safe@5.0.1", + "node_modules": { + "json-stringify-safe": { + "pkg": { + "name": "json-stringify-safe", + "version": "5.0.1" + } + } + } + }, + { + "id": "~npm~json5@2.2.3", + "node_modules": { + "json5": { + "pkg": { + "name": "json5", + "version": "2.2.3" + } + } + } + }, + { + "id": "~npm~jsprim@1.4.2", + "node_modules": { + "assert-plus": { + "link": "../../~npm~assert-plus@1.0.0/node_modules/assert-plus" + }, + "extsprintf": { + "link": "../../~npm~extsprintf@1.3.0/node_modules/extsprintf" + }, + "json-schema": { + "link": "../../~npm~json-schema@0.4.0/node_modules/json-schema" + }, + "jsprim": { + "pkg": { + "name": "jsprim", + "version": "1.4.2" + } + }, + "verror": { + "link": "../../~npm~verror@1.10.0/node_modules/verror" + } + } + }, + { + "id": "~npm~lcov-parse@1.0.0", + "node_modules": { + "lcov-parse": { + "pkg": { + "name": "lcov-parse", + "version": "1.0.0" + } + } + } + }, + { + "id": "~npm~left-pad@1.1.3", + "node_modules": { + "left-pad": { + "pkg": { + "name": "left-pad", + "version": "1.1.3" + } + } + } + }, + { + "id": "~npm~left-pad@1.3.0", + "node_modules": { + "left-pad": { + "pkg": { + "name": "left-pad", + "version": "1.3.0" + } + } + } + }, + { + "id": "~npm~libtap@1.4.1", + "node_modules": { + ".bin": { + "dir": true + }, + "async-hook-domain": { + "link": "../../~npm~async-hook-domain@2.0.4/node_modules/async-hook-domain" + }, + "bind-obj-methods": { + "link": "../../~npm~bind-obj-methods@3.0.0/node_modules/bind-obj-methods" + }, + "diff": { + "link": "../../~npm~diff@4.0.4/node_modules/diff" + }, + "function-loop": { + "link": "../../~npm~function-loop@2.0.1/node_modules/function-loop" + }, + "libtap": { + "pkg": { + "name": "libtap", + "version": "1.4.1" + } + }, + "minipass": { + "link": "../../~npm~minipass@3.3.6/node_modules/minipass" + }, + "own-or": { + "link": "../../~npm~own-or@1.0.0/node_modules/own-or" + }, + "own-or-env": { + "link": "../../~npm~own-or-env@1.0.2/node_modules/own-or-env" + }, + "signal-exit": { + "link": "../../~npm~signal-exit@3.0.7/node_modules/signal-exit" + }, + "stack-utils": { + "link": "../../~npm~stack-utils@2.0.6/node_modules/stack-utils" + }, + "tap-parser": { + "link": "../../~npm~tap-parser@11.0.2/node_modules/tap-parser" + }, + "tap-yaml": { + "link": "../../~npm~tap-yaml@1.0.2/node_modules/tap-yaml" + }, + "tcompare": { + "link": "../../~npm~tcompare@5.0.7/node_modules/tcompare" + }, + "trivial-deferred": { + "link": "../../~npm~trivial-deferred@1.1.2/node_modules/trivial-deferred" + } + } + }, + { + "id": "~npm~locate-path@5.0.0", + "node_modules": { + "locate-path": { + "pkg": { + "name": "locate-path", + "version": "5.0.0" + } + }, + "p-locate": { + "link": "../../~npm~p-locate@4.1.0/node_modules/p-locate" + } + } + }, + { + "id": "~npm~lodash.flattendeep@4.4.0", + "node_modules": { + "lodash.flattendeep": { + "pkg": { + "name": "lodash.flattendeep", + "version": "4.4.0" + } + } + } + }, + { + "id": "~npm~log-driver@1.2.7", + "node_modules": { + "log-driver": { + "pkg": { + "name": "log-driver", + "version": "1.2.7" + } + } + } + }, + { + "id": "~npm~loose-envify@1.4.0", + "node_modules": { + "js-tokens": { + "link": "../../~npm~js-tokens@4.0.0/node_modules/js-tokens" + }, + "loose-envify": { + "pkg": { + "name": "loose-envify", + "version": "1.4.0" + } + } + } + }, + { + "id": "~npm~lru-cache@5.1.1", + "node_modules": { + "lru-cache": { + "pkg": { + "name": "lru-cache", + "version": "5.1.1" + } + }, + "yallist": { + "link": "../../~npm~yallist@3.1.1/node_modules/yallist" + } + } + }, + { + "id": "~npm~lru-cache@6.0.0", + "node_modules": { + "lru-cache": { + "pkg": { + "name": "lru-cache", + "version": "6.0.0" + } + }, + "yallist": { + "link": "../../~npm~yallist@4.0.0/node_modules/yallist" + } + } + }, + { + "id": "~npm~make-dir@3.1.0", + "node_modules": { + ".bin": { + "dir": true + }, + "make-dir": { + "pkg": { + "name": "make-dir", + "version": "3.1.0" + } + }, + "semver": { + "link": "../../~npm~semver@6.3.1/node_modules/semver" + } + } + }, + { + "id": "~npm~make-dir@4.0.0", + "node_modules": { + ".bin": { + "dir": true + }, + "make-dir": { + "pkg": { + "name": "make-dir", + "version": "4.0.0" + } + }, + "semver": { + "link": "../../~npm~semver@7.6.0/node_modules/semver" + } + } + }, + { + "id": "~npm~mime-db@1.52.0", + "node_modules": { + "mime-db": { + "pkg": { + "name": "mime-db", + "version": "1.52.0" + } + } + } + }, + { + "id": "~npm~mime-types@2.1.35", + "node_modules": { + "mime-db": { + "link": "../../~npm~mime-db@1.52.0/node_modules/mime-db" + }, + "mime-types": { + "pkg": { + "name": "mime-types", + "version": "2.1.35" + } + } + } + }, + { + "id": "~npm~minimatch@3.1.5", + "node_modules": { + "brace-expansion": { + "link": "../../~npm~brace-expansion@1.1.21/node_modules/brace-expansion" + }, + "minimatch": { + "pkg": { + "name": "minimatch", + "version": "3.1.5" + } + } + } + }, + { + "id": "~npm~minimist@1.2.8", + "node_modules": { + "minimist": { + "pkg": { + "name": "minimist", + "version": "1.2.8" + } + } + } + }, + { + "id": "~npm~minipass@3.3.6", + "node_modules": { + "minipass": { + "pkg": { + "name": "minipass", + "version": "3.3.6" + } + }, + "yallist": { + "link": "../../~npm~yallist@4.0.0/node_modules/yallist" + } + } + }, + { + "id": "~npm~mkdirp@1.0.4", + "node_modules": { + "mkdirp": { + "pkg": { + "name": "mkdirp", + "version": "1.0.4" + } + } + } + }, + { + "id": "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "node_modules": { + "ms": { + "pkg": { + "name": "ms", + "version": "2.1.3" + } + } + } + }, + { + "id": "~npm~node-preload@0.2.1", + "node_modules": { + "node-preload": { + "pkg": { + "name": "node-preload", + "version": "0.2.1" + } + }, + "process-on-spawn": { + "link": "../../~npm~process-on-spawn@1.1.0/node_modules/process-on-spawn" + } + } + }, + { + "id": "~npm~node-releases@2.0.57", + "node_modules": { + "node-releases": { + "pkg": { + "name": "node-releases", + "version": "2.0.57" + } + } + } + }, + { + "id": "~npm~normalize-path@3.0.0", + "node_modules": { + "normalize-path": { + "pkg": { + "name": "normalize-path", + "version": "3.0.0" + } + } + } + }, + { + "id": "~npm~nyc@15.1.0", + "node_modules": { + ".bin": { + "dir": true + }, + "@istanbuljs/load-nyc-config": { + "link": "../../../~npm~@istanbuljs+load-nyc-config@1.1.0/node_modules/@istanbuljs/load-nyc-config" + }, + "@istanbuljs/schema": { + "link": "../../../~npm~@istanbuljs+schema@0.1.6/node_modules/@istanbuljs/schema" + }, + "caching-transform": { + "link": "../../~npm~caching-transform@4.0.0/node_modules/caching-transform" + }, + "convert-source-map": { + "link": "../../~npm~convert-source-map@1.9.0/node_modules/convert-source-map" + }, + "decamelize": { + "link": "../../~npm~decamelize@1.2.0/node_modules/decamelize" + }, + "find-cache-dir": { + "link": "../../~npm~find-cache-dir@3.3.2/node_modules/find-cache-dir" + }, + "find-up": { + "link": "../../~npm~find-up@4.1.0/node_modules/find-up" + }, + "foreground-child": { + "link": "../../~npm~foreground-child@2.0.0/node_modules/foreground-child" + }, + "get-package-type": { + "link": "../../~npm~get-package-type@0.1.0/node_modules/get-package-type" + }, + "glob": { + "link": "../../~npm~glob@7.2.3/node_modules/glob" + }, + "istanbul-lib-coverage": { + "link": "../../~npm~istanbul-lib-coverage@3.2.2/node_modules/istanbul-lib-coverage" + }, + "istanbul-lib-hook": { + "link": "../../~npm~istanbul-lib-hook@3.0.0/node_modules/istanbul-lib-hook" + }, + "istanbul-lib-instrument": { + "link": "../../~npm~istanbul-lib-instrument@4.0.3/node_modules/istanbul-lib-instrument" + }, + "istanbul-lib-processinfo": { + "link": "../../~npm~istanbul-lib-processinfo@2.0.3/node_modules/istanbul-lib-processinfo" + }, + "istanbul-lib-report": { + "link": "../../~npm~istanbul-lib-report@3.0.1/node_modules/istanbul-lib-report" + }, + "istanbul-lib-source-maps": { + "link": "../../~npm~istanbul-lib-source-maps@4.0.1/node_modules/istanbul-lib-source-maps" + }, + "istanbul-reports": { + "link": "../../~npm~istanbul-reports@3.2.0/node_modules/istanbul-reports" + }, + "make-dir": { + "link": "../../~npm~make-dir@3.1.0/node_modules/make-dir" + }, + "node-preload": { + "link": "../../~npm~node-preload@0.2.1/node_modules/node-preload" + }, + "nyc": { + "pkg": { + "name": "nyc", + "version": "15.1.0" + } + }, + "p-map": { + "link": "../../~npm~p-map@3.0.0/node_modules/p-map" + }, + "process-on-spawn": { + "link": "../../~npm~process-on-spawn@1.1.0/node_modules/process-on-spawn" + }, + "resolve-from": { + "link": "../../~npm~resolve-from@5.0.0/node_modules/resolve-from" + }, + "rimraf": { + "link": "../../~npm~rimraf@3.0.2/node_modules/rimraf" + }, + "signal-exit": { + "link": "../../~npm~signal-exit@3.0.7/node_modules/signal-exit" + }, + "spawn-wrap": { + "link": "../../~npm~spawn-wrap@2.0.0/node_modules/spawn-wrap" + }, + "test-exclude": { + "link": "../../~npm~test-exclude@6.0.0/node_modules/test-exclude" + }, + "yargs": { + "link": "../../~npm~yargs@15.4.1/node_modules/yargs" + } + } + }, + { + "id": "~npm~oauth-sign@0.9.0", + "node_modules": { + "oauth-sign": { + "pkg": { + "name": "oauth-sign", + "version": "0.9.0" + } + } + } + }, + { + "id": "~npm~once@1.4.0", + "node_modules": { + "once": { + "pkg": { + "name": "once", + "version": "1.4.0" + } + }, + "wrappy": { + "link": "../../~npm~wrappy@1.0.2/node_modules/wrappy" + } + } + }, + { + "id": "~npm~opener@1.5.2", + "node_modules": { + "opener": { + "pkg": { + "name": "opener", + "version": "1.5.2" + } + } + } + }, + { + "id": "~npm~own-or-env@1.0.2", + "node_modules": { + "own-or": { + "link": "../../~npm~own-or@1.0.0/node_modules/own-or" + }, + "own-or-env": { + "pkg": { + "name": "own-or-env", + "version": "1.0.2" + } + } + } + }, + { + "id": "~npm~own-or@1.0.0", + "node_modules": { + "own-or": { + "pkg": { + "name": "own-or", + "version": "1.0.0" + } + } + } + }, + { + "id": "~npm~p-limit@2.3.0", + "node_modules": { + "p-limit": { + "pkg": { + "name": "p-limit", + "version": "2.3.0" + } + }, + "p-try": { + "link": "../../~npm~p-try@2.2.0/node_modules/p-try" + } + } + }, + { + "id": "~npm~p-locate@4.1.0", + "node_modules": { + "p-limit": { + "link": "../../~npm~p-limit@2.3.0/node_modules/p-limit" + }, + "p-locate": { + "pkg": { + "name": "p-locate", + "version": "4.1.0" + } + } + } + }, + { + "id": "~npm~p-map@3.0.0", + "node_modules": { + "aggregate-error": { + "link": "../../~npm~aggregate-error@3.1.0/node_modules/aggregate-error" + }, + "p-map": { + "pkg": { + "name": "p-map", + "version": "3.0.0" + } + } + } + }, + { + "id": "~npm~p-try@2.2.0", + "node_modules": { + "p-try": { + "pkg": { + "name": "p-try", + "version": "2.2.0" + } + } + } + }, + { + "id": "~npm~package-hash@4.0.0", + "node_modules": { + "graceful-fs": { + "link": "../../~npm~graceful-fs@4.2.11/node_modules/graceful-fs" + }, + "hasha": { + "link": "../../~npm~hasha@5.2.2/node_modules/hasha" + }, + "lodash.flattendeep": { + "link": "../../~npm~lodash.flattendeep@4.4.0/node_modules/lodash.flattendeep" + }, + "package-hash": { + "pkg": { + "name": "package-hash", + "version": "4.0.0" + } + }, + "release-zalgo": { + "link": "../../~npm~release-zalgo@1.0.0/node_modules/release-zalgo" + } + } + }, + { + "id": "~npm~path-exists@4.0.0", + "node_modules": { + "path-exists": { + "pkg": { + "name": "path-exists", + "version": "4.0.0" + } + } + } + }, + { + "id": "~npm~path-is-absolute@1.0.1", + "node_modules": { + "path-is-absolute": { + "pkg": { + "name": "path-is-absolute", + "version": "1.0.1" + } + } + } + }, + { + "id": "~npm~path-key@3.1.1", + "node_modules": { + "path-key": { + "pkg": { + "name": "path-key", + "version": "3.1.1" + } + } + } + }, + { + "id": "~npm~performance-now@2.1.0", + "node_modules": { + "performance-now": { + "pkg": { + "name": "performance-now", + "version": "2.1.0" + } + } + } + }, + { + "id": "~npm~picocolors@1.1.1", + "node_modules": { + "picocolors": { + "pkg": { + "name": "picocolors", + "version": "1.1.1" + } + } + } + }, + { + "id": "~npm~picomatch@2.3.2", + "node_modules": { + "picomatch": { + "pkg": { + "name": "picomatch", + "version": "2.3.2" + } + } + } + }, + { + "id": "~npm~pkg-dir@4.2.0", + "node_modules": { + "find-up": { + "link": "../../~npm~find-up@4.1.0/node_modules/find-up" + }, + "pkg-dir": { + "pkg": { + "name": "pkg-dir", + "version": "4.2.0" + } + } + } + }, + { + "id": "~npm~process-on-spawn@1.1.0", + "node_modules": { + "fromentries": { + "link": "../../~npm~fromentries@1.3.2/node_modules/fromentries" + }, + "process-on-spawn": { + "pkg": { + "name": "process-on-spawn", + "version": "1.1.0" + } + } + } + }, + { + "id": "~npm~psl@1.15.0", + "node_modules": { + "psl": { + "pkg": { + "name": "psl", + "version": "1.15.0" + } + }, + "punycode": { + "link": "../../~npm~punycode@2.3.1/node_modules/punycode" + } + } + }, + { + "id": "~npm~punycode@2.3.1", + "node_modules": { + "punycode": { + "pkg": { + "name": "punycode", + "version": "2.3.1" + } + } + } + }, + { + "id": "~npm~qs@6.5.5", + "node_modules": { + "qs": { + "pkg": { + "name": "qs", + "version": "6.5.5" + } + } + } + }, + { + "id": "~npm~react@18.2.0", + "node_modules": { + ".bin": { + "dir": true + }, + "loose-envify": { + "link": "../../~npm~loose-envify@1.4.0/node_modules/loose-envify" + }, + "react": { + "pkg": { + "name": "react", + "version": "18.2.0" + } + } + } + }, + { + "id": "~npm~readdirp@3.6.0", + "node_modules": { + "picomatch": { + "link": "../../~npm~picomatch@2.3.2/node_modules/picomatch" + }, + "readdirp": { + "pkg": { + "name": "readdirp", + "version": "3.6.0" + } + } + } + }, + { + "id": "~npm~release-zalgo@1.0.0", + "node_modules": { + "es6-error": { + "link": "../../~npm~es6-error@4.1.1/node_modules/es6-error" + }, + "release-zalgo": { + "pkg": { + "name": "release-zalgo", + "version": "1.0.0" + } + } + } + }, + { + "id": "~npm~request@2.88.2", + "node_modules": { + ".bin": { + "dir": true + }, + "aws-sign2": { + "link": "../../~npm~aws-sign2@0.7.0/node_modules/aws-sign2" + }, + "aws4": { + "link": "../../~npm~aws4@1.13.2/node_modules/aws4" + }, + "caseless": { + "link": "../../~npm~caseless@0.12.0/node_modules/caseless" + }, + "combined-stream": { + "link": "../../~npm~combined-stream@1.0.8/node_modules/combined-stream" + }, + "extend": { + "link": "../../~npm~extend@3.0.2/node_modules/extend" + }, + "forever-agent": { + "link": "../../~npm~forever-agent@0.6.1/node_modules/forever-agent" + }, + "form-data": { + "link": "../../~npm~form-data@2.3.3/node_modules/form-data" + }, + "har-validator": { + "link": "../../~npm~har-validator@5.1.5/node_modules/har-validator" + }, + "http-signature": { + "link": "../../~npm~http-signature@1.2.0/node_modules/http-signature" + }, + "is-typedarray": { + "link": "../../~npm~is-typedarray@1.0.0/node_modules/is-typedarray" + }, + "isstream": { + "link": "../../~npm~isstream@0.1.2/node_modules/isstream" + }, + "json-stringify-safe": { + "link": "../../~npm~json-stringify-safe@5.0.1/node_modules/json-stringify-safe" + }, + "mime-types": { + "link": "../../~npm~mime-types@2.1.35/node_modules/mime-types" + }, + "oauth-sign": { + "link": "../../~npm~oauth-sign@0.9.0/node_modules/oauth-sign" + }, + "performance-now": { + "link": "../../~npm~performance-now@2.1.0/node_modules/performance-now" + }, + "qs": { + "link": "../../~npm~qs@6.5.5/node_modules/qs" + }, + "request": { + "pkg": { + "name": "request", + "version": "2.88.2" + } + }, + "safe-buffer": { + "link": "../../~npm~safe-buffer@5.2.1/node_modules/safe-buffer" + }, + "tough-cookie": { + "link": "../../~npm~tough-cookie@2.5.0/node_modules/tough-cookie" + }, + "tunnel-agent": { + "link": "../../~npm~tunnel-agent@0.6.0/node_modules/tunnel-agent" + }, + "uuid": { + "link": "../../~npm~uuid@3.4.0/node_modules/uuid" + } + } + }, + { + "id": "~npm~require-directory@2.1.1", + "node_modules": { + "require-directory": { + "pkg": { + "name": "require-directory", + "version": "2.1.1" + } + } + } + }, + { + "id": "~npm~require-main-filename@2.0.0", + "node_modules": { + "require-main-filename": { + "pkg": { + "name": "require-main-filename", + "version": "2.0.0" + } + } + } + }, + { + "id": "~npm~resolve-from@5.0.0", + "node_modules": { + "resolve-from": { + "pkg": { + "name": "resolve-from", + "version": "5.0.0" + } + } + } + }, + { + "id": "~npm~rimraf@3.0.2", + "node_modules": { + "glob": { + "link": "../../~npm~glob@7.2.3/node_modules/glob" + }, + "rimraf": { + "pkg": { + "name": "rimraf", + "version": "3.0.2" + } + } + } + }, + { + "id": "~npm~safe-buffer@5.2.1", + "node_modules": { + "safe-buffer": { + "pkg": { + "name": "safe-buffer", + "version": "5.2.1" + } + } + } + }, + { + "id": "~npm~safer-buffer@2.1.2", + "node_modules": { + "safer-buffer": { + "pkg": { + "name": "safer-buffer", + "version": "2.1.2" + } + } + } + }, + { + "id": "~npm~semver@6.3.1", + "node_modules": { + "semver": { + "pkg": { + "name": "semver", + "version": "6.3.1" + } + } + } + }, + { + "id": "~npm~semver@7.6.0", + "node_modules": { + "lru-cache": { + "link": "../../~npm~lru-cache@6.0.0/node_modules/lru-cache" + }, + "semver": { + "pkg": { + "name": "semver", + "version": "7.6.0" + } + } + } + }, + { + "id": "~npm~set-blocking@2.0.0", + "node_modules": { + "set-blocking": { + "pkg": { + "name": "set-blocking", + "version": "2.0.0" + } + } + } + }, + { + "id": "~npm~shebang-command@2.0.0", + "node_modules": { + "shebang-command": { + "pkg": { + "name": "shebang-command", + "version": "2.0.0" + } + }, + "shebang-regex": { + "link": "../../~npm~shebang-regex@3.0.0/node_modules/shebang-regex" + } + } + }, + { + "id": "~npm~shebang-regex@3.0.0", + "node_modules": { + "shebang-regex": { + "pkg": { + "name": "shebang-regex", + "version": "3.0.0" + } + } + } + }, + { + "id": "~npm~signal-exit@3.0.7", + "node_modules": { + "signal-exit": { + "pkg": { + "name": "signal-exit", + "version": "3.0.7" + } + } + } + }, + { + "id": "~npm~source-map-support@0.5.21", + "node_modules": { + "buffer-from": { + "link": "../../~npm~buffer-from@1.1.2/node_modules/buffer-from" + }, + "source-map": { + "link": "../../~npm~source-map@0.6.1/node_modules/source-map" + }, + "source-map-support": { + "pkg": { + "name": "source-map-support", + "version": "0.5.21" + } + } + } + }, + { + "id": "~npm~source-map@0.6.1", + "node_modules": { + "source-map": { + "pkg": { + "name": "source-map", + "version": "0.6.1" + } + } + } + }, + { + "id": "~npm~spawn-wrap@2.0.0", + "node_modules": { + ".bin": { + "dir": true + }, + "foreground-child": { + "link": "../../~npm~foreground-child@2.0.0/node_modules/foreground-child" + }, + "is-windows": { + "link": "../../~npm~is-windows@1.0.2/node_modules/is-windows" + }, + "make-dir": { + "link": "../../~npm~make-dir@3.1.0/node_modules/make-dir" + }, + "rimraf": { + "link": "../../~npm~rimraf@3.0.2/node_modules/rimraf" + }, + "signal-exit": { + "link": "../../~npm~signal-exit@3.0.7/node_modules/signal-exit" + }, + "spawn-wrap": { + "pkg": { + "name": "spawn-wrap", + "version": "2.0.0" + } + }, + "which": { + "link": "../../~npm~which@2.0.2/node_modules/which" + } + } + }, + { + "id": "~npm~sprintf-js@1.0.3", + "node_modules": { + "sprintf-js": { + "pkg": { + "name": "sprintf-js", + "version": "1.0.3" + } + } + } + }, + { + "id": "~npm~sshpk@1.18.0", + "node_modules": { + "asn1": { + "link": "../../~npm~asn1@0.2.6/node_modules/asn1" + }, + "assert-plus": { + "link": "../../~npm~assert-plus@1.0.0/node_modules/assert-plus" + }, + "bcrypt-pbkdf": { + "link": "../../~npm~bcrypt-pbkdf@1.0.2/node_modules/bcrypt-pbkdf" + }, + "dashdash": { + "link": "../../~npm~dashdash@1.14.1/node_modules/dashdash" + }, + "ecc-jsbn": { + "link": "../../~npm~ecc-jsbn@0.1.2/node_modules/ecc-jsbn" + }, + "getpass": { + "link": "../../~npm~getpass@0.1.7/node_modules/getpass" + }, + "jsbn": { + "link": "../../~npm~jsbn@0.1.1/node_modules/jsbn" + }, + "safer-buffer": { + "link": "../../~npm~safer-buffer@2.1.2/node_modules/safer-buffer" + }, + "sshpk": { + "pkg": { + "name": "sshpk", + "version": "1.18.0" + } + }, + "tweetnacl": { + "link": "../../~npm~tweetnacl@0.14.5/node_modules/tweetnacl" + } + } + }, + { + "id": "~npm~stack-utils@2.0.6", + "node_modules": { + "escape-string-regexp": { + "link": "../../~npm~escape-string-regexp@2.0.0/node_modules/escape-string-regexp" + }, + "stack-utils": { + "pkg": { + "name": "stack-utils", + "version": "2.0.6" + } + } + } + }, + { + "id": "~npm~string-width@4.2.3", + "node_modules": { + "emoji-regex": { + "link": "../../~npm~emoji-regex@8.0.0/node_modules/emoji-regex" + }, + "is-fullwidth-code-point": { + "link": "../../~npm~is-fullwidth-code-point@3.0.0/node_modules/is-fullwidth-code-point" + }, + "string-width": { + "pkg": { + "name": "string-width", + "version": "4.2.3" + } + }, + "strip-ansi": { + "link": "../../~npm~strip-ansi@6.0.1/node_modules/strip-ansi" + } + } + }, + { + "id": "~npm~strip-ansi@6.0.1", + "node_modules": { + "ansi-regex": { + "link": "../../~npm~ansi-regex@5.0.1/node_modules/ansi-regex" + }, + "strip-ansi": { + "pkg": { + "name": "strip-ansi", + "version": "6.0.1" + } + } + } + }, + { + "id": "~npm~strip-bom@4.0.0", + "node_modules": { + "strip-bom": { + "pkg": { + "name": "strip-bom", + "version": "4.0.0" + } + } + } + }, + { + "id": "~npm~supports-color@7.2.0", + "node_modules": { + "has-flag": { + "link": "../../~npm~has-flag@4.0.0/node_modules/has-flag" + }, + "supports-color": { + "pkg": { + "name": "supports-color", + "version": "7.2.0" + } + } + } + }, + { + "id": "~npm~tap-mocha-reporter@5.0.4", + "node_modules": { + ".bin": { + "dir": true + }, + "color-support": { + "link": "../../~npm~color-support@1.1.3/node_modules/color-support" + }, + "debug": { + "link": "../../~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms/node_modules/debug" + }, + "diff": { + "link": "../../~npm~diff@4.0.4/node_modules/diff" + }, + "escape-string-regexp": { + "link": "../../~npm~escape-string-regexp@2.0.0/node_modules/escape-string-regexp" + }, + "glob": { + "link": "../../~npm~glob@7.2.3/node_modules/glob" + }, + "tap-mocha-reporter": { + "pkg": { + "name": "tap-mocha-reporter", + "version": "5.0.4" + } + }, + "tap-parser": { + "link": "../../~npm~tap-parser@11.0.2/node_modules/tap-parser" + }, + "tap-yaml": { + "link": "../../~npm~tap-yaml@1.0.2/node_modules/tap-yaml" + }, + "unicode-length": { + "link": "../../~npm~unicode-length@2.1.0/node_modules/unicode-length" + } + } + }, + { + "id": "~npm~tap-parser@11.0.2", + "node_modules": { + "events-to-array": { + "link": "../../~npm~events-to-array@1.1.2/node_modules/events-to-array" + }, + "minipass": { + "link": "../../~npm~minipass@3.3.6/node_modules/minipass" + }, + "tap-parser": { + "pkg": { + "name": "tap-parser", + "version": "11.0.2" + } + }, + "tap-yaml": { + "link": "../../~npm~tap-yaml@1.0.2/node_modules/tap-yaml" + } + } + }, + { + "id": "~npm~tap-yaml@1.0.2", + "node_modules": { + "tap-yaml": { + "pkg": { + "name": "tap-yaml", + "version": "1.0.2" + } + }, + "yaml": { + "link": "../../~npm~yaml@1.10.3/node_modules/yaml" + } + } + }, + { + "id": "~npm~tap@15.2.3~peer.6f88d0ccf17dbbdc", + "node_modules": { + ".bin": { + "dir": true + }, + "chokidar": { + "link": "../../~npm~chokidar@3.6.0/node_modules/chokidar" + }, + "coveralls": { + "link": "../../~npm~coveralls@3.1.1/node_modules/coveralls" + }, + "findit": { + "link": "../../~npm~findit@2.0.0/node_modules/findit" + }, + "foreground-child": { + "link": "../../~npm~foreground-child@2.0.0/node_modules/foreground-child" + }, + "fs-exists-cached": { + "link": "../../~npm~fs-exists-cached@1.0.0/node_modules/fs-exists-cached" + }, + "glob": { + "link": "../../~npm~glob@7.2.3/node_modules/glob" + }, + "isexe": { + "link": "../../~npm~isexe@2.0.0/node_modules/isexe" + }, + "istanbul-lib-processinfo": { + "link": "../../~npm~istanbul-lib-processinfo@2.0.3/node_modules/istanbul-lib-processinfo" + }, + "jackspeak": { + "link": "../../~npm~jackspeak@1.4.2/node_modules/jackspeak" + }, + "libtap": { + "link": "../../~npm~libtap@1.4.1/node_modules/libtap" + }, + "minipass": { + "link": "../../~npm~minipass@3.3.6/node_modules/minipass" + }, + "mkdirp": { + "link": "../../~npm~mkdirp@1.0.4/node_modules/mkdirp" + }, + "nyc": { + "link": "../../~npm~nyc@15.1.0/node_modules/nyc" + }, + "opener": { + "link": "../../~npm~opener@1.5.2/node_modules/opener" + }, + "rimraf": { + "link": "../../~npm~rimraf@3.0.2/node_modules/rimraf" + }, + "signal-exit": { + "link": "../../~npm~signal-exit@3.0.7/node_modules/signal-exit" + }, + "source-map-support": { + "link": "../../~npm~source-map-support@0.5.21/node_modules/source-map-support" + }, + "tap": { + "pkg": { + "name": "tap", + "version": "15.2.3" + } + }, + "tap-mocha-reporter": { + "link": "../../~npm~tap-mocha-reporter@5.0.4/node_modules/tap-mocha-reporter" + }, + "tap-parser": { + "link": "../../~npm~tap-parser@11.0.2/node_modules/tap-parser" + }, + "tap-yaml": { + "link": "../../~npm~tap-yaml@1.0.2/node_modules/tap-yaml" + }, + "tcompare": { + "link": "../../~npm~tcompare@5.0.7/node_modules/tcompare" + }, + "which": { + "link": "../../~npm~which@2.0.2/node_modules/which" + } + } + }, + { + "id": "~npm~tcompare@5.0.7", + "node_modules": { + "diff": { + "link": "../../~npm~diff@4.0.4/node_modules/diff" + }, + "tcompare": { + "pkg": { + "name": "tcompare", + "version": "5.0.7" + } + } + } + }, + { + "id": "~npm~test-exclude@6.0.0", + "node_modules": { + "@istanbuljs/schema": { + "link": "../../../~npm~@istanbuljs+schema@0.1.6/node_modules/@istanbuljs/schema" + }, + "glob": { + "link": "../../~npm~glob@7.2.3/node_modules/glob" + }, + "minimatch": { + "link": "../../~npm~minimatch@3.1.5/node_modules/minimatch" + }, + "test-exclude": { + "pkg": { + "name": "test-exclude", + "version": "6.0.0" + } + } + } + }, + { + "id": "~npm~to-regex-range@5.0.1", + "node_modules": { + "is-number": { + "link": "../../~npm~is-number@7.0.0/node_modules/is-number" + }, + "to-regex-range": { + "pkg": { + "name": "to-regex-range", + "version": "5.0.1" + } + } + } + }, + { + "id": "~npm~tough-cookie@2.5.0", + "node_modules": { + "psl": { + "link": "../../~npm~psl@1.15.0/node_modules/psl" + }, + "punycode": { + "link": "../../~npm~punycode@2.3.1/node_modules/punycode" + }, + "tough-cookie": { + "pkg": { + "name": "tough-cookie", + "version": "2.5.0" + } + } + } + }, + { + "id": "~npm~trivial-deferred@1.1.2", + "node_modules": { + "trivial-deferred": { + "pkg": { + "name": "trivial-deferred", + "version": "1.1.2" + } + } + } + }, + { + "id": "~npm~tunnel-agent@0.6.0", + "node_modules": { + "safe-buffer": { + "link": "../../~npm~safe-buffer@5.2.1/node_modules/safe-buffer" + }, + "tunnel-agent": { + "pkg": { + "name": "tunnel-agent", + "version": "0.6.0" + } + } + } + }, + { + "id": "~npm~tweetnacl@0.14.5", + "node_modules": { + "tweetnacl": { + "pkg": { + "name": "tweetnacl", + "version": "0.14.5" + } + } + } + }, + { + "id": "~npm~type-fest@0.8.1", + "node_modules": { + "type-fest": { + "pkg": { + "name": "type-fest", + "version": "0.8.1" + } + } + } + }, + { + "id": "~npm~typedarray-to-buffer@3.1.5", + "node_modules": { + "is-typedarray": { + "link": "../../~npm~is-typedarray@1.0.0/node_modules/is-typedarray" + }, + "typedarray-to-buffer": { + "pkg": { + "name": "typedarray-to-buffer", + "version": "3.1.5" + } + } + } + }, + { + "id": "~npm~unicode-length@2.1.0", + "node_modules": { + "punycode": { + "link": "../../~npm~punycode@2.3.1/node_modules/punycode" + }, + "unicode-length": { + "pkg": { + "name": "unicode-length", + "version": "2.1.0" + } + } + } + }, + { + "id": "~npm~update-browserslist-db@1.3.3~peer.27c7ade2f48570ae", + "node_modules": { + ".bin": { + "dir": true + }, + "browserslist": { + "link": "../../~npm~browserslist@4.29.1/node_modules/browserslist" + }, + "escalade": { + "link": "../../~npm~escalade@3.2.0/node_modules/escalade" + }, + "picocolors": { + "link": "../../~npm~picocolors@1.1.1/node_modules/picocolors" + }, + "update-browserslist-db": { + "pkg": { + "name": "update-browserslist-db", + "version": "1.3.3" + } + } + } + }, + { + "id": "~npm~uri-js@4.4.1", + "node_modules": { + "punycode": { + "link": "../../~npm~punycode@2.3.1/node_modules/punycode" + }, + "uri-js": { + "pkg": { + "name": "uri-js", + "version": "4.4.1" + } + } + } + }, + { + "id": "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "node_modules": { + "react": { + "link": "../../~npm~react@18.2.0/node_modules/react" + }, + "use-sync-external-store": { + "pkg": { + "name": "use-sync-external-store", + "version": "1.2.0" + } + } + } + }, + { + "id": "~npm~uuid@3.4.0", + "node_modules": { + "uuid": { + "pkg": { + "name": "uuid", + "version": "3.4.0" + } + } + } + }, + { + "id": "~npm~uuid@8.3.2", + "node_modules": { + "uuid": { + "pkg": { + "name": "uuid", + "version": "8.3.2" + } + } + } + }, + { + "id": "~npm~verror@1.10.0", + "node_modules": { + "assert-plus": { + "link": "../../~npm~assert-plus@1.0.0/node_modules/assert-plus" + }, + "core-util-is": { + "link": "../../~npm~core-util-is@1.0.2/node_modules/core-util-is" + }, + "extsprintf": { + "link": "../../~npm~extsprintf@1.3.0/node_modules/extsprintf" + }, + "verror": { + "pkg": { + "name": "verror", + "version": "1.10.0" + } + } + } + }, + { + "id": "~npm~which-module@2.0.1", + "node_modules": { + "which-module": { + "pkg": { + "name": "which-module", + "version": "2.0.1" + } + } + } + }, + { + "id": "~npm~which@2.0.2", + "node_modules": { + "isexe": { + "link": "../../~npm~isexe@2.0.0/node_modules/isexe" + }, + "which": { + "pkg": { + "name": "which", + "version": "2.0.2" + } + } + } + }, + { + "id": "~npm~wrap-ansi@6.2.0", + "node_modules": { + "ansi-styles": { + "link": "../../~npm~ansi-styles@4.3.0/node_modules/ansi-styles" + }, + "string-width": { + "link": "../../~npm~string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../../~npm~strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "wrap-ansi": { + "pkg": { + "name": "wrap-ansi", + "version": "6.2.0" + } + } + } + }, + { + "id": "~npm~wrap-ansi@7.0.0", + "node_modules": { + "ansi-styles": { + "link": "../../~npm~ansi-styles@4.3.0/node_modules/ansi-styles" + }, + "string-width": { + "link": "../../~npm~string-width@4.2.3/node_modules/string-width" + }, + "strip-ansi": { + "link": "../../~npm~strip-ansi@6.0.1/node_modules/strip-ansi" + }, + "wrap-ansi": { + "pkg": { + "name": "wrap-ansi", + "version": "7.0.0" + } + } + } + }, + { + "id": "~npm~wrappy@1.0.2", + "node_modules": { + "wrappy": { + "pkg": { + "name": "wrappy", + "version": "1.0.2" + } + } + } + }, + { + "id": "~npm~write-file-atomic@3.0.3", + "node_modules": { + "imurmurhash": { + "link": "../../~npm~imurmurhash@0.1.4/node_modules/imurmurhash" + }, + "is-typedarray": { + "link": "../../~npm~is-typedarray@1.0.0/node_modules/is-typedarray" + }, + "signal-exit": { + "link": "../../~npm~signal-exit@3.0.7/node_modules/signal-exit" + }, + "typedarray-to-buffer": { + "link": "../../~npm~typedarray-to-buffer@3.1.5/node_modules/typedarray-to-buffer" + }, + "write-file-atomic": { + "pkg": { + "name": "write-file-atomic", + "version": "3.0.3" + } + } + } + }, + { + "id": "~npm~y18n@4.0.3", + "node_modules": { + "y18n": { + "pkg": { + "name": "y18n", + "version": "4.0.3" + } + } + } + }, + { + "id": "~npm~yallist@3.1.1", + "node_modules": { + "yallist": { + "pkg": { + "name": "yallist", + "version": "3.1.1" + } + } + } + }, + { + "id": "~npm~yallist@4.0.0", + "node_modules": { + "yallist": { + "pkg": { + "name": "yallist", + "version": "4.0.0" + } + } + } + }, + { + "id": "~npm~yaml@1.10.3", + "node_modules": { + "yaml": { + "pkg": { + "name": "yaml", + "version": "1.10.3" + } + } + } + }, + { + "id": "~npm~yargs-parser@18.1.3", + "node_modules": { + "camelcase": { + "link": "../../~npm~camelcase@5.3.1/node_modules/camelcase" + }, + "decamelize": { + "link": "../../~npm~decamelize@1.2.0/node_modules/decamelize" + }, + "yargs-parser": { + "pkg": { + "name": "yargs-parser", + "version": "18.1.3" + } + } + } + }, + { + "id": "~npm~yargs@15.4.1", + "node_modules": { + "cliui": { + "link": "../../~npm~cliui@6.0.0/node_modules/cliui" + }, + "decamelize": { + "link": "../../~npm~decamelize@1.2.0/node_modules/decamelize" + }, + "find-up": { + "link": "../../~npm~find-up@4.1.0/node_modules/find-up" + }, + "get-caller-file": { + "link": "../../~npm~get-caller-file@2.0.5/node_modules/get-caller-file" + }, + "require-directory": { + "link": "../../~npm~require-directory@2.1.1/node_modules/require-directory" + }, + "require-main-filename": { + "link": "../../~npm~require-main-filename@2.0.0/node_modules/require-main-filename" + }, + "set-blocking": { + "link": "../../~npm~set-blocking@2.0.0/node_modules/set-blocking" + }, + "string-width": { + "link": "../../~npm~string-width@4.2.3/node_modules/string-width" + }, + "which-module": { + "link": "../../~npm~which-module@2.0.1/node_modules/which-module" + }, + "y18n": { + "link": "../../~npm~y18n@4.0.3/node_modules/y18n" + }, + "yargs": { + "pkg": { + "name": "yargs", + "version": "15.4.1" + } + }, + "yargs-parser": { + "link": "../../~npm~yargs-parser@18.1.3/node_modules/yargs-parser" + } + } + } + ], + "importers": { + ".bin": { + "dir": true + }, + "@isaacs/string-locale-compare": { + "link": "../.vlt/~npm~@isaacs+string-locale-compare@1.1.0/node_modules/@isaacs/string-locale-compare" + }, + "debug": { + "link": ".vlt/~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms/node_modules/debug" + }, + "left-pad": { + "link": ".vlt/~npm~left-pad@1.3.0/node_modules/left-pad" + }, + "localdir": { + "link": "../vendor/localdir" + }, + "lp-alias": { + "link": ".vlt/~npm~left-pad@1.1.3/node_modules/left-pad" + }, + "lp-remote": { + "link": ".vlt/remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz/node_modules/left-pad" + }, + "ms-tgz": { + "link": ".vlt/file~vendor+ms-2.1.2.tgz/node_modules/ms" + }, + "react": { + "link": ".vlt/~npm~react@18.2.0/node_modules/react" + }, + "semver_x": { + "link": ".vlt/~npm~semver@7.6.0/node_modules/semver" + }, + "slc-git": { + "link": ".vlt/git~github_cisaacs+string-locale-compare~v1.1.0/node_modules/@isaacs/string-locale-compare" + }, + "use-sync-external-store": { + "link": ".vlt/~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba/node_modules/use-sync-external-store" + } + }, + "members": {}, + "linkTargets": { + "vendor/localdir": { + "name": "localdir", + "version": "1.3.0" + } + } +} diff --git a/scripts/capture-vlt-tree.mjs b/scripts/capture-vlt-tree.mjs new file mode 100644 index 00000000..fa0e2b31 --- /dev/null +++ b/scripts/capture-vlt-tree.mjs @@ -0,0 +1,137 @@ +#!/usr/bin/env node +// Prints the on-disk layout of a vlt-installed project as the listing the +// crawler tests stage (crates/socket-patch-core/tests/fixtures/vlt-trees/): +// every node_modules/.vlt entry (real package dirs with their package.json +// identity, dependency links, other real dirs), the store's top-level files, +// the internal hoist dir, and the importer node_modules of the root and of +// each workspace member. Store entry names are kept byte for byte. +// +// node scripts/capture-vlt-tree.mjs \ +// > crates/socket-patch-core/tests/fixtures/vlt-trees//listing.json + +import fs from 'node:fs' +import path from 'node:path' + +const [root, vltVersion] = process.argv.slice(2) +if (!root || !vltVersion) { + process.stderr.write('usage: capture-vlt-tree.mjs \n') + process.exit(2) +} + +const byteOrder = (a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b)) +const sortedDir = (dir) => fs.readdirSync(dir).sort(byteOrder) + +const identity = (dir) => { + const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')) + return { name: pkg.name, version: pkg.version } +} + +const describe = (full) => { + const st = fs.lstatSync(full) + if (st.isSymbolicLink()) { + return { link: fs.readlinkSync(full) } + } + if (st.isDirectory() && fs.existsSync(path.join(full, 'package.json'))) { + return { pkg: identity(full) } + } + if (st.isDirectory()) { + return { dir: true } + } + return { file: true } +} + +const listModules = (nm) => { + const out = {} + if (!fs.existsSync(nm)) { + return out + } + for (const child of sortedDir(nm)) { + const full = path.join(nm, child) + const st = fs.lstatSync(full) + if (child.startsWith('@') && st.isDirectory() && !st.isSymbolicLink()) { + for (const scoped of sortedDir(full)) { + out[`${child}/${scoped}`] = describe(path.join(full, scoped)) + } + } else if (child === '.bin' && st.isDirectory()) { + out[child] = { dir: true } + } else { + out[child] = describe(full) + } + } + return out +} + +const nm = path.join(root, 'node_modules') +const store = path.join(nm, '.vlt') +const lockPath = path.join(root, 'vlt-lock.json') +const lock = fs.existsSync(lockPath) ? JSON.parse(fs.readFileSync(lockPath, 'utf8')) : {} + +const entries = [] +const storeFiles = [] +for (const name of sortedDir(store)) { + const full = path.join(store, name) + const st = fs.lstatSync(full) + if (name === 'node_modules' || !st.isDirectory()) { + if (!st.isDirectory()) { + storeFiles.push(name) + } + continue + } + entries.push({ id: name, node_modules: listModules(path.join(full, 'node_modules')) }) +} + +const importers = listModules(nm) +delete importers['.vlt'] +delete importers['.vlt-lock.json'] + +const members = {} +const linkTargets = {} +const recordTarget = (from, value) => { + if (!value.link) { + return + } + const target = path.relative(root, path.resolve(path.dirname(from), value.link)) + if (target.split(path.sep).includes('node_modules')) { + return + } + const full = path.join(root, target) + if (fs.existsSync(path.join(full, 'package.json'))) { + linkTargets[target.split(path.sep).join('/')] = identity(full) + } +} +for (const [rel, value] of Object.entries(importers)) { + recordTarget(path.join(nm, rel), value) +} +const walkMembers = (dir) => { + for (const child of sortedDir(dir)) { + if (child === 'node_modules' || child.startsWith('.')) { + continue + } + const full = path.join(dir, child) + if (!fs.lstatSync(full).isDirectory()) { + continue + } + const memberNm = path.join(full, 'node_modules') + if (fs.existsSync(memberNm)) { + const rel = path.relative(root, full).split(path.sep).join('/') + members[rel] = listModules(memberNm) + for (const [dep, value] of Object.entries(members[rel])) { + recordTarget(path.join(memberNm, dep), value) + } + } + walkMembers(full) + } +} +walkMembers(root) + +const listing = { + vlt: vltVersion, + lockfileVersion: lock.lockfileVersion ?? null, + storeFiles, + hoist: listModules(path.join(store, 'node_modules')), + store: entries, + importers, + members, + linkTargets, +} +process.stdout.write(`${JSON.stringify(listing, null, 1)}\n`) From e96959b207bbbfcf69ebf1b689d91e7b14ecdf0f Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Fri, 25 Sep 2026 21:39:55 -0400 Subject: [PATCH 10/46] Stop counting store dependency links as copies When a git, remote or file: dependency in a vlt (or pnpm) store depended on a patched package, apply and rollback treated that dependency link as a second installed copy: the run reported an extra "already patched" or "already original" result, and get, vex, vendor and repair could pick the link as the package's location. An importer link into a git or tarball entry was also listed twice. The resolver now follows the store-entry rule the scan already uses: inside a store entry only real directories are copies, and a store copy an importer link already points at keeps the importer path. A node_modules/.vlt that is a link is never followed, and tests now pin the hoist-dir skip and the policy that a same-version git copy is patched as its own copy. Doc comments broken in the previous commit are rewrapped. Assisted-by: Claude Code:claude-opus-5-5 --- .../tests/in_process_npm_multicopy.rs | 24 ++- .../src/crawlers/npm_crawler.rs | 95 ++++++--- .../src/crawlers/pkg_managers.rs | 6 +- crates/socket-patch-core/src/utils/fs.rs | 3 +- .../tests/covgap_crawlers_npm_crawler.rs | 5 +- .../tests/crawler_npm_e2e.rs | 193 +++++++++++++++++- 6 files changed, 293 insertions(+), 33 deletions(-) diff --git a/crates/socket-patch-cli/tests/in_process_npm_multicopy.rs b/crates/socket-patch-cli/tests/in_process_npm_multicopy.rs index 35ab91da..4e52774f 100644 --- a/crates/socket-patch-cli/tests/in_process_npm_multicopy.rs +++ b/crates/socket-patch-cli/tests/in_process_npm_multicopy.rs @@ -268,7 +268,9 @@ fn rollback_restores_every_on_disk_copy_of_a_duplicated_package() { /// (`.vlt/~npm~dupvuln@1.0.0~peer.2/` and `~peer.3/`), both real and /// runtime-loaded. The importer links ONE of them, so the resolver hands /// apply one primary and the store fan-out must reach the other; rollback -/// restores both. Returns `(root, primary index.js, twin index.js)`. +/// restores both. A git dependency links the primary from its own entry, +/// a dependency edge that must never count as a third copy. Returns +/// `(root, primary index.js, twin index.js)`. fn build_vlt_peer_variant_tree(tmp: &Path, link_importer: bool) -> (PathBuf, PathBuf, PathBuf) { let name = "dupvuln"; let purl = "pkg:npm/dupvuln@1.0.0"; @@ -290,6 +292,14 @@ fn build_vlt_peer_variant_tree(tmp: &Path, link_importer: bool) -> (PathBuf, Pat let primary = write_copy(&entry("~npm~dupvuln@1.0.0~peer.2"), name, "1.0.0", original); let twin = write_copy(&entry("~npm~dupvuln@1.0.0~peer.3"), name, "1.0.0", original); std::fs::write(tmp.join("node_modules").join(".vlt-lock.json"), "{}").unwrap(); + let git_nm = store.join("git~github_cx+y~v1.0.0").join("node_modules"); + write_copy(&git_nm.join("y"), "y", "1.0.0", b"require('dupvuln');\n"); + #[cfg(unix)] + std::os::unix::fs::symlink( + "../../~npm~dupvuln@1.0.0~peer.2/node_modules/dupvuln", + git_nm.join(name), + ) + .unwrap(); if link_importer { #[cfg(unix)] std::os::unix::fs::symlink( @@ -354,16 +364,22 @@ fn apply_and_rollback_reach_both_vlt_peer_variant_copies_from_an_importer_link() let (code, v) = run_apply(&root); assert_eq!(code, 0, "apply must succeed; envelope={v}"); assert_eq!(v["status"], "success", "envelope={v}"); + assert_eq!(v["summary"]["applied"], 1, "envelope={v}"); + assert_eq!(v["summary"]["skipped"], 0, "envelope={v}"); assert_vlt_copies([&primary, &twin], true, "after apply"); let (code, v) = run_rollback(&root); assert_eq!(code, 0, "rollback must succeed; envelope={v}"); + assert_eq!(v["rolledBack"], 1, "envelope={v}"); + assert_eq!(v["alreadyOriginal"], 0, "envelope={v}"); assert_vlt_copies([&primary, &twin], false, "after rollback"); } /// Without an importer link (a transitive-only dependency) both store /// copies are found by the resolver itself; each is patched exactly once -/// and both are restored. +/// and both are restored. The first copy's fan-out already reaches the +/// second, so the second reports skipped (apply) and already original +/// (rollback), and the git entry's link adds nothing. #[test] fn apply_and_rollback_reach_both_transitive_only_vlt_store_copies() { let tmp = tempfile::tempdir().unwrap(); @@ -371,9 +387,13 @@ fn apply_and_rollback_reach_both_transitive_only_vlt_store_copies() { let (code, v) = run_apply(&root); assert_eq!(code, 0, "apply must succeed; envelope={v}"); + assert_eq!(v["summary"]["applied"], 1, "envelope={v}"); + assert_eq!(v["summary"]["skipped"], 1, "envelope={v}"); assert_vlt_copies([&primary, &twin], true, "after apply"); let (code, v) = run_rollback(&root); assert_eq!(code, 0, "rollback must succeed; envelope={v}"); + assert_eq!(v["rolledBack"], 1, "envelope={v}"); + assert_eq!(v["alreadyOriginal"], 1, "envelope={v}"); assert_vlt_copies([&primary, &twin], false, "after rollback"); } diff --git a/crates/socket-patch-core/src/crawlers/npm_crawler.rs b/crates/socket-patch-core/src/crawlers/npm_crawler.rs index f5263ec9..cfc1cfc4 100644 --- a/crates/socket-patch-core/src/crawlers/npm_crawler.rs +++ b/crates/socket-patch-core/src/crawlers/npm_crawler.rs @@ -451,9 +451,9 @@ enum ScanPolicy<'a> { /// One pnpm or vlt store entry's `node_modules`: only REAL /// directories are inventoried — a symlinked (or, on Windows, /// junctioned) entry here is the package's dependency pointing at a - /// sibling store entry, - /// which is inventoried via that entry; following it would record the - /// same package under a path owned by a different store entry. + /// sibling store entry, which is inventoried via that entry; following + /// it would record the same package under a path owned by a different + /// store entry. /// `identity_seen` optionally carries the entry's own package name /// (what the store dir name decodes to) when its name@version is /// already inventoried — the importer pass wins the `seen` dedup for @@ -621,10 +621,16 @@ impl NpmCrawler { if pending.is_empty() { return pending; } - let mut queue: VecDeque = VecDeque::from([node_modules_path.to_path_buf()]); - while let Some(nm_path) = queue.pop_front() { + let mut queue: VecDeque<(PathBuf, bool)> = + VecDeque::from([(node_modules_path.to_path_buf(), false)]); + while let Some((nm_path, store_entry)) = queue.pop_front() { for target in &pending { let pkg_path = nm_path.join(&target.dir_key); + // Inside a store entry a link is a dependency edge into a + // sibling entry, whose own probe records that copy. + if store_entry && !is_real_package_dir(&nm_path, &target.dir_key).await { + continue; + } let pkg_json_path = pkg_path.join("package.json"); match read_package_json(&pkg_json_path).await { @@ -639,8 +645,11 @@ impl NpmCrawler { let copies = result.entry(target.purl.clone()).or_default(); // Record each physical copy once — a path reached // twice (defensive against overlapping walks) is not - // double-counted. - if !copies.iter().any(|c| c.path == pkg_path) { + // double-counted, and a store copy an importer link + // already resolves to keeps the importer path. + let recorded = copies.iter().any(|c| c.path == pkg_path) + || (store_entry && resolves_to_any(&pkg_path, copies).await); + if !recorded { copies.push(CrawledPackage { name: target.name.clone(), version: found_version, @@ -674,7 +683,8 @@ impl NpmCrawler { } /// Append the `node_modules` dirs living one level below `nm_path` - /// (inside each of its package dirs, scoped or not) to `queue`. + /// (inside each of its package dirs, scoped or not) to `queue`, each + /// tagged `true` when it is a store entry's (see `ScanPolicy::StoreEntry`). /// Mirrors `scan_node_modules`' traversal policy: hidden entries are /// skipped and symlinked packages are never traversed — a symlink here /// points into pnpm's content-addressed store or an `npm link` target @@ -686,7 +696,7 @@ impl NpmCrawler { async fn collect_nested_node_modules( nm_path: &Path, pending_names: Option<&HashSet<&str>>, - queue: &mut VecDeque, + queue: &mut VecDeque<(PathBuf, bool)>, ) { for entry in crate::utils::fs::list_dir_entries(nm_path).await { let name = entry.file_name(); @@ -780,13 +790,13 @@ impl NpmCrawler { } let nested = entry_path.join(&scoped_name).join("node_modules"); if is_dir(&nested).await { - queue.push_back(nested); + queue.push_back((nested, false)); } } } else { let nested = entry_path.join("node_modules"); if is_dir(&nested).await { - queue.push_back(nested); + queue.push_back((nested, false)); } } } @@ -812,7 +822,7 @@ impl NpmCrawler { fn enqueue_pending_store_entries( entries: Vec, pending_names: Option<&HashSet<&str>>, - queue: &mut VecDeque, + queue: &mut VecDeque<(PathBuf, bool)>, ) { for entry in entries { if let (Some(filter), Some((entry_pkg, _version))) = (pending_names, &entry.advertised) @@ -821,7 +831,7 @@ impl NpmCrawler { continue; } } - queue.push_back(entry.node_modules); + queue.push_back((entry.node_modules, true)); } } @@ -1245,13 +1255,12 @@ impl NpmCrawler { /// Inventory the packages under each virtual-store entry's /// `node_modules` (entries come from `list_pnpm_store_entries`, - /// `collect_nested_store_entries` or `list_vlt_store_entries`). An entry - /// whose name decodes to a - /// name@version the importer pass already inventoried (every - /// root-linked direct dep) skips the redundant package.json re-read - /// via `identity_seen` — the entry is still walked, because - /// bundled/injected dependencies are real dirs that physically live - /// only inside the store entry. + /// `collect_nested_store_entries` or `list_vlt_store_entries`). An + /// entry whose name decodes to a name@version the importer pass already + /// inventoried (every root-linked direct dep) skips the redundant + /// package.json re-read via `identity_seen` — the entry is still + /// walked, because bundled/injected dependencies are real dirs that + /// physically live only inside the store entry. async fn scan_store_entries( entries: Vec, seen: &mut HashSet, @@ -1466,9 +1475,11 @@ enum StoreLayout { /// decodes to a DIFFERENT name@version is skipped and an undecodable /// name stays probeable. vlt entries come from `list_vlt_store_entries` /// and must decode to exactly the primary's name@version: an -/// undecodable vlt id is a git/file/remote artifact with its own bytes, -/// not a peer variant of the registry copy. The package.json probe is -/// the authority either way. +/// undecodable vlt id (git, remote, `file:`) is never a peer variant. +/// A matching copy inside one is still installed, and +/// `NpmCrawler::find_by_purls` returns it as a primary of its own (it +/// probes every undecodable entry), so it is patched like any other. +/// The package.json probe is the authority either way. /// 3. Only REAL directories count (a link inside a store entry is another /// entry's copy, already yielded via that entry), the copy `pkg_path` /// itself canonicalizes to is excluded, and results are deduped by @@ -1582,6 +1593,35 @@ pub async fn find_store_peer_variant_copies(pkg_path: &Path) -> Vec { // Utility // --------------------------------------------------------------------------- +/// Whether every component of `dir_key` (`name` or `@scope/name`) below +/// `nm_path` is a real directory: links and junctions do not count. +async fn is_real_package_dir(nm_path: &Path, dir_key: &str) -> bool { + let mut path = nm_path.to_path_buf(); + for component in dir_key.split('/') { + path.push(component); + let real = tokio::fs::symlink_metadata(&path) + .await + .is_ok_and(|m| m.is_dir()); + if !real { + return false; + } + } + true +} + +/// Whether `pkg_path` is the physical dir one of `copies` resolves to. +async fn resolves_to_any(pkg_path: &Path, copies: &[CrawledPackage]) -> bool { + let Ok(canon) = tokio::fs::canonicalize(pkg_path).await else { + return false; + }; + for copy in copies { + if tokio::fs::canonicalize(©.path).await.ok().as_ref() == Some(&canon) { + return true; + } + } + false +} + /// Whether a PURL-derived path component is safe to join onto the /// `node_modules` root. An npm package's scope (`@types`) and bare name /// (`node`) are each a single path segment, so a real one never contains a @@ -2549,7 +2589,11 @@ mod tests { ); assert_eq!( queue, - VecDeque::from([PathBuf::from("a"), PathBuf::from("b"), PathBuf::from("c")]) + VecDeque::from([ + (PathBuf::from("a"), true), + (PathBuf::from("b"), true), + (PathBuf::from("c"), true), + ]) ); let mut queue = VecDeque::new(); let as_pnpm = entries() @@ -2562,7 +2606,7 @@ mod tests { &mut queue, ); assert!( - !queue.contains(&PathBuf::from("a")), + !queue.contains(&(PathBuf::from("a"), true)), "the pnpm decoder misreads the legacy name" ); } @@ -2585,6 +2629,7 @@ mod tests { "··ms@2.1.3/node_modules/ms", "~npm~a@1.0.0/node_modules/a", "node_modules/@scope", + "node_modules/node_modules/hoist-decoy", ".VLT.DELETE.9.~npm~b@1.0.0/node_modules/b", "~npm~no-nm@1.0.0/no-nm", "elsewhere/node_modules", diff --git a/crates/socket-patch-core/src/crawlers/pkg_managers.rs b/crates/socket-patch-core/src/crawlers/pkg_managers.rs index f2528a5b..8ae536e6 100644 --- a/crates/socket-patch-core/src/crawlers/pkg_managers.rs +++ b/crates/socket-patch-core/src/crawlers/pkg_managers.rs @@ -87,7 +87,9 @@ pub enum NpmPkgManager { /// /// vlt wins over every other lockfile or store marker: its install state /// only exists after a vlt install, while a sibling `bun.lock`, -/// `pnpm-lock.yaml` or `yarn.lock` may be stale. Bun comes before pnpm in the precedence because bun's isolated +/// `pnpm-lock.yaml` or `yarn.lock` may be stale. +/// +/// Bun comes before pnpm in the precedence because bun's isolated /// linker (v1.3.2+ default) populates `node_modules/.bun/` which /// superficially resembles pnpm's `.pnpm/` content store. The /// lockfile filename disambiguates cleanly. @@ -117,6 +119,8 @@ pub fn detect_npm_pkg_manager(project_root: &Path) -> NpmPkgManager { return NpmPkgManager::YarnBerryPnP; } + // 2. vlt — its store or hidden lock exists only after a vlt install, + // so a stale sibling lockfile never outranks it. if project_root .join(crate::constants::npm_family::VLT_STORE_DIR) .is_dir() diff --git a/crates/socket-patch-core/src/utils/fs.rs b/crates/socket-patch-core/src/utils/fs.rs index 6b10102e..f3b2c88c 100644 --- a/crates/socket-patch-core/src/utils/fs.rs +++ b/crates/socket-patch-core/src/utils/fs.rs @@ -1183,7 +1183,8 @@ mod tests { /// Windows link shapes vlt produces: junctions (absolute targets, vlt /// >= 1.0.0-rc.22 and pnpm) and directory symlinks (older vlt). Both /// read as links through `entry_file_type` and `symlink_metadata`, are - /// followed by `is_dir`, report their target through `read_link`, and /// `remove_link` deletes them while the target survives. + /// followed by `is_dir`, report their target through `read_link`, and + /// `remove_link` deletes them while the target survives. #[cfg(windows)] async fn assert_windows_dir_link(tmp: &Path, link: &Path, target: &Path) { let entry = list_dir_entries(tmp) diff --git a/crates/socket-patch-core/tests/covgap_crawlers_npm_crawler.rs b/crates/socket-patch-core/tests/covgap_crawlers_npm_crawler.rs index 8aadafc9..c67b077f 100644 --- a/crates/socket-patch-core/tests/covgap_crawlers_npm_crawler.rs +++ b/crates/socket-patch-core/tests/covgap_crawlers_npm_crawler.rs @@ -307,8 +307,9 @@ async fn find_store_peer_variant_copies_unreadable_primary_returns_empty() { /// the ROOT `node_modules`, reachable only on the link's canonical chain. /// Real copies whose DepID decodes to the primary's `name@version` are /// returned (a `~peer.` twin); a git dependency of the same -/// `name@version` is a different artifact (its own bytes), never a -/// variant, and a store entry reached through a link is not a store entry. +/// `name@version` is never a variant (the resolver reports it as a primary +/// of its own), and a store entry reached through a link is not a store +/// entry. #[cfg(unix)] #[tokio::test] #[serial_test::parallel] diff --git a/crates/socket-patch-core/tests/crawler_npm_e2e.rs b/crates/socket-patch-core/tests/crawler_npm_e2e.rs index d70475a9..cd73f644 100644 --- a/crates/socket-patch-core/tests/crawler_npm_e2e.rs +++ b/crates/socket-patch-core/tests/crawler_npm_e2e.rs @@ -2543,19 +2543,42 @@ async fn find_by_purls_resolves_vlt_store_transitives() { let purls: Vec = copies.keys().cloned().collect(); assert!(purls.len() > 200, "{tree}: the capture is the full tree"); + let importer_links: Vec = listing["importers"] + .as_object() + .unwrap() + .keys() + .map(|key| nm.join(key)) + .collect(); let result = NpmCrawler.find_by_purls(&nm, &purls).await.unwrap(); for purl in &purls { let found = result .get(purl) .unwrap_or_else(|| panic!("{tree}: {purl} must resolve")); - let allowed: Vec<_> = copies[purl].iter().map(|p| canonical(p)).collect(); + let mut allowed: Vec<_> = copies[purl].iter().map(|p| canonical(p)).collect(); + let mut resolved = Vec::new(); for pkg in found { + let is_link = std::fs::symlink_metadata(&pkg.path) + .unwrap() + .file_type() + .is_symlink(); + assert!( + !is_link || importer_links.contains(&pkg.path), + "{tree}: {purl} resolved to the dependency link {}", + pkg.path.display() + ); assert!( allowed.contains(&canonical(&pkg.path)), "{tree}: {purl} resolved to {} which is no copy of it", pkg.path.display() ); + resolved.push(canonical(&pkg.path)); } + resolved.sort(); + allowed.sort(); + assert_eq!( + resolved, allowed, + "{tree}: {purl} resolves to each real copy exactly once; got {found:?}" + ); if let Some(key) = vlt_importer_key(&listing, tmp.path(), purl) { assert_eq!( found[0].path, @@ -2572,6 +2595,12 @@ async fn find_by_purls_resolves_vlt_store_transitives() { .is_ok_and(|rel| rel.to_string_lossy().contains("debug"))), "{tree}: the modifier-extra ms entry must resolve; got {ms:?}" ); + let tap = &result["pkg:npm/tap@15.2.3"]; + assert_eq!( + tap.len(), + 1, + "{tree}: the git entry's link to tap is an edge, not a copy; got {tap:?}" + ); let alias = &result["pkg:npm/left-pad@1.1.3"]; assert_eq!( alias[0].path.parent().unwrap().parent().unwrap().parent(), @@ -2732,8 +2761,161 @@ async fn find_by_purls_probes_undecodable_vlt_store_entry() { } } +/// Links inside a store entry's `node_modules` are dependency edges, never +/// copies: a git entry and a registry entry both link `tap` and `@s/p` +/// from their real entries, on either side of the real entries in readdir +/// order. An importer link into an undecodable entry already names that +/// entry's copy, so the probe of the entry adds nothing. +#[cfg(unix)] +#[tokio::test] +#[serial_test::parallel] +async fn find_by_purls_skips_vlt_dependency_links_and_importer_resolved_copies() { + use std::os::unix::fs::symlink; + + let tmp = tempfile::tempdir().unwrap(); + let nm = tmp.path().join("node_modules"); + let store = nm.join(".vlt"); + tokio::fs::create_dir_all(&store).await.unwrap(); + tokio::fs::write(nm.join(".vlt-lock.json"), b"{}") + .await + .unwrap(); + let tap = store.join("~npm~tap@1.0.0/node_modules/tap"); + stage_pkg_dir(&tap, "tap", "1.0.0").await; + let scoped = store.join("~npm~@s+p@1.0.0/node_modules/@s/p"); + stage_pkg_dir(&scoped, "@s/p", "1.0.0").await; + for (id, name) in [("git~github_cx+y~v1.0.0", "y"), ("~npm~zz@1.0.0", "zz")] { + let entry_nm = store.join(id).join("node_modules"); + stage_pkg_dir(&entry_nm.join(name), name, "1.0.0").await; + symlink( + "../../~npm~tap@1.0.0/node_modules/tap", + entry_nm.join("tap"), + ) + .unwrap(); + tokio::fs::create_dir_all(entry_nm.join("@s")) + .await + .unwrap(); + symlink( + "../../../~npm~@s+p@1.0.0/node_modules/@s/p", + entry_nm.join("@s/p"), + ) + .unwrap(); + } + symlink(".vlt/git~github_cx+y~v1.0.0/node_modules/y", nm.join("y")).unwrap(); + let remote_id = "remote~https_c++r.example+left-pad-1.2.0.tgz"; + stage_pkg_dir( + &store.join(remote_id).join("node_modules/left-pad"), + "left-pad", + "1.2.0", + ) + .await; + symlink( + format!(".vlt/{remote_id}/node_modules/left-pad"), + nm.join("left-pad"), + ) + .unwrap(); + + let purls: Vec = [ + "pkg:npm/tap@1.0.0", + "pkg:npm/@s/p@1.0.0", + "pkg:npm/zz@1.0.0", + "pkg:npm/y@1.0.0", + "pkg:npm/left-pad@1.2.0", + ] + .iter() + .map(|p| p.to_string()) + .collect(); + let result = NpmCrawler.find_by_purls(&nm, &purls).await.unwrap(); + let paths = |purl: &str| -> Vec { + result + .get(purl) + .map(|found| found.iter().map(|p| p.path.clone()).collect()) + .unwrap_or_default() + }; + assert_eq!(paths("pkg:npm/tap@1.0.0"), vec![tap]); + assert_eq!(paths("pkg:npm/@s/p@1.0.0"), vec![scoped]); + assert_eq!( + paths("pkg:npm/zz@1.0.0"), + vec![store.join("~npm~zz@1.0.0/node_modules/zz")] + ); + assert_eq!(paths("pkg:npm/y@1.0.0"), vec![nm.join("y")]); + assert_eq!(paths("pkg:npm/left-pad@1.2.0"), vec![nm.join("left-pad")]); +} + +/// A git (or remote, `file:`) entry holding a real copy whose package.json +/// says the same `name@version` as a registry copy is an installed copy in +/// its own right: the resolver returns it beside the importer link, while +/// the peer fan-out from the importer link never adds it (it is no peer +/// variant, and the resolver already reports it). +#[cfg(unix)] +#[tokio::test] +#[serial_test::parallel] +async fn find_by_purls_returns_same_version_git_copy_as_its_own_primary() { + use socket_patch_core::crawlers::npm_crawler::find_store_peer_variant_copies; + + let tmp = tempfile::tempdir().unwrap(); + let nm = tmp.path().join("node_modules"); + let store = nm.join(".vlt"); + stage_pkg_dir( + &store.join("~npm~tap@1.0.0/node_modules/tap"), + "tap", + "1.0.0", + ) + .await; + let git_copy = store.join("git~github_cz+tap~v1.0.0/node_modules/tap"); + stage_pkg_dir(&git_copy, "tap", "1.0.0").await; + std::os::unix::fs::symlink(".vlt/~npm~tap@1.0.0/node_modules/tap", nm.join("tap")).unwrap(); + + let purl = "pkg:npm/tap@1.0.0".to_string(); + let result = NpmCrawler + .find_by_purls(&nm, std::slice::from_ref(&purl)) + .await + .unwrap(); + assert_eq!( + result[&purl] + .iter() + .map(|p| p.path.clone()) + .collect::>(), + vec![nm.join("tap"), git_copy] + ); + assert!(find_store_peer_variant_copies(&nm.join("tap")) + .await + .is_empty()); +} + +/// Only a REAL `node_modules/.vlt` directory is a store: one that links to +/// another tree is never followed, by the resolver or by the scan. +#[cfg(unix)] +#[tokio::test] +#[serial_test::parallel] +async fn find_by_purls_and_crawl_all_never_follow_a_linked_vlt_store() { + let tmp = tempfile::tempdir().unwrap(); + let nm = tmp.path().join("node_modules"); + tokio::fs::create_dir_all(&nm).await.unwrap(); + tokio::fs::write(nm.join(".vlt-lock.json"), b"{}") + .await + .unwrap(); + let outside = tempfile::tempdir().unwrap(); + stage_pkg_dir( + &outside.path().join("~npm~x@1.0.0/node_modules/x"), + "x", + "1.0.0", + ) + .await; + std::os::unix::fs::symlink(outside.path(), nm.join(".vlt")).unwrap(); + + let purl = "pkg:npm/x@1.0.0".to_string(); + let result = NpmCrawler + .find_by_purls(&nm, std::slice::from_ref(&purl)) + .await + .unwrap(); + assert!(result.is_empty(), "got {result:?}"); + let scanned = NpmCrawler.crawl_all(&options_at(tmp.path())).await; + assert!(!scanned.iter().any(|p| p.purl == purl), "got {scanned:?}"); +} + /// What the traversal must never read inside `.vlt`: the hoist dir -/// `node_modules` (links plus real `@scope` dirs), the `vlt.json` file, +/// `node_modules` (links plus real `@scope` dirs, skipped whole even when +/// it holds a `node_modules` of its own), the `vlt.json` file, /// `.VLT.DELETE..` rollback staging, an entry dir without its /// own `node_modules`, an entry reached through a link, and an entry's /// `.bin`. Decoy packages sit in each; none may resolve or be scanned. @@ -2760,6 +2942,12 @@ async fn find_by_purls_and_crawl_all_skip_vlt_hoist_dir_and_vlt_json_and_delete_ "9.9.9", ) .await; + stage_pkg_dir( + &store.join("node_modules/node_modules/nested-hoist-decoy"), + "nested-hoist-decoy", + "9.9.9", + ) + .await; stage_pkg_dir( &store.join(".VLT.DELETE.4f2a.~npm~ghost@1.0.0/node_modules/ghost"), "ghost", @@ -2785,6 +2973,7 @@ async fn find_by_purls_and_crawl_all_skip_vlt_hoist_dir_and_vlt_json_and_delete_ let decoys = [ "pkg:npm/hoist-decoy@9.9.9", "pkg:npm/@babel/scoped-decoy@9.9.9", + "pkg:npm/nested-hoist-decoy@9.9.9", "pkg:npm/ghost@1.0.0", "pkg:npm/bare@1.0.0", "pkg:npm/linked@1.0.0", From 44460c1a81c44195afc5a4a2ac27460db2ccf806 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Fri, 25 Sep 2026 22:10:43 -0400 Subject: [PATCH 11/46] Rewrite vlt locks for hosted patches The hosted rewriter now understands vlt-lock.json. For each patched package it points every default-registry node at the Socket-hosted tarball by splicing only the integrity and URL slots, so the DepID, the flags, the trailing slots, the edges and the options stay byte-identical and vlt ci keeps the lock as written. It reads every vlt lock era (no lockfileVersion, 0 with the legacy ids, 1), refuses a lock vlt itself could not read (BOM, unknown version, non-canonical layout), skips named-alias, scoped and jsr registry copies with a warning, and warns when an old vlt would ignore the lock. Rollback, remove and the vendored takeover now revert these edits, including ledgers the depscan PR flow writes: the registry slots go back on the node even after vlt re-laid the line, and a node vlt has re-locked away counts as already reverted. The scan and get commands start reading vlt-lock.json in a later change; until then this is reachable through the shared golden fixtures and the revert paths. Assisted-by: Claude Code:claude-opus-5-5 --- .gitattributes | 3 + .gitignore | 3 + CHANGELOG.md | 8 + crates/socket-patch-cli/CLI_CONTRACT.md | 2 +- .../tests/in_process_rollback_hosted.rs | 63 +- .../src/patch/redirect/mod.rs | 25 +- .../src/patch/redirect/replay.rs | 135 ++- .../src/patch/redirect/state.rs | 65 +- .../src/patch/redirect/takeover.rs | 143 ++- .../src/patch/redirect/vlt.rs | 894 ++++++++++++++ .../vlt/alias-edge/expected-confirmation.json | 7 + .../npm/vlt/alias-edge/expected-edits.json | 10 + .../npm/vlt/alias-edge/expected-warnings.json | 1 + .../npm/vlt/alias-edge/expected/vlt-lock.json | 14 + .../npm/vlt/alias-edge/input/vlt-lock.json | 14 + .../npm/vlt/alias-edge/overrides.json | 13 + .../expected-confirmation.json | 7 + .../expected-edits.json | 10 + .../expected-warnings.json | 1 + .../expected/vlt-lock.json | 16 + .../input/vlt-lock.json | 16 + .../alias-url-equals-registry/overrides.json | 13 + .../npm/vlt/basic/expected-confirmation.json | 7 + .../npm/vlt/basic/expected-edits.json | 10 + .../npm/vlt/basic/expected-warnings.json | 1 + .../npm/vlt/basic/expected/vlt-lock.json | 16 + .../npm/vlt/basic/input/vlt-lock.json | 16 + .../redirect/npm/vlt/basic/overrides.json | 13 + .../expected-confirmation.json | 8 + .../expected-edits.json | 18 + .../expected-warnings.json | 1 + .../expected/vlt-lock.json | 12 + .../input/vlt-lock.json | 12 + .../bins-and-platform-slots/overrides.json | 24 + .../bom-refusal/expected-confirmation.json | 5 + .../npm/vlt/bom-refusal/expected-edits.json | 1 + .../vlt/bom-refusal/expected-warnings.json | 3 + .../npm/vlt/bom-refusal/input/vlt-lock.json | 16 + .../npm/vlt/bom-refusal/overrides.json | 13 + .../expected-confirmation.json | 11 + .../vlt/capture-0.0.0-1/expected-edits.json | 42 + .../capture-0.0.0-1/expected-warnings.json | 5 + .../capture-0.0.0-1/expected/vlt-lock.json | 39 + .../vlt/capture-0.0.0-1/input/vlt-lock.json | 39 + .../npm/vlt/capture-0.0.0-1/overrides.json | 69 ++ .../expected-confirmation.json | 12 + .../vlt/capture-0.0.0-16/expected-edits.json | 50 + .../capture-0.0.0-16/expected-warnings.json | 3 + .../capture-0.0.0-16/expected/vlt-lock.json | 44 + .../vlt/capture-0.0.0-16/input/vlt-lock.json | 44 + .../npm/vlt/capture-0.0.0-16/input/vlt.json | 1 + .../npm/vlt/capture-0.0.0-16/overrides.json | 69 ++ .../expected-confirmation.json | 12 + .../vlt/capture-0.0.0-19/expected-edits.json | 50 + .../capture-0.0.0-19/expected-warnings.json | 1 + .../capture-0.0.0-19/expected/vlt-lock.json | 45 + .../vlt/capture-0.0.0-19/input/vlt-lock.json | 45 + .../npm/vlt/capture-0.0.0-19/input/vlt.json | 1 + .../npm/vlt/capture-0.0.0-19/overrides.json | 69 ++ .../expected-confirmation.json | 12 + .../vlt/capture-0.0.0-32/expected-edits.json | 50 + .../capture-0.0.0-32/expected-warnings.json | 1 + .../capture-0.0.0-32/expected/vlt-lock.json | 43 + .../vlt/capture-0.0.0-32/input/vlt-lock.json | 43 + .../npm/vlt/capture-0.0.0-32/input/vlt.json | 1 + .../npm/vlt/capture-0.0.0-32/overrides.json | 69 ++ .../expected-confirmation.json | 12 + .../capture-1.0.0-rc.14/expected-edits.json | 50 + .../expected-warnings.json | 1 + .../expected/vlt-lock.json | 43 + .../capture-1.0.0-rc.14/input/vlt-lock.json | 43 + .../vlt/capture-1.0.0-rc.14/input/vlt.json | 1 + .../vlt/capture-1.0.0-rc.14/overrides.json | 69 ++ .../expected-confirmation.json | 12 + .../capture-1.0.0-rc.15/expected-edits.json | 50 + .../expected-warnings.json | 1 + .../expected/vlt-lock.json | 43 + .../capture-1.0.0-rc.15/input/vlt-lock.json | 43 + .../vlt/capture-1.0.0-rc.15/input/vlt.json | 1 + .../vlt/capture-1.0.0-rc.15/overrides.json | 69 ++ .../expected-confirmation.json | 12 + .../capture-1.0.0-rc.32/expected-edits.json | 50 + .../expected-warnings.json | 1 + .../expected/vlt-lock.json | 43 + .../capture-1.0.0-rc.32/input/vlt-lock.json | 43 + .../vlt/capture-1.0.0-rc.32/input/vlt.json | 1 + .../vlt/capture-1.0.0-rc.32/overrides.json | 69 ++ .../expected-confirmation.json | 12 + .../capture-1.0.0-rc.33/expected-edits.json | 50 + .../expected-warnings.json | 1 + .../expected/vlt-lock.json | 46 + .../capture-1.0.0-rc.33/input/vlt-lock.json | 46 + .../vlt/capture-1.0.0-rc.33/input/vlt.json | 1 + .../vlt/capture-1.0.0-rc.33/overrides.json | 69 ++ .../expected-confirmation.json | 12 + .../capture-1.0.0-rc.8/expected-edits.json | 50 + .../capture-1.0.0-rc.8/expected-warnings.json | 1 + .../capture-1.0.0-rc.8/expected/vlt-lock.json | 43 + .../capture-1.0.0-rc.8/input/vlt-lock.json | 43 + .../npm/vlt/capture-1.0.0-rc.8/input/vlt.json | 1 + .../npm/vlt/capture-1.0.0-rc.8/overrides.json | 69 ++ .../capture-1.0.10/expected-confirmation.json | 12 + .../vlt/capture-1.0.10/expected-edits.json | 50 + .../vlt/capture-1.0.10/expected-warnings.json | 1 + .../vlt/capture-1.0.10/expected/vlt-lock.json | 46 + .../vlt/capture-1.0.10/input/vlt-lock.json | 46 + .../npm/vlt/capture-1.0.10/input/vlt.json | 1 + .../npm/vlt/capture-1.0.10/overrides.json | 69 ++ .../capture-1.1.1/expected-confirmation.json | 12 + .../npm/vlt/capture-1.1.1/expected-edits.json | 50 + .../vlt/capture-1.1.1/expected-warnings.json | 1 + .../vlt/capture-1.1.1/expected/vlt-lock.json | 46 + .../npm/vlt/capture-1.1.1/input/vlt-lock.json | 46 + .../npm/vlt/capture-1.1.1/input/vlt.json | 1 + .../npm/vlt/capture-1.1.1/overrides.json | 69 ++ .../capture-1.2.0/expected-confirmation.json | 12 + .../npm/vlt/capture-1.2.0/expected-edits.json | 50 + .../vlt/capture-1.2.0/expected-warnings.json | 1 + .../vlt/capture-1.2.0/expected/vlt-lock.json | 46 + .../npm/vlt/capture-1.2.0/input/vlt-lock.json | 46 + .../npm/vlt/capture-1.2.0/input/vlt.json | 1 + .../npm/vlt/capture-1.2.0/overrides.json | 69 ++ .../npm/vlt/crlf/expected-confirmation.json | 7 + .../redirect/npm/vlt/crlf/expected-edits.json | 10 + .../npm/vlt/crlf/expected-warnings.json | 1 + .../npm/vlt/crlf/expected/vlt-lock.json | 16 + .../redirect/npm/vlt/crlf/input/vlt-lock.json | 16 + .../redirect/npm/vlt/crlf/overrides.json | 13 + .../expected-confirmation.json | 5 + .../expected-edits.json | 1 + .../expected-warnings.json | 4 + .../input/vlt-lock.json | 15 + .../custom-registry-skipped/overrides.json | 13 + .../expected-confirmation.json | 7 + .../expected-edits.json | 10 + .../expected-warnings.json | 1 + .../expected/vlt-lock.json | 15 + .../input/vlt-lock.json | 15 + .../vlt/default-registry-alias/overrides.json | 13 + .../expected-confirmation.json | 5 + .../hidden-lock-sentinel/expected-edits.json | 1 + .../expected-warnings.json | 3 + .../input/node_modules/.vlt-lock.json | 0 .../vlt/hidden-lock-sentinel/input/vlt.json | 1 + .../vlt/hidden-lock-sentinel/overrides.json | 13 + .../invalid-json/expected-confirmation.json | 5 + .../npm/vlt/invalid-json/expected-edits.json | 1 + .../vlt/invalid-json/expected-warnings.json | 3 + .../npm/vlt/invalid-json/input/vlt-lock.json | 9 + .../npm/vlt/invalid-json/overrides.json | 13 + .../jsr-skipped/expected-confirmation.json | 7 + .../npm/vlt/jsr-skipped/expected-edits.json | 10 + .../vlt/jsr-skipped/expected-warnings.json | 3 + .../vlt/jsr-skipped/expected/vlt-lock.json | 19 + .../npm/vlt/jsr-skipped/input/vlt-lock.json | 19 + .../npm/vlt/jsr-skipped/overrides.json | 14 + .../expected-confirmation.json | 7 + .../expected-edits.json | 18 + .../expected-warnings.json | 1 + .../expected/vlt-lock.json | 12 + .../input/vlt-lock.json | 12 + .../input/vlt.json | 1 + .../overrides.json | 13 + .../expected-confirmation.json | 7 + .../expected-edits.json | 10 + .../expected-warnings.json | 4 + .../expected/vlt-lock.json | 9 + .../input/vlt-lock.json | 9 + .../overrides.json | 13 + .../expected-confirmation.json | 7 + .../lock-absent-version/expected-edits.json | 10 + .../expected-warnings.json | 3 + .../expected/vlt-lock.json | 9 + .../lock-absent-version/input/vlt-lock.json | 9 + .../vlt/lock-absent-version/input/vlt.json | 1 + .../vlt/lock-absent-version/overrides.json | 13 + .../expected-confirmation.json | 7 + .../expected-edits.json | 10 + .../expected-warnings.json | 3 + .../expected/vlt-lock.json | 10 + .../input/vlt-lock.json | 10 + .../overrides.json | 13 + .../expected-confirmation.json | 7 + .../lock-v0-empty-segment/expected-edits.json | 10 + .../expected-warnings.json | 1 + .../expected/vlt-lock.json | 10 + .../lock-v0-empty-segment/input/vlt-lock.json | 10 + .../vlt/lock-v0-empty-segment/input/vlt.json | 3 + .../vlt/lock-v0-empty-segment/overrides.json | 13 + .../expected-confirmation.json | 7 + .../lock-v0-npm-segment/expected-edits.json | 10 + .../expected-warnings.json | 1 + .../expected/vlt-lock.json | 14 + .../lock-v0-npm-segment/input/vlt-lock.json | 14 + .../vlt/lock-v0-npm-segment/overrides.json | 13 + .../expected-confirmation.json | 7 + .../expected-edits.json | 10 + .../expected-warnings.json | 4 + .../expected/vlt-lock.json | 12 + .../input/vlt-lock.json | 12 + .../overrides.json | 13 + .../lock-v1-3tuple/expected-confirmation.json | 7 + .../vlt/lock-v1-3tuple/expected-edits.json | 10 + .../vlt/lock-v1-3tuple/expected-warnings.json | 1 + .../vlt/lock-v1-3tuple/expected/vlt-lock.json | 12 + .../vlt/lock-v1-3tuple/input/vlt-lock.json | 12 + .../npm/vlt/lock-v1-3tuple/overrides.json | 13 + .../expected-confirmation.json | 7 + .../expected-edits.json | 10 + .../expected-warnings.json | 1 + .../expected/vlt-lock.json | 15 + .../input/vlt-lock.json | 15 + .../lock-v1-both-registry-keys/overrides.json | 13 + .../expected-confirmation.json | 7 + .../expected-edits.json | 10 + .../expected-warnings.json | 1 + .../expected/vlt-lock.json | 15 + .../input/vlt-lock.json | 15 + .../overrides.json | 13 + .../expected-confirmation.json | 5 + .../lock-version-exponent/expected-edits.json | 1 + .../expected-warnings.json | 3 + .../lock-version-exponent/input/vlt-lock.json | 16 + .../vlt/lock-version-exponent/overrides.json | 13 + .../expected-confirmation.json | 5 + .../lock-version-float/expected-edits.json | 1 + .../lock-version-float/expected-warnings.json | 3 + .../lock-version-float/input/vlt-lock.json | 16 + .../npm/vlt/lock-version-float/overrides.json | 13 + .../expected-confirmation.json | 5 + .../lock-version-string/expected-edits.json | 1 + .../expected-warnings.json | 3 + .../lock-version-string/input/vlt-lock.json | 16 + .../vlt/lock-version-string/overrides.json | 13 + .../expected-confirmation.json | 5 + .../expected-edits.json | 1 + .../expected-warnings.json | 3 + .../input/vlt-lock.json | 16 + .../lock-version-unsupported/overrides.json | 13 + .../expected-confirmation.json | 5 + .../lone-surrogate-string/expected-edits.json | 1 + .../expected-warnings.json | 3 + .../lone-surrogate-string/input/vlt-lock.json | 16 + .../vlt/lone-surrogate-string/overrides.json | 13 + .../expected-confirmation.json | 7 + .../mirror-registries-npm/expected-edits.json | 10 + .../expected-warnings.json | 1 + .../expected/vlt-lock.json | 14 + .../mirror-registries-npm/input/vlt-lock.json | 14 + .../vlt/mirror-registries-npm/overrides.json | 13 + .../missing-sha512/expected-confirmation.json | 7 + .../vlt/missing-sha512/expected-edits.json | 1 + .../vlt/missing-sha512/expected-warnings.json | 3 + .../vlt/missing-sha512/input/vlt-lock.json | 16 + .../npm/vlt/missing-sha512/overrides.json | 11 + .../vlt/mixed-eol/expected-confirmation.json | 8 + .../npm/vlt/mixed-eol/expected-edits.json | 18 + .../npm/vlt/mixed-eol/expected-warnings.json | 1 + .../npm/vlt/mixed-eol/expected/vlt-lock.json | 18 + .../npm/vlt/mixed-eol/input/vlt-lock.json | 18 + .../redirect/npm/vlt/mixed-eol/overrides.json | 24 + .../modifier-extra/expected-confirmation.json | 8 + .../vlt/modifier-extra/expected-edits.json | 18 + .../vlt/modifier-extra/expected-warnings.json | 1 + .../vlt/modifier-extra/expected/vlt-lock.json | 19 + .../vlt/modifier-extra/input/vlt-lock.json | 19 + .../npm/vlt/modifier-extra/overrides.json | 24 + .../expected-confirmation.json | 7 + .../vlt/multiple-versions/expected-edits.json | 10 + .../multiple-versions/expected-warnings.json | 1 + .../multiple-versions/expected/vlt-lock.json | 16 + .../vlt/multiple-versions/input/vlt-lock.json | 16 + .../npm/vlt/multiple-versions/overrides.json | 13 + .../expected-confirmation.json | 5 + .../no-lockfile-vlt-json/expected-edits.json | 1 + .../expected-warnings.json | 3 + .../vlt/no-lockfile-vlt-json/input/vlt.json | 7 + .../vlt/no-lockfile-vlt-json/overrides.json | 13 + .../expected-confirmation.json | 5 + .../expected-edits.json | 1 + .../expected-warnings.json | 3 + .../input/vlt-lock.json | 26 + .../overrides.json | 13 + .../expected-confirmation.json | 5 + .../vlt/not-json-object/expected-edits.json | 1 + .../not-json-object/expected-warnings.json | 3 + .../vlt/not-json-object/input/vlt-lock.json | 1 + .../npm/vlt/not-json-object/overrides.json | 13 + .../peer-extras/expected-confirmation.json | 7 + .../npm/vlt/peer-extras/expected-edits.json | 26 + .../vlt/peer-extras/expected-warnings.json | 1 + .../vlt/peer-extras/expected/vlt-lock.json | 21 + .../npm/vlt/peer-extras/input/vlt-lock.json | 21 + .../npm/vlt/peer-extras/overrides.json | 13 + .../expected-confirmation.json | 7 + .../re-redirect-stale-url/expected-edits.json | 10 + .../expected-warnings.json | 1 + .../expected/vlt-lock.json | 14 + .../re-redirect-stale-url/input/vlt-lock.json | 14 + .../vlt/re-redirect-stale-url/overrides.json | 13 + .../expected-confirmation.json | 7 + .../expected-edits.json | 10 + .../expected-warnings.json | 1 + .../expected/vlt-lock.json | 20 + .../input/vlt-lock.json | 20 + .../remote-file-git-untouched/overrides.json | 13 + .../vlt/rerun-noop/expected-confirmation.json | 7 + .../npm/vlt/rerun-noop/expected-edits.json | 1 + .../npm/vlt/rerun-noop/expected-warnings.json | 1 + .../npm/vlt/rerun-noop/input/vlt-lock.json | 14 + .../npm/vlt/rerun-noop/overrides.json | 13 + .../expected-confirmation.json | 7 + .../expected-edits.json | 1 + .../expected-warnings.json | 3 + .../input/vlt-lock.json | 13 + .../overrides.json | 13 + .../expected-confirmation.json | 7 + .../expected-edits.json | 10 + .../expected-warnings.json | 3 + .../expected/vlt-lock.json | 15 + .../input/vlt-lock.json | 15 + .../input/vlt.json | 1 + .../overrides.json | 13 + .../expected-confirmation.json | 7 + .../expected-edits.json | 10 + .../expected-warnings.json | 3 + .../expected/vlt-lock.json | 12 + .../input/vlt-lock.json | 12 + .../scalar-registry-warning/overrides.json | 13 + .../scoped-package/expected-confirmation.json | 7 + .../vlt/scoped-package/expected-edits.json | 18 + .../vlt/scoped-package/expected-warnings.json | 1 + .../vlt/scoped-package/expected/vlt-lock.json | 18 + .../vlt/scoped-package/input/vlt-lock.json | 18 + .../npm/vlt/scoped-package/overrides.json | 14 + .../expected-confirmation.json | 7 + .../expected-edits.json | 10 + .../expected-warnings.json | 3 + .../expected/vlt-lock.json | 19 + .../input/vlt-lock.json | 19 + .../scoped-registry-skipped/overrides.json | 14 + .../expected-confirmation.json | 9 + .../expected-edits.json | 38 + .../expected-warnings.json | 3 + .../expected/package-lock.json | 26 + .../expected/vlt-lock.json | 21 + .../input/node_modules/.vlt-lock.json | 0 .../input/package-lock.json | 26 + .../input/vlt-lock.json | 21 + .../overrides.json | 24 + .../expected-confirmation.json | 7 + .../sibling-package-lock/expected-edits.json | 24 + .../expected-warnings.json | 3 + .../expected/package-lock.json | 26 + .../expected/vlt-lock.json | 16 + .../input/package-lock.json | 26 + .../sibling-package-lock/input/vlt-lock.json | 16 + .../vlt/sibling-package-lock/overrides.json | 13 + .../expected-confirmation.json | 7 + .../expected-edits.json | 16 + .../expected-warnings.json | 4 + .../expected/package-lock.json | 26 + .../input/package-lock.json | 26 + .../input/vlt-lock.json | 21 + .../vlt/sibling-refused-in-vlt/overrides.json | 13 + .../expected-confirmation.json | 7 + .../unsupported-lock-key/expected-edits.json | 1 + .../expected-warnings.json | 3 + .../unsupported-lock-key/input/vlt-lock.json | 21 + .../vlt/unsupported-lock-key/overrides.json | 13 + .../expected-confirmation.json | 7 + .../url-segment-default/expected-edits.json | 10 + .../expected-warnings.json | 1 + .../expected/vlt-lock.json | 15 + .../url-segment-default/input/vlt-lock.json | 15 + .../vlt/url-segment-default/overrides.json | 13 + .../expected-confirmation.json | 7 + .../expected-edits.json | 10 + .../expected-warnings.json | 1 + .../expected/vlt-lock.json | 15 + .../input/vlt-lock.json | 15 + .../url-segment-trailing-slash/overrides.json | 13 + .../expected-confirmation.json | 5 + .../vendored-entry-tgz/expected-edits.json | 1 + .../vendored-entry-tgz/expected-warnings.json | 3 + .../vendored-entry-tgz/input/vlt-lock.json | 14 + .../npm/vlt/vendored-entry-tgz/overrides.json | 13 + .../vendored-entry/expected-confirmation.json | 5 + .../vlt/vendored-entry/expected-edits.json | 1 + .../vlt/vendored-entry/expected-warnings.json | 3 + .../vlt/vendored-entry/input/vlt-lock.json | 14 + .../npm/vlt/vendored-entry/overrides.json | 13 + .../vlt-json-bom/expected-confirmation.json | 7 + .../npm/vlt/vlt-json-bom/expected-edits.json | 10 + .../vlt/vlt-json-bom/expected-warnings.json | 1 + .../vlt/vlt-json-bom/expected/vlt-lock.json | 10 + .../npm/vlt/vlt-json-bom/input/vlt-lock.json | 10 + .../npm/vlt/vlt-json-bom/input/vlt.json | 3 + .../npm/vlt/vlt-json-bom/overrides.json | 13 + .../vlt/workspace/expected-confirmation.json | 7 + .../npm/vlt/workspace/expected-edits.json | 10 + .../npm/vlt/workspace/expected-warnings.json | 1 + .../npm/vlt/workspace/expected/vlt-lock.json | 15 + .../npm/vlt/workspace/input/vlt-lock.json | 15 + .../redirect/npm/vlt/workspace/input/vlt.json | 8 + .../redirect/npm/vlt/workspace/overrides.json | 13 + .../vex-discover-golden/redirect-npm.json | 1041 +++++++++++++++++ .../vex-discover-golden/vlt-locks.json | 98 ++ .../fixtures/vlt-locks/0.0.0-1/vlt-lock.json | 39 + .../fixtures/vlt-locks/0.0.0-16/vlt-lock.json | 44 + .../fixtures/vlt-locks/0.0.0-16/vlt.json | 1 + .../fixtures/vlt-locks/0.0.0-19/vlt-lock.json | 45 + .../fixtures/vlt-locks/0.0.0-19/vlt.json | 1 + .../fixtures/vlt-locks/0.0.0-32/vlt-lock.json | 43 + .../fixtures/vlt-locks/0.0.0-32/vlt.json | 1 + .../vlt-locks/1.0.0-rc.14/vlt-lock.json | 43 + .../fixtures/vlt-locks/1.0.0-rc.14/vlt.json | 1 + .../vlt-locks/1.0.0-rc.15/vlt-lock.json | 43 + .../fixtures/vlt-locks/1.0.0-rc.15/vlt.json | 1 + .../vlt-locks/1.0.0-rc.32/vlt-lock.json | 43 + .../fixtures/vlt-locks/1.0.0-rc.32/vlt.json | 1 + .../vlt-locks/1.0.0-rc.33/vlt-lock.json | 46 + .../fixtures/vlt-locks/1.0.0-rc.33/vlt.json | 1 + .../vlt-locks/1.0.0-rc.8/vlt-lock.json | 43 + .../fixtures/vlt-locks/1.0.0-rc.8/vlt.json | 1 + .../fixtures/vlt-locks/1.0.10/vlt-lock.json | 46 + .../tests/fixtures/vlt-locks/1.0.10/vlt.json | 1 + .../fixtures/vlt-locks/1.1.1/vlt-lock.json | 46 + .../tests/fixtures/vlt-locks/1.1.1/vlt.json | 1 + .../fixtures/vlt-locks/1.2.0/vlt-lock.json | 46 + .../tests/fixtures/vlt-locks/1.2.0/vlt.json | 1 + .../tests/fixtures/vlt-locks/README.md | 28 + .../tests/redirect_golden.rs | 24 + .../tests/redirect_golden_reverse_replay.rs | 220 ++++ crates/socket-patch-core/tests/vlt_locks.rs | 344 ++++++ 435 files changed, 9308 insertions(+), 88 deletions(-) create mode 100644 crates/socket-patch-core/src/patch/redirect/vlt.rs create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/input/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/input/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/input/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/input/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/input/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/input/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/input/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/input/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/input/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/input/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/input/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/input/node_modules/.vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/input/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/input/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/input/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/input/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/input/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/input/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected/package-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/input/node_modules/.vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/input/package-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected/package-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/input/package-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/expected/package-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/input/package-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/input/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/input/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/vex-discover-golden/vlt-locks.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-1/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-16/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-16/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-19/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-19/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-32/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-32/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.14/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.14/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.15/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.15/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.32/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.32/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.33/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.33/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.8/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.8/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.10/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.10/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/1.1.1/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/1.1.1/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/1.2.0/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/1.2.0/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/vlt-locks/README.md create mode 100644 crates/socket-patch-core/tests/redirect_golden_reverse_replay.rs create mode 100644 crates/socket-patch-core/tests/vlt_locks.rs diff --git a/.gitattributes b/.gitattributes index e7f9e267..679f441f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,3 +10,6 @@ crates/socket-patch-core/tests/fixtures/pdm-native/*.lock -text # refuse CRLF by design (vendor_lockfile_crlf_unsupported), and the tests # derive their CRLF variants from the LF bytes themselves. crates/socket-patch-core/tests/fixtures/pnpm-hosted/** -text + +# Captured vlt locks are byte-real; CRLF variants are derived in the tests. +crates/socket-patch-core/tests/fixtures/vlt-locks/** -text diff --git a/.gitignore b/.gitignore index f751c7dc..b3a6faf3 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,9 @@ build/Release # Dependency directories node_modules/ jspm_packages/ +# vlt golden inputs carry an install-state sentinel (commit with `git add -f`) +!crates/socket-patch-core/tests/fixtures/redirect/**/node_modules/ +!crates/socket-patch-core/tests/fixtures/redirect/**/node_modules/.vlt-lock.json # Snowpack dependency directory (https://snowpack.dev/) web_modules/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f192916..2ebc467e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -279,6 +279,14 @@ into the new version's section — see docs/releasing.md. `failed to roll back` for pnpm and vlt alike. `--update` in a vlt project suggests `vlt install @socketsecurity/socket-patch@latest`, and in vlx's cache `vlx -y -- @socketsecurity/socket-patch@latest …`. +- **`rollback`, `remove` and the vendored takeover revert hosted vlt + redirects.** A `redirect_vlt_lock_node` ledger edit (written by the + depscan PR flow, or by `scan --mode hosted` once it rewrites + `vlt-lock.json`) puts the registry integrity and URL back on the node, + keeping whatever vlt re-laid since (a moved comma, a new flag or bins + slot, CRLF re-saved as LF). A node vlt has since re-locked away is + already reverted; any other change refuses with the `vlt-lock.json` + remedy. Peer and modifier variants are claimed per `name@version`. - **`redirect_yarn_berry_mixed_line_endings` and `vendor_yarn_berry_mixed_line_endings`.** A `yarn.lock` (or, vendored, a root `package.json`) that mixes CRLF and LF line endings — or holds a bare diff --git a/crates/socket-patch-cli/CLI_CONTRACT.md b/crates/socket-patch-cli/CLI_CONTRACT.md index 7e89f34f..6168dd2c 100644 --- a/crates/socket-patch-cli/CLI_CONTRACT.md +++ b/crates/socket-patch-cli/CLI_CONTRACT.md @@ -921,7 +921,7 @@ Restore the system but keep the local patch state for a later re-apply: manifest ### Hosted unwind coverage -* **Per-purl reverts** exist for **cargo, golang and the npm family** (`redirect_revert_supported`): staged, fail-closed on drift, and honoring `dry_run` (every inverse and drift check resolves like a wet run; nothing flushes and the ledger is untouched). npm purls on projects with bun-lock edits DEFER to the whole-ledger replay (below) whenever it will run — the scope covers every record, and the replay stages the bun group all-or-nothing. A SCOPED unwind (`rollback `, or `remove ` while other hosted records remain) takes the per-purl revert instead: it claims that purl's `redirect_bun_lock_package` edits by the recorded line's spec (`@` registry spec, or a hosted URL whose tarball leaf is `-.tgz`) and replays them like the yarn/pnpm text kinds (whole-line fragments, CRLF-exact); a sibling version's edit is neither claimed nor a refusal; an edit that mentions the package but is not a bun packages-entry line refuses with the unscoped-`rollback` remedy. Pinned by `tests/in_process_vendor_bun_takeover.rs` (`bun_scoped_rollback_of_one_of_two_hosted_records_unwinds_only_that_purl` and the `remove` twin). Native binary `redirect_bun_lockb_package` snapshots follow the same scoped ownership rule and restore only the claimed package records; unrelated binary resolutions stay intact. yarn lock blocks (`redirect_yarn_berry_entry` / `redirect_yarn_classic_entry`) are recorded in the lock's on-disk line endings and replayed byte-exactly; when a `core.autocrlf` checkout has since flipped the lock's UNIFORM ending (LF ↔ CRLF — the committed ledger keeps its fragments verbatim), this per-purl revert and the whole-ledger replay below match the recorded blocks respelled in the live ending and restore in that ending (v5.0). A lock with mixed endings proves nothing and still refuses as drift. +* **Per-purl reverts** exist for **cargo, golang and the npm family** (`redirect_revert_supported`): staged, fail-closed on drift, and honoring `dry_run` (every inverse and drift check resolves like a wet run; nothing flushes and the ledger is untouched). npm purls on projects with bun-lock edits DEFER to the whole-ledger replay (below) whenever it will run — the scope covers every record, and the replay stages the bun group all-or-nothing. A SCOPED unwind (`rollback `, or `remove ` while other hosted records remain) takes the per-purl revert instead: it claims that purl's `redirect_bun_lock_package` edits by the recorded line's spec (`@` registry spec, or a hosted URL whose tarball leaf is `-.tgz`) and replays them like the yarn/pnpm text kinds (whole-line fragments, CRLF-exact); a sibling version's edit is neither claimed nor a refusal; an edit that mentions the package but is not a bun packages-entry line refuses with the unscoped-`rollback` remedy. Pinned by `tests/in_process_vendor_bun_takeover.rs` (`bun_scoped_rollback_of_one_of_two_hosted_records_unwinds_only_that_purl` and the `remove` twin). Native binary `redirect_bun_lockb_package` snapshots follow the same scoped ownership rule and restore only the claimed package records; unrelated binary resolutions stay intact. yarn lock blocks (`redirect_yarn_berry_entry` / `redirect_yarn_classic_entry`) are recorded in the lock's on-disk line endings and replayed byte-exactly; when a `core.autocrlf` checkout has since flipped the lock's UNIFORM ending (LF ↔ CRLF — the committed ledger keeps its fragments verbatim), this per-purl revert and the whole-ledger replay below match the recorded blocks respelled in the live ending and restore in that ending (v5.0). A lock with mixed endings proves nothing and still refuses as drift. vlt `redirect_vlt_lock_node` edits record entry text (`"": `, no indent, comma or `\r`) and revert slot by slot, in the per-purl revert and the whole-ledger replay alike: the line keyed by the recorded DepID gets the recorded slots [2] and [3] back while it keeps the flags, trailing slots, comma and line ending vlt has written since; a line already at the recorded original, or a DepID vlt has re-locked away with no line still carrying the hosted URL, is already reverted; anything else refuses as drift (remedy: restore the registry pin for the DepID by hand, or re-run `socket-patch scan --mode hosted` and roll back). Per-purl claims are by key: `@` or `@~` (peer and modifier variants). * **Whole-ledger reverse replay** (`revert_remaining_redirect_edits`, core `patch/redirect/replay.rs`) runs whenever the in-scope hosted record set equals the FULL ledger record set — however the scope was spelled (bare `rollback`, `rollback '**'`, an identifier set covering every record; `remove` reuses the same eligibility rule). It walks every remaining ledger edit in reverse write order through a **per-kind inverse table**, staged and committed **per ecosystem group, all-or-nothing**: one drifted, ambiguous (a fragment appearing more than once), or unhandled edit refuses the whole group byte-untouched while other groups proceed. This covers **gem, golang, pypi, composer, bun**, the yarn/pnpm text kinds (normally claimed by the per-purl npm revert first), and the **non-package rideshare edits** — the pnpm `trustLockfile` auto-config (a pristine created scaffold is deleted; a user-modified one keeps the file and loses only the `trustLockfile: true` line, warned as `redirect_pnpm_trust_scaffold_modified`) — plus a "last one out turns off the lights" pass: when the record map empties but non-package edits remain, they are replayed in the same persist, so the trust edit never strands. The npm `.npmrc` `allow-remote=all` auto-config (`redirect_npmrc_allow_remote`) replays in the `npm` group (a pristine created file is deleted; otherwise only the line is removed, warned as `redirect_npmrc_allow_remote_modified` for a modified created file) and is ALSO claimed by the per-purl npm revert of the last package-lock entry, so a scoped unwind never strands it. * **maven and nuget fail closed**: their structured-metadata kinds (`redirect_maven_repository` / `redirect_maven_dep_management` / `redirect_maven_config` / `redirect_maven_trusted_checksums`, `redirect_nuget_source` / `redirect_nuget_lock`) have no revert implementation, so any such edit refuses its whole group (the maven `` suffix rewrite alone IS invertible, but it rides the same all-or-nothing group). The refusal keeps their records + edits in the ledger and names the remedy: re-run `scan --mode hosted` to normalize, or restore the lockfiles from version control. * **Unknown edit kinds fail closed (forward compatibility).** A ledger edit kind this release has no inverse for (written by a newer socket-patch) refuses in the replay's reserved `unknown` group with "the redirect ledger holds a {kind} edit this socket-patch release does not understand; upgrade socket-patch", and every record of every ecosystem is held while that group refuses, so no record is dropped beside an edit it may own. The other groups still unwind on disk and drop their edits; only their records wait until the unknown group clears. A per-purl revert (`rollback `, `remove`, the hosted→vendored takeover) refuses with the same text, and with nothing written, when any unknown `redirect_*` edit's `key`, `original` or `new` names the purl's `@` (at a package-name boundary: `left-pad@1.3.0` does not name `pad@1.3.0`, nor does `@scope/a@1.0.0` name `a@1.0.0`). When that scope covers every record, the whole-ledger replay above still runs after the refusal. The vendored flows' takeover reconcile (`vendor_supersedes_redirect`) drops nothing for such a purl and falls back to the manual advisory. vlt ledgers (`redirect_vlt_lock_node` edits, vendored entries with `flavor: "vlt"`) require the socket-patch release that adds vlt support. The ledger `version` stays 1: compatibility is decided per kind. diff --git a/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs b/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs index bd177041..6c4b80ea 100644 --- a/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs +++ b/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs @@ -854,17 +854,13 @@ async fn scoped_unsupported_ecosystem_fails_closed() { } /// A ledger written by a newer socket-patch carries a hosted edit kind this -/// release has no revert for (`redirect_vlt_lock_node`). A scoped rollback -/// of the purl it names must refuse with nothing written, and an unscoped -/// one must keep the record while that edit survives. -async fn write_vlt_ledger_fixture(root: &Path, with_gem: bool) -> String { - let vlt_new = format!( - "\"~npm~left-pad@1.2.3\": [0,\"left-pad\",\"sha512-PATCHEDpatched==\",\"{LP_HOSTED_URL}\"]" - ); - let vlt_lock = format!( - "{{\n \"lockfileVersion\": 1,\n \"options\": {{}},\n \"nodes\": {{\n {vlt_new}\n }},\n \"edges\": {{\n \"file~_d left-pad\": \"prod 1.2.3 ~npm~left-pad@1.2.3\"\n }}\n}}\n" - ); - std::fs::write(root.join("vlt-lock.json"), &vlt_lock).unwrap(); +/// release has no revert for (`redirect_future_lock_entry`). A scoped +/// rollback of the purl it names must refuse with nothing written, and an +/// unscoped one must keep the record while that edit survives. +async fn write_unknown_kind_ledger_fixture(root: &Path, with_gem: bool) -> String { + let future_new = format!("left-pad@1.2.3 {LP_HOSTED_URL}"); + let future_lock = format!("{future_new}\n"); + std::fs::write(root.join("future.lock"), &future_lock).unwrap(); std::fs::write( root.join("yarn.lock"), yarn_lock_content(&yarn_redirected_block()), @@ -882,17 +878,17 @@ async fn write_vlt_ledger_fixture(root: &Path, with_gem: bool) -> String { edits.push(gem_source_edit()); } edits.push(FileEdit { - path: "vlt-lock.json".to_string(), - kind: "redirect_vlt_lock_node".to_string(), + path: "future.lock".to_string(), + kind: "redirect_future_lock_entry".to_string(), action: "rewritten".to_string(), key: Some("left-pad@1.2.3".to_string()), original: Some(Value::String( - "\"~npm~left-pad@1.2.3\": [0,\"left-pad\",\"sha512-UPSTREAMupstream==\"]".to_string(), + "left-pad@1.2.3 sha512-UPSTREAMupstream==".to_string(), )), - new: Some(Value::String(vlt_new)), + new: Some(Value::String(future_new)), }); write_hosted_ledger(root, records, edits).await; - vlt_lock + future_lock } fn ledger_edit_kinds(root: &Path) -> Vec { @@ -909,7 +905,7 @@ fn ledger_edit_kinds(root: &Path) -> Vec { #[serial] async fn scoped_rollback_refuses_a_purl_named_by_an_unknown_edit_kind() { let tmp = tempfile::tempdir().unwrap(); - let vlt_lock = write_vlt_ledger_fixture(tmp.path(), true).await; + let future_lock = write_unknown_kind_ledger_fixture(tmp.path(), true).await; let ledger_before = std::fs::read(ledger_path(tmp.path())).unwrap(); let yarn_before = std::fs::read_to_string(tmp.path().join("yarn.lock")).unwrap(); @@ -925,10 +921,9 @@ async fn scoped_rollback_refuses_a_purl_named_by_an_unknown_edit_kind() { assert_eq!(failed.len(), 1, "{envelope}"); assert_eq!(failed[0]["purl"], LP_PURL); assert!( - failed[0]["error"] - .as_str() - .unwrap() - .contains("redirect_vlt_lock_node edit this socket-patch release does not understand"), + failed[0]["error"].as_str().unwrap().contains( + "redirect_future_lock_entry edit this socket-patch release does not understand" + ), "{envelope}" ); assert_eq!( @@ -940,8 +935,8 @@ async fn scoped_rollback_refuses_a_purl_named_by_an_unknown_edit_kind() { yarn_before ); assert_eq!( - std::fs::read_to_string(tmp.path().join("vlt-lock.json")).unwrap(), - vlt_lock + std::fs::read_to_string(tmp.path().join("future.lock")).unwrap(), + future_lock ); } @@ -949,7 +944,7 @@ async fn scoped_rollback_refuses_a_purl_named_by_an_unknown_edit_kind() { #[serial] async fn unscoped_rollback_holds_the_record_beside_an_unknown_edit_kind() { let tmp = tempfile::tempdir().unwrap(); - let vlt_lock = write_vlt_ledger_fixture(tmp.path(), true).await; + let future_lock = write_unknown_kind_ledger_fixture(tmp.path(), true).await; let code = rollback_in_process(tmp.path(), Vec::new(), false).await; assert_eq!( @@ -957,8 +952,8 @@ async fn unscoped_rollback_holds_the_record_beside_an_unknown_edit_kind() { "an unknown edit kind must fail the rollback closed" ); assert_eq!( - std::fs::read_to_string(tmp.path().join("vlt-lock.json")).unwrap(), - vlt_lock + std::fs::read_to_string(tmp.path().join("future.lock")).unwrap(), + future_lock ); let ledger: Value = serde_json::from_slice(&std::fs::read(ledger_path(tmp.path())).unwrap()).unwrap(); @@ -966,7 +961,10 @@ async fn unscoped_rollback_holds_the_record_beside_an_unknown_edit_kind() { assert!(ledger["records"].get(GEM_PURL).is_some(), "{ledger}"); // The groups this release understands still unwind on disk; their // records wait for the unknown group to clear. - assert_eq!(ledger_edit_kinds(tmp.path()), ["redirect_vlt_lock_node"]); + assert_eq!( + ledger_edit_kinds(tmp.path()), + ["redirect_future_lock_entry"] + ); assert_eq!( std::fs::read_to_string(tmp.path().join("yarn.lock")).unwrap(), yarn_lock_content(&yarn_original_block()) @@ -984,7 +982,7 @@ async fn unscoped_rollback_holds_the_record_beside_an_unknown_edit_kind() { #[serial] async fn scoped_rollback_of_the_only_record_holds_it_beside_an_unknown_edit_kind() { let tmp = tempfile::tempdir().unwrap(); - let vlt_lock = write_vlt_ledger_fixture(tmp.path(), false).await; + let future_lock = write_unknown_kind_ledger_fixture(tmp.path(), false).await; let (code, envelope) = run_rollback_subprocess(tmp.path(), &[LP_PURL]); assert_eq!(code, 1, "{envelope}"); @@ -1001,8 +999,8 @@ async fn scoped_rollback_of_the_only_record_holds_it_beside_an_unknown_edit_kind .collect(); assert_eq!(failed, [LP_PURL, "group:unknown"], "{envelope}"); assert_eq!( - std::fs::read_to_string(tmp.path().join("vlt-lock.json")).unwrap(), - vlt_lock + std::fs::read_to_string(tmp.path().join("future.lock")).unwrap(), + future_lock ); assert_eq!( std::fs::read_to_string(tmp.path().join("yarn.lock")).unwrap(), @@ -1011,7 +1009,10 @@ async fn scoped_rollback_of_the_only_record_holds_it_beside_an_unknown_edit_kind let ledger: Value = serde_json::from_slice(&std::fs::read(ledger_path(tmp.path())).unwrap()).unwrap(); assert!(ledger["records"].get(LP_PURL).is_some(), "{ledger}"); - assert_eq!(ledger_edit_kinds(tmp.path()), ["redirect_vlt_lock_node"]); + assert_eq!( + ledger_edit_kinds(tmp.path()), + ["redirect_future_lock_entry"] + ); } // --------------------------------------------------------------------------- diff --git a/crates/socket-patch-core/src/patch/redirect/mod.rs b/crates/socket-patch-core/src/patch/redirect/mod.rs index ccfcadac..fd0c6821 100644 --- a/crates/socket-patch-core/src/patch/redirect/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/mod.rs @@ -42,6 +42,7 @@ mod requirements; mod staged; mod state; mod takeover; +pub mod vlt; pub use replay::{revert_remaining_redirect_edits, GroupRefusal, ReplayOutcome}; pub use state::{ drop_superseded_purl, load_redirect_state, persist_redirect_state, save_redirect_state, @@ -219,6 +220,16 @@ pub struct RewriteResult { pub hatch_uuids: std::collections::BTreeSet, pub confirmed_hatch_uuids: std::collections::BTreeSet, pub confirmed_requirements_uuids: std::collections::BTreeSet, + /// Patch uuids every default-registry vlt instance of which carries the + /// patched slots, written by this run or already in place. When vlt + /// drives, npm confirmation keys off this set alone. + pub confirmed_vlt_uuids: std::collections::BTreeSet, + /// Patch uuids the vlt rewriter refused (no sha512, an instance outside + /// the node grammar, a failed residual gate). Never confirmed, whichever + /// lock drives. + pub refused_vlt_uuids: std::collections::BTreeSet, + /// [`vlt::vlt_drives`] over the rewriter's input files. + pub vlt_drives: bool, } /// Combined name as it appears in registry coordinates / lock keys. @@ -340,6 +351,8 @@ pub fn rewrite_registry_redirect_with_pipenv_version( rewrite_yarn_classic(files, overrides, &mut result); rewrite_yarn_berry(files, overrides, &mut result); rewrite_bun_lock(files, overrides, &mut result); + vlt::rewrite_vlt_lock(files, overrides, &mut result); + result.vlt_drives = vlt::vlt_drives(files); requirements::rewrite(files, overrides, &mut result); rewrite_hatch(files, overrides, &mut result); rewrite_uv_lock(files, overrides, python_metadata, &mut result); @@ -449,12 +462,13 @@ fn rewrite_npm_lock( .filter(|f| files.contains_key(*f)) .collect(); if present.is_empty() { - // Another npm-family lock (pnpm — root or nested Rush —, yarn, bun) - // owns the redirect for these deps and its rewriter emits its own - // per-dep diagnostics; warning "no package-lock.json" on every + // Another npm-family lock (pnpm — root or nested Rush —, yarn, bun, + // vlt) owns the redirect for these deps and its rewriter emits its + // own per-dep diagnostics; warning "no package-lock.json" on every // successful pnpm/yarn/bun/Rush run is pure noise that trains users // to ignore the warnings channel. Only warn when NO npm-family - // lockfile exists at all. + // lockfile exists at all. A vlt project without its lock gets + // `redirect_vlt_no_lockfile` from the vlt rewriter instead. let sibling_lock_present = files.keys().any(|k| { k == "yarn.lock" || k == "bun.lock" @@ -463,6 +477,9 @@ fn rewrite_npm_lock( || k.ends_with("/pnpm-lock.yaml") || k == "shrinkwrap.yaml" || k.ends_with("/shrinkwrap.yaml") + || k == crate::constants::npm_family::VLT_LOCK + || k == crate::constants::npm_family::VLT_CONFIG + || k == crate::constants::npm_family::VLT_HIDDEN_LOCK_REL }); if !sibling_lock_present { // Without a lock, the installer-state marker still identifies diff --git a/crates/socket-patch-core/src/patch/redirect/replay.rs b/crates/socket-patch-core/src/patch/redirect/replay.rs index f0c29b37..f47e81f6 100644 --- a/crates/socket-patch-core/src/patch/redirect/replay.rs +++ b/crates/socket-patch-core/src/patch/redirect/replay.rs @@ -91,6 +91,9 @@ enum Inverse { /// surviving (refused) package-lock edit keeps the setting it needs. NpmrcAllowRemote, BunBinaryPackage, + /// A hosted vlt node splice: `original`'s slots [2] and [3] go back on + /// the line keyed by the recorded DepID ([`super::vlt::revert_vlt_slots`]). + VltSlots, /// Owned by a per-purl revert (npm JSON kinds). Present here only /// when that revert failed — refuse the group rather than guess. PerPurlOnly, @@ -131,6 +134,7 @@ fn classify(kind: &str, action: &str) -> (&'static str, Inverse) { } "redirect_bun_lockb_package" => ("bun", Inverse::BunBinaryPackage), "redirect_bun_lock_package" => ("bun", Inverse::ReplaceFragment), + super::vlt::KIND => ("vlt", Inverse::VltSlots), "redirect_gemfile_lock_dependency_pin" | "redirect_gemfile_lock_checksum" | "redirect_gemfile_source_block" => ( @@ -192,7 +196,7 @@ pub(super) fn is_unclassified_kind(kind: &str, action: &str) -> bool { /// lock flavor), so dropping a record beside one would strand that edit. fn groups_for_record_purl(purl: &str) -> &'static [&'static str] { if purl.starts_with("pkg:npm/") { - &["npm", "yarn", "pnpm", "bun", "unknown"] + &["npm", "yarn", "pnpm", "bun", "vlt", "unknown"] } else if purl.starts_with("pkg:cargo/") { &["cargo", "unknown"] } else if purl.starts_with("pkg:gem/") { @@ -528,6 +532,35 @@ pub async fn revert_remaining_redirect_edits( } } } + Inverse::VltSlots => { + let content = match staged_read(&staged, project_root, &edit.path).await { + Ok(Some(content)) => content, + Ok(None) => { + refuse(format!("{} no longer exists", edit.path), &mut outcome); + refused_groups.insert(group); + continue 'group; + } + Err(error) => { + refuse(error, &mut outcome); + refused_groups.insert(group); + continue 'group; + } + }; + match super::vlt::revert_vlt_slots(&content, edit) { + Ok(Some(restored)) => { + staged.insert(edit.path.clone(), Some(restored)); + group_drops.insert(idx); + } + Ok(None) => { + group_drops.insert(idx); + } + Err(reason) => { + refuse(reason, &mut outcome); + refused_groups.insert(group); + continue 'group; + } + } + } Inverse::PerPurlOnly => { refuse( format!( @@ -2033,20 +2066,18 @@ mod tests { assert_eq!(state.edits.len(), 1); } - const VLT_LOCK: &str = "{\n \"lockfileVersion\": 0,\n \"nodes\": {\n \"~npm~minimist@1.2.8\": [0,\"minimist\",\"sha512-p\",\"https://patch.socket.dev/npm/minimist/1.2.8/t/u/minimist-1.2.8.tgz\"]\n },\n \"edges\": {}\n}\n"; + const FUTURE_LOCK: &str = + "minimist@1.2.8 https://patch.socket.dev/npm/minimist/1.2.8/t/u/minimist-1.2.8.tgz\n"; - fn vlt_lock_node_edit() -> FileEdit { + fn future_lock_edit() -> FileEdit { FileEdit { key: Some("minimist@1.2.8".into()), ..edit( - "vlt-lock.json", - "redirect_vlt_lock_node", + "future.lock", + "redirect_future_lock_entry", "rewritten", - Some("\"~npm~minimist@1.2.8\": [0,\"minimist\",\"sha512-r\"]"), - Some( - "\"~npm~minimist@1.2.8\": [0,\"minimist\",\"sha512-p\",\ - \"https://patch.socket.dev/npm/minimist/1.2.8/t/u/minimist-1.2.8.tgz\"]", - ), + Some("minimist@1.2.8 sha512-r"), + Some("minimist@1.2.8 https://patch.socket.dev/npm/minimist/1.2.8/t/u/minimist-1.2.8.tgz"), ) } } @@ -2055,11 +2086,11 @@ mod tests { async fn unclassified_kind_holds_the_npm_record_and_every_other_record() { for dry_run in [true, false] { let dir = TempDir::new().unwrap(); - write(dir.path(), "vlt-lock.json", VLT_LOCK).await; + write(dir.path(), "future.lock", FUTURE_LOCK).await; write(dir.path(), "composer.lock", "https://patch.example/c\n").await; let mut state = state_with( vec![ - vlt_lock_node_edit(), + future_lock_edit(), edit( "composer.lock", "redirect_composer_dist", @@ -2075,13 +2106,13 @@ mod tests { assert_eq!(out.refusals[0].group, "unknown"); assert_eq!( out.refusals[0].reason, - "the redirect ledger holds a redirect_vlt_lock_node edit this socket-patch \ + "the redirect ledger holds a redirect_future_lock_entry edit this socket-patch \ release does not understand; upgrade socket-patch" ); assert!(out.dropped_records.is_empty(), "{out:?}"); assert!(state.records.contains_key("pkg:npm/minimist@1.2.8")); assert!(state.records.contains_key("pkg:composer/v/c@1.0.0")); - assert_eq!(read(dir.path(), "vlt-lock.json").await, VLT_LOCK); + assert_eq!(read(dir.path(), "future.lock").await, FUTURE_LOCK); let kinds: Vec<&str> = state.edits.iter().map(|e| e.kind.as_str()).collect(); // The composer group still unwinds on disk; only its record waits // for the unknown group to clear. @@ -2090,17 +2121,82 @@ mod tests { read(dir.path(), "composer.lock").await, "https://patch.example/c\n" ); - assert_eq!(kinds, ["redirect_vlt_lock_node", "redirect_composer_dist"]); + assert_eq!( + kinds, + ["redirect_future_lock_entry", "redirect_composer_dist"] + ); } else { assert_eq!( read(dir.path(), "composer.lock").await, "https://packagist.example/c\n" ); - assert_eq!(kinds, ["redirect_vlt_lock_node"]); + assert_eq!(kinds, ["redirect_future_lock_entry"]); } } } + const VLT_REGISTRY_ENTRY: &str = + "\"~npm~minimist@1.2.8~peer.1\": [2,\"minimist\",\"sha512-r\"]"; + const VLT_HOSTED_ENTRY: &str = "\"~npm~minimist@1.2.8~peer.1\": [2,\"minimist\",\"sha512-p\",\"https://patch.socket.dev/npm/minimist/1.2.8/t/u/minimist-1.2.8.tgz\"]"; + + fn vlt_lock(entry: &str) -> String { + format!( + "{{\r\n \"lockfileVersion\": 1,\r\n \"nodes\": {{\r\n {entry},\r\n \"~npm~zz@1.0.0\": [0,\"zz\",\"sha512-z\"]\r\n }}\r\n}}\r\n" + ) + } + + fn vlt_edit() -> FileEdit { + FileEdit { + key: Some("minimist@1.2.8~peer.1".into()), + ..edit( + "vlt-lock.json", + super::super::vlt::KIND, + "rewritten", + Some(VLT_REGISTRY_ENTRY), + Some(VLT_HOSTED_ENTRY), + ) + } + } + + #[tokio::test] + async fn vlt_node_edits_replay_by_slots_and_drop_the_npm_record() { + let dir = TempDir::new().unwrap(); + write(dir.path(), "vlt-lock.json", &vlt_lock(VLT_HOSTED_ENTRY)).await; + let mut state = state_with(vec![vlt_edit()], &["pkg:npm/minimist@1.2.8"]); + let out = revert_remaining_redirect_edits(dir.path(), &mut state, false).await; + assert!(out.fully_reverted(), "{out:?}"); + assert_eq!( + read(dir.path(), "vlt-lock.json").await, + vlt_lock(VLT_REGISTRY_ENTRY) + ); + assert!(state.edits.is_empty() && state.records.is_empty()); + assert_eq!(out.dropped_records, ["pkg:npm/minimist@1.2.8"]); + } + + #[tokio::test] + async fn a_drifted_vlt_edit_holds_the_npm_record() { + let dir = TempDir::new().unwrap(); + let drifted = VLT_HOSTED_ENTRY.replace("sha512-p", "sha512-x"); + write(dir.path(), "vlt-lock.json", &vlt_lock(&drifted)).await; + let mut state = state_with(vec![vlt_edit()], &["pkg:npm/minimist@1.2.8"]); + let out = revert_remaining_redirect_edits(dir.path(), &mut state, false).await; + assert_eq!(out.refusals.len(), 1, "{out:?}"); + assert_eq!(out.refusals[0].group, "vlt"); + assert!(out.refusals[0].reason.contains("drifted"), "{out:?}"); + assert_eq!(read(dir.path(), "vlt-lock.json").await, vlt_lock(&drifted)); + assert_eq!(state.edits.len(), 1); + assert!(state.records.contains_key("pkg:npm/minimist@1.2.8")); + } + + #[tokio::test] + async fn a_vlt_edit_whose_lock_is_gone_refuses() { + let dir = TempDir::new().unwrap(); + let mut state = state_with(vec![vlt_edit()], &["pkg:npm/minimist@1.2.8"]); + let out = revert_remaining_redirect_edits(dir.path(), &mut state, false).await; + assert_eq!(out.refusals[0].reason, "vlt-lock.json no longer exists"); + assert!(state.records.contains_key("pkg:npm/minimist@1.2.8")); + } + #[test] fn every_ecosystem_group_list_includes_the_unknown_group() { for purl in [ @@ -2116,7 +2212,11 @@ mod tests { ] { assert!(groups_for_record_purl(purl).contains(&"unknown"), "{purl}"); } - assert!(is_unclassified_kind("redirect_vlt_lock_node", "rewritten")); + assert!(is_unclassified_kind( + "redirect_future_lock_entry", + "rewritten" + )); + assert!(!is_unclassified_kind("redirect_vlt_lock_node", "rewritten")); assert!(!is_unclassified_kind( "redirect_bun_lock_package", "rewritten" @@ -2580,6 +2680,7 @@ mod tests { ("redirect_yarn_berry_entry", "rewritten"), ("redirect_bun_lock_package", "rewritten"), ("redirect_bun_lockb_package", "rewritten"), + ("redirect_vlt_lock_node", "rewritten"), ("redirect_gemfile_lock_dependency_pin", "rewritten"), ("redirect_gemfile_lock_dependency_pin", "added"), ("redirect_gemfile_lock_checksum", "rewritten"), diff --git a/crates/socket-patch-core/src/patch/redirect/state.rs b/crates/socket-patch-core/src/patch/redirect/state.rs index 420ecde9..0bad12b7 100644 --- a/crates/socket-patch-core/src/patch/redirect/state.rs +++ b/crates/socket-patch-core/src/patch/redirect/state.rs @@ -376,11 +376,12 @@ pub fn drop_superseded_purl(state: &mut RedirectState, purl: &str) -> bool { return false; }; // Version-exact instance keys: `name@version`, pnpm v6 peer-suffixed - // `name@version(peer…)`, pnpm v5 respelled `name@version_peer…`. + // `name@version(peer…)`, pnpm v5 respelled `name@version_peer…`, vlt + // peer/modifier variants `name@version~extra`. let version_exact = key == name_at_version || key .strip_prefix(name_at_version.as_str()) - .is_some_and(|rest| rest.starts_with('(') || rest.starts_with('_')); + .is_some_and(|rest| rest.starts_with(['(', '_', '~'])); // Artifact anchor: the edit's rewritten (`new`) content references // this purl's hosted artifact (its patch uuid — spelling-invariant // across raw / `\/`-escaped / percent-encoded URL forms). @@ -841,18 +842,14 @@ mod tests { ); } - fn vlt_node_edit(name: &str, version: &str, url: &str) -> FileEdit { + fn future_lock_edit(name: &str, version: &str, url: &str) -> FileEdit { FileEdit { - path: "vlt-lock.json".to_string(), - kind: "redirect_vlt_lock_node".to_string(), + path: "future.lock".to_string(), + kind: "redirect_future_lock_entry".to_string(), action: "rewritten".to_string(), key: Some(format!("{name}@{version}")), - original: Some(serde_json::json!(format!( - "\"~npm~{name}@{version}\": [0,\"{name}\",\"sha512-r\"]" - ))), - new: Some(serde_json::json!(format!( - "\"~npm~{name}@{version}\": [0,\"{name}\",\"sha512-p\",\"{url}\"]" - ))), + original: Some(serde_json::json!(format!("{name}@{version} sha512-r"))), + new: Some(serde_json::json!(format!("{name}@{version} {url}"))), } } @@ -860,7 +857,7 @@ mod tests { fn drop_superseded_purl_drops_nothing_beside_an_unclassified_edit_naming_it() { let url = hosted_url("left-pad", "1.3.0", SAMPLE_UUID); let unanchored = hosted_url("left-pad", "1.3.0", "0e0e0e0e-0000-4000-8000-000000000000"); - for (with_record, vlt_key, vlt_url) in [ + for (with_record, future_key, future_url) in [ (true, "left-pad@1.3.0", url.as_str()), (false, "left-pad@1.3.0", url.as_str()), (false, "left-pad@1.3.0~custom", unanchored.as_str()), @@ -879,8 +876,8 @@ mod tests { &url, ), FileEdit { - key: Some(vlt_key.to_string()), - ..vlt_node_edit("left-pad", "1.3.0", vlt_url) + key: Some(future_key.to_string()), + ..future_lock_edit("left-pad", "1.3.0", future_url) }, ]; let before = serde_json::to_value(&state).unwrap(); @@ -888,7 +885,7 @@ mod tests { assert_eq!( serde_json::to_value(&state).unwrap(), before, - "{with_record} {vlt_key}" + "{with_record} {future_key}" ); } } @@ -925,12 +922,12 @@ mod tests { "redirect_pnpm_resolution", Some("pad@1.3.0"), ), - vlt_node_edit( + future_lock_edit( "left-pad", "1.3.0", &hosted_url("left-pad", "1.3.0", "0e0e0e0e-0000-4000-8000-000000000000"), ), - vlt_node_edit( + future_lock_edit( "@scope/pad", "1.3.0", &hosted_url( @@ -943,7 +940,39 @@ mod tests { assert!(drop_superseded_purl(&mut state, "pkg:npm/pad@1.3.0")); assert!(state.records.is_empty()); let kinds: Vec<&str> = state.edits.iter().map(|e| e.kind.as_str()).collect(); - assert_eq!(kinds, ["redirect_vlt_lock_node", "redirect_vlt_lock_node"]); + assert_eq!( + kinds, + ["redirect_future_lock_entry", "redirect_future_lock_entry"] + ); + } + + #[test] + fn drop_superseded_purl_claims_vlt_variant_keys() { + let mut state = RedirectState::new(); + state.edits = vec![ + edit( + "vlt-lock.json", + "redirect_vlt_lock_node", + Some("left-pad@1.3.0"), + ), + edit( + "vlt-lock.json", + "redirect_vlt_lock_node", + Some("left-pad@1.3.0~peer.0df72515a50372ba"), + ), + edit( + "vlt-lock.json", + "redirect_vlt_lock_node", + Some("left-pad@1.3.0-rc.1~peer.1"), + ), + ]; + assert!(drop_superseded_purl(&mut state, "pkg:npm/left-pad@1.3.0")); + let keys: Vec<&str> = state + .edits + .iter() + .filter_map(|e| e.key.as_deref()) + .collect(); + assert_eq!(keys, ["left-pad@1.3.0-rc.1~peer.1"]); } /// A version-boundary key (`left-pad@1.3.10`) and a different package diff --git a/crates/socket-patch-core/src/patch/redirect/takeover.rs b/crates/socket-patch-core/src/patch/redirect/takeover.rs index 18d71993..54163894 100644 --- a/crates/socket-patch-core/src/patch/redirect/takeover.rs +++ b/crates/socket-patch-core/src/patch/redirect/takeover.rs @@ -621,10 +621,11 @@ fn cargo_declared_req(fragment: Option<&Value>) -> Option { /// The npm-family text-fragment edit kinds CLAIMED BY KEY: `original`/`new` /// hold the whole lock fragment as a string, the edit's `key` embeds /// `@`, and the revert is a `replacen(new, original)`. -const NPM_TEXT_KINDS: [&str; 3] = [ +const NPM_TEXT_KINDS: [&str; 4] = [ "redirect_yarn_classic_entry", "redirect_yarn_berry_entry", "redirect_pnpm_resolution", + super::vlt::KIND, ]; /// The bun hosted rewriter's edit kind (`rewrite_bun_lock`): `original`/`new` @@ -636,8 +637,9 @@ const NPM_TEXT_KINDS: [&str; 3] = [ /// (`elems[0]`), the field the rewriter itself matched on. const BUN_TEXT_KIND: &str = "redirect_bun_lock_package"; -/// Does this edit kind replay as a whole text fragment -/// (`content.replacen(new, original, 1)`, fail-closed on drift)? +/// Does this edit kind replay as a text fragment, fail-closed on drift? +/// Most replace the whole fragment (`content.replacen(new, original, 1)`); +/// vlt node edits revert slot by slot ([`super::vlt::revert_vlt_slots`]). fn replays_as_text_fragment(kind: &str) -> bool { NPM_TEXT_KINDS.contains(&kind) || kind == BUN_TEXT_KIND } @@ -829,6 +831,7 @@ pub async fn revert_npm_redirect_purl( for (i, e) in state.edits.iter().enumerate() { let key = e.key.as_deref().unwrap_or_default(); let claimed = match e.kind.as_str() { + super::vlt::KIND => super::vlt::claims_key(key, &name, &version), k if NPM_TEXT_KINDS.contains(&k) => { key == lock_key || (k == "redirect_pnpm_resolution" @@ -971,6 +974,13 @@ pub async fn revert_npm_redirect_purl( edit.path )); }; + if edit.kind == super::vlt::KIND { + if let Some(restored) = super::vlt::revert_vlt_slots(&content, edit)? { + staged.insert(edit.path.clone(), Some(restored)); + out.reverted_files.push(edit.path.clone()); + } + continue; + } // A yarn block recorded on a CRLF checkout, replayed on an LF // one (or the reverse — `core.autocrlf` re-spells the lock on // every OS switch, never the committed ledger): when neither @@ -2472,27 +2482,126 @@ mod tests { assert!(state.edits.is_empty(), "edits dropped"); } - fn vlt_lock_node_edit(name: &str, version: &str) -> FileEdit { + fn future_lock_edit(name: &str, version: &str) -> FileEdit { FileEdit { - path: "vlt-lock.json".into(), - kind: "redirect_vlt_lock_node".into(), + path: "future.lock".into(), + kind: "redirect_future_lock_entry".into(), action: "rewritten".into(), key: Some(format!("{name}@{version}")), - original: Some(Value::String(format!( - "\"~npm~{name}@{version}\": [0,\"{name}\",\"sha512-r\"]" - ))), - new: Some(Value::String(format!( - "\"~npm~{name}@{version}\": [0,\"{name}\",\"sha512-p\",\"{NPM_URL}\"]" + original: Some(Value::String(format!("{name}@{version} sha512-r"))), + new: Some(Value::String(format!("{name}@{version} {NPM_URL}"))), + } + } + + fn vlt_lock(entries: &[String]) -> String { + let body: Vec = entries.iter().map(|e| format!(" {e}")).collect(); + format!( + "{{\n \"lockfileVersion\": 1,\n \"nodes\": {{\n{}\n }},\n \"edges\": {{}}\n}}\n", + body.join(",\n") + ) + } + + fn vlt_entry(id: &str, slots: &str) -> String { + format!("\"{id}\": [0,\"left-pad\",{slots}]") + } + + fn vlt_node_edit(key: &str, id: &str) -> FileEdit { + FileEdit { + path: "vlt-lock.json".into(), + kind: super::super::vlt::KIND.into(), + action: "rewritten".into(), + key: Some(key.into()), + original: Some(Value::String(vlt_entry(id, "\"sha512-r\""))), + new: Some(Value::String(vlt_entry( + id, + &format!("\"sha512-p\",\"{NPM_URL}\""), ))), } } + #[tokio::test] + async fn npm_vlt_takeover_claims_every_variant_by_key_and_leaves_other_versions() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let hosted = format!("\"sha512-p\",\"{NPM_URL}\""); + let wired = vlt_lock(&[ + vlt_entry("·npm·left-pad@1.3.0·%E1%B9%97%3A3", &hosted), + vlt_entry("~npm~left-pad@1.3.0", &hosted), + vlt_entry("~npm~left-pad@1.3.0-rc.1", &hosted), + vlt_entry("~npm~left-pad@1.3.0~peer.2", &hosted), + ]); + tokio::fs::write(root.join("vlt-lock.json"), &wired) + .await + .unwrap(); + let mut state = RedirectState::new(); + state.records.insert(NPM_PURL.into(), record()); + state.edits = vec![ + vlt_node_edit( + "left-pad@1.3.0~%E1%B9%97%3A3", + "·npm·left-pad@1.3.0·%E1%B9%97%3A3", + ), + vlt_node_edit("left-pad@1.3.0", "~npm~left-pad@1.3.0"), + vlt_node_edit("left-pad@1.3.0-rc.1", "~npm~left-pad@1.3.0-rc.1"), + vlt_node_edit("left-pad@1.3.0~peer.2", "~npm~left-pad@1.3.0~peer.2"), + ]; + revert_redirect_purl(root, &mut state, NPM_PURL, false) + .await + .expect("revert succeeds"); + let registry = "\"sha512-r\""; + assert_eq!( + tokio::fs::read_to_string(root.join("vlt-lock.json")) + .await + .unwrap(), + vlt_lock(&[ + vlt_entry("·npm·left-pad@1.3.0·%E1%B9%97%3A3", registry), + vlt_entry("~npm~left-pad@1.3.0", registry), + vlt_entry("~npm~left-pad@1.3.0-rc.1", &hosted), + vlt_entry("~npm~left-pad@1.3.0~peer.2", registry), + ]) + ); + let keys: Vec<&str> = state + .edits + .iter() + .filter_map(|e| e.key.as_deref()) + .collect(); + assert_eq!(keys, ["left-pad@1.3.0-rc.1"]); + assert!(state.records.is_empty()); + } + + #[tokio::test] + async fn npm_vlt_takeover_refuses_a_drifted_line_untouched() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let wired = vlt_lock(&[vlt_entry("~npm~left-pad@1.3.0", "\"sha512-other\"")]); + tokio::fs::write(root.join("vlt-lock.json"), &wired) + .await + .unwrap(); + let mut state = RedirectState::new(); + state.records.insert(NPM_PURL.into(), record()); + state.edits = vec![vlt_node_edit("left-pad@1.3.0", "~npm~left-pad@1.3.0")]; + let before = state.clone(); + let err = revert_redirect_purl(root, &mut state, NPM_PURL, false) + .await + .unwrap_err(); + assert!(err.contains("drifted from the recorded redirect"), "{err}"); + assert_eq!( + serde_json::to_value(&state).unwrap(), + serde_json::to_value(&before).unwrap() + ); + assert_eq!( + tokio::fs::read_to_string(root.join("vlt-lock.json")) + .await + .unwrap(), + wired + ); + } + #[tokio::test] async fn npm_unclassified_edit_naming_the_purl_refuses_the_claim_untouched() { for dry_run in [true, false] { let (tmp, mut state) = npm_redirected_fixture("yarn.lock", &classic_pristine()).await; let root = tmp.path(); - state.edits.push(vlt_lock_node_edit("left-pad", "1.3.0")); + state.edits.push(future_lock_edit("left-pad", "1.3.0")); let wired = tokio::fs::read_to_string(root.join("yarn.lock")) .await .unwrap(); @@ -2502,7 +2611,7 @@ mod tests { .unwrap_err(); assert_eq!( err, - "the redirect ledger holds a redirect_vlt_lock_node edit this socket-patch \ + "the redirect ledger holds a redirect_future_lock_entry edit this socket-patch \ release does not understand; upgrade socket-patch" ); assert_eq!( @@ -2523,24 +2632,24 @@ mod tests { async fn npm_unclassified_edit_for_another_package_does_not_block_the_claim() { let (tmp, mut state) = npm_redirected_fixture("yarn.lock", &classic_pristine()).await; let root = tmp.path(); - state.edits.push(vlt_lock_node_edit("left-pad", "1.3.1")); + state.edits.push(future_lock_edit("left-pad", "1.3.1")); revert_redirect_purl(root, &mut state, NPM_PURL, false) .await .expect("revert succeeds"); assert_eq!(state.edits.len(), 1); - assert_eq!(state.edits[0].kind, "redirect_vlt_lock_node"); + assert_eq!(state.edits[0].kind, "redirect_future_lock_entry"); } #[tokio::test] async fn npm_unclassified_edit_for_a_longer_or_scoped_name_does_not_block_the_claim() { for other in ["long-left-pad", "@scope/left-pad"] { let (tmp, mut state) = npm_redirected_fixture("yarn.lock", &classic_pristine()).await; - state.edits.push(vlt_lock_node_edit(other, "1.3.0")); + state.edits.push(future_lock_edit(other, "1.3.0")); revert_redirect_purl(tmp.path(), &mut state, NPM_PURL, false) .await .unwrap_or_else(|e| panic!("{other}: {e}")); assert_eq!(state.edits.len(), 1, "{other}"); - assert_eq!(state.edits[0].kind, "redirect_vlt_lock_node", "{other}"); + assert_eq!(state.edits[0].kind, "redirect_future_lock_entry", "{other}"); } } diff --git a/crates/socket-patch-core/src/patch/redirect/vlt.rs b/crates/socket-patch-core/src/patch/redirect/vlt.rs new file mode 100644 index 00000000..36f3b921 --- /dev/null +++ b/crates/socket-patch-core/src/patch/redirect/vlt.rs @@ -0,0 +1,894 @@ +//! vlt `vlt-lock.json` hosted rewriter, the byte-for-byte twin of the TS +//! `registry-rewrite/vlt.ts`. +//! +//! A default-registry node keeps its DepID and has only slot [2] (the +//! patched sha512) and slot [3] (the hosted URL) spliced into its one-line +//! tuple; nothing else in the lock changes. The ledger records entry text +//! (`"": `, no indent, comma or `\r`), because vlt moves +//! commas and re-lays flags and trailing slots on later saves, so the +//! revert ([`revert_vlt_slots`]) works slot by slot. + +use std::collections::BTreeMap; + +use serde_json::{Map, Value}; + +use super::{full_name, DepOverride, FileEdit, RewriteResult, RewriteWarning}; +use crate::constants::npm_family::{ + BUN_LOCK, BUN_LOCKB, NPM_LOCKS, PNPM_LOCK, VLT_CONFIG, VLT_HIDDEN_LOCK_REL, VLT_LOCK, +}; +use crate::vendor::vlt_lock_text::{ + entry_text, is_default_registry, nodes_block, parse_node_entry_text, parse_node_line, + parse_vendored_path, render_entry_line, render_tuple_with_slots, sniff_lock, split_dep_id, + split_lines, DepIdKind, LockSniff, NodeEntry, ParsedLock, SectionSpan, +}; + +/// The ledger kind of a hosted vlt node splice. +pub const KIND: &str = "redirect_vlt_lock_node"; + +/// Every other npm-family lock whose presence makes `vlt-lock.json` +/// ambiguous as the install driver. +const SIBLING_LOCKS: [&str; 6] = [ + NPM_LOCKS[1], + NPM_LOCKS[0], + "yarn.lock", + PNPM_LOCK, + BUN_LOCK, + BUN_LOCKB, +]; + +/// Does vlt drive hosted confirmation and the artifact preflight? +/// `vlt-lock.json` must be present, and either vlt's install state (the +/// `node_modules/.vlt-lock.json` sentinel) is too, or no other npm-family +/// lock is. Otherwise both locks are rewritten and neither decides alone. +pub fn vlt_drives(files: &BTreeMap) -> bool { + files.contains_key(VLT_LOCK) + && (files.contains_key(VLT_HIDDEN_LOCK_REL) + || !SIBLING_LOCKS.iter().any(|lock| files.contains_key(*lock))) +} + +fn lock_unsupported(detail: &str) -> RewriteWarning { + RewriteWarning { + code: "redirect_vlt_lock_unsupported".into(), + detail: format!( + "vlt-lock.json {detail}; re-save it with a current vlt (`vlt install`) or update \ + socket-patch" + ), + } +} + +/// A lock that passed the lock-level parse, with its nodes section located. +struct HostedLock { + parsed: ParsedLock, + nodes: Option, +} + +fn parse_hosted_lock(text: &str) -> Result { + let parsed = match sniff_lock(text) { + LockSniff::Readable(parsed) => parsed, + LockSniff::Bom => { + return Err(lock_unsupported( + "starts with a UTF-8 BOM, which vlt cannot read", + )) + } + LockSniff::NotJsonObject => return Err(lock_unsupported("is not a JSON object")), + LockSniff::UnsupportedVersion(raw) => { + return Err(lock_unsupported(&format!("has lockfileVersion {raw}"))) + } + }; + let lines = split_lines(text); + let nodes = nodes_block(&lines); + let one_node_per_line = nodes.is_some_and(|span| { + matches!(span, SectionSpan::Inline { .. }) + || span + .entry_lines() + .any(|i| parse_node_line(lines[i]).is_some()) + }); + if !one_node_per_line && parsed.nodes().is_some_and(|n| !n.is_empty()) { + return Err(lock_unsupported( + "nodes section is not in vlt's canonical layout", + )); + } + Ok(HostedLock { parsed, nodes }) +} + +/// The lock-level refusal alone, run before any vendored vlt entry is +/// reverted for a hosted takeover. An absent lock passes. +pub fn preflight_vlt_hosted(files: &BTreeMap) -> Result<(), RewriteWarning> { + match files.get(VLT_LOCK) { + Some(text) => parse_hosted_lock(text).map(|_| ()), + None => Ok(()), + } +} + +/// Is `id` a registry node of `name@version`, and is its segment the +/// default registry? `None` for any other node. +fn registry_instance( + id: &str, + name: &str, + version: &str, + options: Option<&Map>, +) -> Option { + let dep_id = split_dep_id(id)?; + (dep_id.registry_identity()? == (name, version)) + .then(|| is_default_registry(&dep_id.first, options)) +} + +fn is_old_lockfile_ignored(lock: &HostedLock, files: &BTreeMap) -> bool { + if lock.parsed.version == Some(1) { + return false; + } + let options = lock.parsed.options(); + let has_legacy_default = lock.parsed.nodes().is_some_and(|nodes| { + nodes.keys().any(|id| { + split_dep_id(id).is_some_and(|dep_id| { + dep_id.kind == DepIdKind::Registry + && dep_id.first != "npm" + && is_default_registry(&dep_id.first, options) + }) + }) + }); + let declares_modifiers = files.get(VLT_CONFIG).is_some_and(|text| { + let text = text.strip_prefix('\u{feff}').unwrap_or(text); + serde_json::from_str::(text) + .ok() + .and_then(|v| v.as_object().map(|o| o.contains_key("modifiers"))) + .unwrap_or(false) + }); + has_legacy_default && !declares_modifiers +} + +fn is_scalar_registry_ignored(lock: &HostedLock) -> bool { + let options = lock.parsed.options(); + let scalar = options + .and_then(|o| o.get("registry")) + .is_some_and(Value::is_string); + let registries_npm = options + .and_then(|o| o.get("registries")) + .and_then(|r| r.get("npm")) + .is_some_and(Value::is_string); + scalar && (lock.parsed.version != Some(1) || !registries_npm) +} + +fn lock_level_warnings(lock: &HostedLock, files: &BTreeMap) -> Vec { + let mut warnings = Vec::new(); + if lock.parsed.version.is_none() { + warnings.push(RewriteWarning { + code: "redirect_vlt_lockfile_version_missing".into(), + detail: "vlt-lock.json has no lockfileVersion; vlt ≥ 1.0.0-rc.15 silently \ + re-resolves it on `vlt install` (and `vlt ci` fails); re-lock with a \ + current vlt" + .into(), + }); + } + if is_old_lockfile_ignored(lock, files) { + warnings.push(RewriteWarning { + code: "redirect_vlt_old_lockfile_ignored".into(), + detail: "vlt 0.0.0-16 … 0.0.0-24 ignore vlt-lock.json unless vlt.json declares \ + \"modifiers\": {}; upgrade vlt or add \"modifiers\": {} to vlt.json" + .into(), + }); + } + if is_scalar_registry_ignored(lock) { + warnings.push(RewriteWarning { + code: "redirect_vlt_scalar_registry_ignored".into(), + detail: "vlt 1.0.0-rc.7 … rc.29 ignore vlt-lock.json when a scalar `registry` is \ + configured; upgrade vlt to ≥ 1.0.0-rc.30" + .into(), + }); + } + if !vlt_drives(files) { + let others: Vec<&str> = SIBLING_LOCKS + .iter() + .copied() + .filter(|lock| files.contains_key(*lock)) + .collect(); + warnings.push(RewriteWarning { + code: "redirect_vlt_sibling_lockfiles".into(), + detail: format!( + "vlt-lock.json and {} are both present; socket-patch rewrote both — delete the \ + lock your installs do not use", + others.join(", ") + ), + }); + } + warnings +} + +fn npm_purl(name: &str, version: &str) -> String { + format!("pkg:npm/{}@{version}", name.replacen('@', "%40", 1)) +} + +/// A `file` node of socket-patch's vendored vlt shape for `name@version`. +fn has_vendored_node(nodes: &Map, name: &str, version: &str) -> bool { + nodes.iter().any(|(id, tuple)| { + let slot1 = tuple.get(1).and_then(Value::as_str); + split_dep_id(id).is_some_and(|dep_id| { + dep_id.kind == DepIdKind::File + && slot1 == Some(name) + && parse_vendored_path(&dep_id.first, name).is_some_and(|p| p.version == version) + }) + }) +} + +/// The ledger key of an instance: `@`, plus `~` and the raw +/// extra segment for a peer or modifier variant, in either era. +fn ledger_key(name: &str, version: &str, extra: Option<&str>) -> String { + match extra { + Some(extra) => format!("{name}@{version}~{extra}"), + None => format!("{name}@{version}"), + } +} + +/// Does a ledger edit of [`KIND`] belong to `name@version`? Claims are by +/// key, with a `~` boundary before a variant's extra segment. +pub(crate) fn claims_key(key: &str, name: &str, version: &str) -> bool { + let base = format!("{name}@{version}"); + key.strip_prefix(base.as_str()) + .is_some_and(|rest| rest.is_empty() || rest.starts_with('~')) +} + +/// The one nodes-section line index keyed `id` that parses under the node +/// grammar with slot [1] naming `name`; `None` when there are zero or +/// several lines with that key, or the one line is outside the grammar. +fn instance_line( + lines: &[String], + span: Option, + id: &str, + name: &str, +) -> Option { + let span = span?; + let prefix = format!(" \"{id}\": "); + let mut found = span + .entry_lines() + .filter(|&i| lines[i].starts_with(prefix.as_str())); + let idx = found.next()?; + if found.next().is_some() { + return None; + } + let line = parse_node_line(&lines[idx])?; + (line.entry.key == id && line.entry.name().as_deref() == Some(name)).then_some(idx) +} + +/// The residual gate: re-parsed, every instance carries the patched slots. +fn every_instance_pinned(text: &str, ids: &[&str], sha512: &str, url: &str) -> Option<()> { + let json: Value = serde_json::from_str(text).ok()?; + let nodes = json.get("nodes")?.as_object()?; + ids.iter() + .all(|id| { + let tuple = nodes.get(*id).and_then(Value::as_array); + tuple.is_some_and(|t| { + t.get(2).and_then(Value::as_str) == Some(sha512) + && t.get(3).and_then(Value::as_str) == Some(url) + }) + }) + .then_some(()) +} + +struct Splice { + line: usize, + text: String, + edit: FileEdit, +} + +fn unsupported_key(result: &mut RewriteResult, dep: &DepOverride, id: &str) { + result.warnings.push(RewriteWarning { + code: "redirect_vlt_unsupported_lock_key".into(), + detail: format!( + "vlt-lock.json entry {id} cannot be rewritten safely; re-save the lock with `vlt \ + install`" + ), + }); + result.refused_vlt_uuids.insert(dep.patch_uuid.clone()); +} + +/// Rewrite one override's default-registry instances, or none of them. +/// Returns whether any line changed. +fn rewrite_dep( + lock: &HostedLock, + lines: &mut [String], + dep: &DepOverride, + result: &mut RewriteResult, +) -> bool { + let name = full_name(dep); + let version = dep.version.as_str(); + let options = lock.parsed.options(); + let empty = Map::new(); + let nodes = lock.parsed.nodes().unwrap_or(&empty); + + let mut defaults: Vec<&str> = Vec::new(); + let mut foreign: Vec<&str> = Vec::new(); + for id in nodes.keys() { + match registry_instance(id, &name, version, options) { + Some(true) => defaults.push(id), + Some(false) => foreign.push(id), + None => {} + } + } + if !foreign.is_empty() { + result.warnings.push(RewriteWarning { + code: "redirect_vlt_custom_registry_skipped".into(), + detail: format!( + "hosted mode only redirects packages from vlt's default registry; {} left \ + unchanged (use --mode vendored or agent mode)", + foreign.join(", ") + ), + }); + } + let Some(sha512) = dep.integrity.sha512.as_deref().filter(|s| !s.is_empty()) else { + result.warnings.push(RewriteWarning { + code: "redirect_vlt_missing_sha512".into(), + detail: format!( + "hosted artifact for {} has no sha512 integrity; retry later", + npm_purl(&name, version) + ), + }); + result.refused_vlt_uuids.insert(dep.patch_uuid.clone()); + return false; + }; + if defaults.is_empty() { + let warning = if has_vendored_node(nodes, &name, version) { + RewriteWarning { + code: "redirect_vlt_entry_vendored".into(), + detail: format!( + "{name}@{version} is vendored; re-run `socket-patch scan --mode hosted` from \ + a ledger that owns it, or `socket-patch vendor --revert`" + ), + } + } else { + RewriteWarning { + code: "redirect_vlt_entry_not_found".into(), + detail: format!( + "vlt-lock.json has no default-registry entry for {name}@{version}; run `vlt \ + install` first" + ), + } + }; + result.warnings.push(warning); + return false; + } + + let mut located: Vec<(usize, &str)> = Vec::new(); + for id in &defaults { + match instance_line(lines, lock.nodes, id, &name) { + Some(idx) => located.push((idx, id)), + None => { + unsupported_key(result, dep, id); + return false; + } + } + } + located.sort_unstable(); + + let s2 = serde_json::to_string(sha512).expect("a str serializes to JSON infallibly"); + let s3 = serde_json::to_string(&dep.artifact_url).expect("a str serializes to JSON infallibly"); + let mut splices: Vec = Vec::new(); + for &(idx, id) in &located { + let line = parse_node_line(&lines[idx]).expect("instance_line parsed this line"); + let elems = &line.entry.elems; + if elems.len() >= 4 && elems[2] == s2 && elems[3] == s3 { + continue; + } + let tuple = render_tuple_with_slots(elems, Some(&s2), Some(&s3)); + let new_text = entry_text(id, &tuple); + let extra = split_dep_id(id).and_then(|dep_id| dep_id.extra); + splices.push(Splice { + line: idx, + text: render_entry_line(&new_text, line.comma, line.cr), + edit: FileEdit { + path: VLT_LOCK.into(), + kind: KIND.into(), + action: "rewritten".into(), + key: Some(ledger_key(&name, version, extra.as_deref())), + original: Some(Value::String(line.entry.entry_text())), + new: Some(Value::String(new_text)), + }, + }); + } + + let mut candidate: Vec = lines.to_vec(); + for splice in &splices { + candidate[splice.line].clone_from(&splice.text); + } + let ids: Vec<&str> = located.iter().map(|(_, id)| *id).collect(); + if every_instance_pinned(&candidate.join("\n"), &ids, sha512, &dep.artifact_url).is_none() { + unsupported_key(result, dep, ids[0]); + return false; + } + + result.confirmed_vlt_uuids.insert(dep.patch_uuid.clone()); + let changed = !splices.is_empty(); + for splice in splices { + lines[splice.line] = splice.text; + result.edits.push(splice.edit); + } + changed +} + +/// The hosted vlt rewrite (DESIGN §3.2–§3.8): lock-level refusal and +/// advisories, then each npm override's default-registry instances. +pub(super) fn rewrite_vlt_lock( + files: &BTreeMap, + overrides: &[DepOverride], + result: &mut RewriteResult, +) { + let npm: Vec<&DepOverride> = overrides.iter().filter(|o| o.ecosystem == "npm").collect(); + if npm.is_empty() { + return; + } + let Some(text) = files.get(VLT_LOCK) else { + if files.contains_key(VLT_CONFIG) || files.contains_key(VLT_HIDDEN_LOCK_REL) { + result.warnings.push(RewriteWarning { + code: "redirect_vlt_no_lockfile".into(), + detail: "vlt project has no vlt-lock.json; run `vlt install` and commit \ + vlt-lock.json" + .into(), + }); + } + return; + }; + let lock = match parse_hosted_lock(text) { + Ok(lock) => lock, + Err(warning) => { + result.warnings.push(warning); + return; + } + }; + result.warnings.extend(lock_level_warnings(&lock, files)); + + let mut lines: Vec = split_lines(text).into_iter().map(str::to_string).collect(); + let mut changed = false; + for dep in npm { + changed |= rewrite_dep(&lock, &mut lines, dep, result); + } + if changed { + result.files.insert(VLT_LOCK.into(), lines.join("\n")); + } +} + +// ── revert ─────────────────────────────────────────────────────────────── + +fn slot_value(entry: &NodeEntry<'_>, index: usize) -> Option { + match entry.slot(index) { + None | Some("null") => None, + Some(raw) => serde_json::from_str(raw).ok(), + } +} + +fn same_slots(a: &NodeEntry<'_>, b: &NodeEntry<'_>) -> bool { + slot_value(a, 2) == slot_value(b, 2) && slot_value(a, 3) == slot_value(b, 3) +} + +fn drift(id: &str, why: &str) -> String { + format!( + "vlt-lock.json {why}; restore the registry pin for {id} manually, or re-run \ + `socket-patch scan --mode hosted` and then roll back" + ) +} + +/// Undo one [`KIND`] edit by slots. The line keyed by the recorded DepID +/// gets `original`'s slots [2] and [3] back while its current flags, +/// trailing slots, indent, comma and `\r` stay, so a lock vlt re-laid since +/// the rewrite still reverts. `Ok(None)` when there is nothing to revert: +/// the line already holds `original`'s slots, or the DepID and the hosted +/// URL are both gone (a re-lock). Anything else is drift. +pub(crate) fn revert_vlt_slots(text: &str, edit: &FileEdit) -> Result, String> { + fn fragment(v: &Option) -> Option<&str> { + v.as_ref().and_then(Value::as_str) + } + let (Some(original), Some(new)) = (fragment(&edit.original), fragment(&edit.new)) else { + return Err(format!("{KIND} edit is missing its recorded fragments")); + }; + let (Some(original), Some(new)) = (parse_node_entry_text(original), parse_node_entry_text(new)) + else { + return Err(format!( + "{KIND} edit records fragments that are not vlt node entries" + )); + }; + if original.key != new.key { + return Err(format!("{KIND} edit records two different DepIDs")); + } + let id = original.key; + let lines = split_lines(text); + let Some(span) = nodes_block(&lines) else { + return Err(drift(id, "nodes section is not in vlt's canonical layout")); + }; + let prefix = format!(" \"{id}\": "); + let keyed: Vec = span + .entry_lines() + .filter(|&i| lines[i].starts_with(prefix.as_str())) + .collect(); + let url = slot_value(&new, 3); + match keyed.as_slice() { + [] => { + let url_left = url + .as_ref() + .and_then(Value::as_str) + .is_some_and(|url| lines.iter().any(|line| line.contains(url))); + if url_left { + Err(drift( + id, + &format!("no longer has {id}, but still pins its hosted URL"), + )) + } else { + Ok(None) + } + } + [idx] => { + let Some(line) = parse_node_line(lines[*idx]) else { + return Err(drift( + id, + &format!("entry {id} is outside vlt's node grammar"), + )); + }; + if same_slots(&line.entry, &original) { + return Ok(None); + } + if !same_slots(&line.entry, &new) { + return Err(drift( + id, + &format!("entry {id} drifted from the recorded redirect"), + )); + } + let tuple = render_tuple_with_slots( + &line.entry.elems, + original.slot(2).filter(|s| *s != "null"), + original.slot(3).filter(|s| *s != "null"), + ); + let mut out: Vec = lines.iter().map(|l| (*l).to_string()).collect(); + out[*idx] = render_entry_line(&entry_text(id, &tuple), line.comma, line.cr); + Ok(Some(out.join("\n"))) + } + _ => Err(drift(id, &format!("has {id} more than once"))), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const SHA: &str = "sha512-PATCHED=="; + const URL: &str = "https://patch.socket.dev/patch/npm/t/u/left-pad-1.3.0.tgz"; + const REG_SHA: &str = "sha512-REGISTRY=="; + const REG_URL: &str = "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"; + + fn files(entries: &[(&str, &str)]) -> BTreeMap { + entries + .iter() + .map(|(k, v)| ((*k).to_string(), (*v).to_string())) + .collect() + } + + fn dep(name: &str, version: &str, sha512: Option<&str>) -> DepOverride { + serde_json::from_value(serde_json::json!({ + "ecosystem": "npm", + "name": name, + "version": version, + "token": "t", + "patchUuid": format!("uuid-{name}"), + "artifactUrl": URL, + "integrity": { "sha512": sha512 }, + })) + .unwrap() + } + + fn lock_with(node_lines: &[&str]) -> String { + let mut out = + String::from("{\n \"lockfileVersion\": 1,\n \"options\": {},\n \"nodes\": {\n"); + for (i, line) in node_lines.iter().enumerate() { + out.push_str(" "); + out.push_str(line); + if i + 1 < node_lines.len() { + out.push(','); + } + out.push('\n'); + } + out.push_str(" },\n \"edges\": {}\n}\n"); + out + } + + fn rewrite(lock: &str, deps: &[DepOverride]) -> RewriteResult { + let mut result = RewriteResult::default(); + rewrite_vlt_lock(&files(&[(VLT_LOCK, lock)]), deps, &mut result); + result + } + + fn codes(result: &RewriteResult) -> Vec<&str> { + result.warnings.iter().map(|w| w.code.as_str()).collect() + } + + fn vlt_edit(original: &str, new: &str) -> FileEdit { + FileEdit { + path: VLT_LOCK.into(), + kind: KIND.into(), + action: "rewritten".into(), + key: Some("left-pad@1.3.0".into()), + original: Some(Value::String(original.into())), + new: Some(Value::String(new.into())), + } + } + + const ID: &str = "~npm~left-pad@1.3.0"; + + fn registry_entry() -> String { + format!("\"{ID}\": [0,\"left-pad\",\"{REG_SHA}\",\"{REG_URL}\"]") + } + + fn hosted_entry() -> String { + format!("\"{ID}\": [0,\"left-pad\",\"{SHA}\",\"{URL}\"]") + } + + #[test] + fn vlt_drives_needs_the_lock_and_the_sentinel_or_no_sibling() { + let sentinel = (VLT_HIDDEN_LOCK_REL, ""); + let lock = (VLT_LOCK, "{}"); + assert!(!vlt_drives(&files(&[]))); + assert!(!vlt_drives(&files(&[sentinel, (VLT_CONFIG, "{}")]))); + assert!(vlt_drives(&files(&[lock]))); + assert!(vlt_drives(&files(&[lock, (VLT_CONFIG, "{}")]))); + for sibling in SIBLING_LOCKS { + let other = (sibling, "x"); + assert!(!vlt_drives(&files(&[lock, other])), "{sibling}"); + assert!(vlt_drives(&files(&[lock, other, sentinel])), "{sibling}"); + } + assert!(!vlt_drives(&files(&[ + lock, + ("package-lock.json", "x"), + ("bun.lockb", "x") + ]))); + assert!(vlt_drives(&files(&[ + lock, + ("packages/a/package-lock.json", "x") + ]))); + } + + #[test] + fn ledger_keys_carry_the_raw_extra_after_a_tilde() { + let lock = lock_with(&[ + &format!("\"·npm·left-pad@1.3.0·%E1%B9%97%3A3\": [0,\"left-pad\",\"{REG_SHA}\"]"), + &format!("\"~npm~left-pad@1.3.0~_croot_s_g_s#x\": [0,\"left-pad\",\"{REG_SHA}\"]"), + &format!("\"~npm~left-pad@1.3.0~peer.2\": [0,\"left-pad\",\"{REG_SHA}\"]"), + &format!("\"{ID}\": [0,\"left-pad\",\"{REG_SHA}\"]"), + ]); + let result = rewrite(&lock, &[dep("left-pad", "1.3.0", Some(SHA))]); + let keys: Vec<&str> = result + .edits + .iter() + .map(|e| e.key.as_deref().unwrap()) + .collect(); + assert_eq!( + keys, + [ + "left-pad@1.3.0~%E1%B9%97%3A3", + "left-pad@1.3.0~_croot_s_g_s#x", + "left-pad@1.3.0~peer.2", + "left-pad@1.3.0", + ] + ); + for key in keys { + assert!(claims_key(key, "left-pad", "1.3.0"), "{key}"); + } + assert!(result.confirmed_vlt_uuids.contains("uuid-left-pad")); + } + + #[test] + fn claims_stop_at_the_version_boundary() { + assert!(claims_key("@s/p@1.0.0", "@s/p", "1.0.0")); + assert!(claims_key("@s/p@1.0.0~peer.1", "@s/p", "1.0.0")); + for foreign in [ + "left-pad@1.3.01", + "left-pad@1.3.0-rc.1", + "left-pad@1.3.0(peer)", + "left-pad@1.3.0_x", + "long-left-pad@1.3.0", + "left-pad@1.3", + ] { + assert!(!claims_key(foreign, "left-pad", "1.3.0"), "{foreign}"); + } + } + + #[test] + fn residual_gate_refuses_when_the_parsed_node_is_not_the_spliced_line() { + let entry = format!(" \"{ID}\": [0,\"left-pad\",\"{REG_SHA}\"]"); + let lock = format!( + "{{\n \"lockfileVersion\": 1,\n \"nodes\": {{\n{entry}\n }},\n \"nodes\": {{\n{entry}\n }}\n}}\n" + ); + let result = rewrite(&lock, &[dep("left-pad", "1.3.0", Some(SHA))]); + assert_eq!(codes(&result), ["redirect_vlt_unsupported_lock_key"]); + assert!(result.files.is_empty() && result.edits.is_empty()); + assert!(result.refused_vlt_uuids.contains("uuid-left-pad")); + assert!(result.confirmed_vlt_uuids.is_empty()); + } + + #[test] + fn an_unsupported_instance_refuses_every_instance_of_the_dep_only() { + let lock = format!( + "{{\n \"lockfileVersion\": 1,\n \"nodes\": {{\n \"{ID}\": [0,\"left-pad\",\"{REG_SHA}\"],\n \"~npm~left-pad@1.3.0~peer.1\": [0, \"left-pad\"],\n \"~npm~ms@2.1.3\": [0,\"ms\",\"{REG_SHA}\"]\n }}\n}}\n" + ); + let result = rewrite( + &lock, + &[ + dep("left-pad", "1.3.0", Some(SHA)), + dep("ms", "2.1.3", Some(SHA)), + ], + ); + assert_eq!(codes(&result), ["redirect_vlt_unsupported_lock_key"]); + assert_eq!(result.edits.len(), 1); + assert_eq!(result.edits[0].key.as_deref(), Some("ms@2.1.3")); + assert!(result.refused_vlt_uuids.contains("uuid-left-pad")); + assert!(result.confirmed_vlt_uuids.contains("uuid-ms")); + } + + #[test] + fn a_name_mismatch_in_slot_one_is_unsupported() { + let lock = lock_with(&[&format!("\"{ID}\": [0,\"other\",\"{REG_SHA}\"]")]); + let result = rewrite(&lock, &[dep("left-pad", "1.3.0", Some(SHA))]); + assert_eq!(codes(&result), ["redirect_vlt_unsupported_lock_key"]); + } + + #[test] + fn lock_level_parse_refusals() { + let refused = |text: &str| { + preflight_vlt_hosted(&files(&[(VLT_LOCK, text)])) + .unwrap_err() + .detail + }; + assert!(refused("\u{feff}{}").contains("UTF-8 BOM")); + assert!(refused("[]").contains("not a JSON object")); + assert!(refused("{\"lockfileVersion\": 1.0}").contains("lockfileVersion 1.0")); + let pretty = format!( + "{{\n \"lockfileVersion\": 1,\n \"nodes\": {{\n \"{ID}\": [\n 0,\n \"left-pad\"\n ]\n }}\n}}\n" + ); + assert!(refused(&pretty).contains("canonical layout")); + assert!(preflight_vlt_hosted(&files(&[])).is_ok()); + assert!(preflight_vlt_hosted(&files(&[(VLT_LOCK, "{\"nodes\": {}}")])).is_ok()); + let ok = lock_with(&[®istry_entry()]); + assert!(preflight_vlt_hosted(&files(&[(VLT_LOCK, &ok)])).is_ok()); + } + + #[test] + fn missing_sha512_and_empty_sha512_refuse() { + let lock = lock_with(&[®istry_entry()]); + for sha in [None, Some("")] { + let result = rewrite(&lock, &[dep("left-pad", "1.3.0", sha)]); + assert_eq!(codes(&result), ["redirect_vlt_missing_sha512"]); + assert!(result.refused_vlt_uuids.contains("uuid-left-pad")); + } + } + + #[test] + fn non_npm_overrides_leave_everything_alone() { + let mut other = dep("left-pad", "1.3.0", Some(SHA)); + other.ecosystem = "pypi".into(); + let mut result = RewriteResult::default(); + rewrite_vlt_lock(&files(&[(VLT_CONFIG, "{}")]), &[other], &mut result); + assert!(result.warnings.is_empty()); + } + + fn revert(lock: &str, edit: &FileEdit) -> Result, String> { + revert_vlt_slots(lock, edit) + } + + #[test] + fn revert_restores_the_slots_after_a_comma_move() { + let edit = vlt_edit(®istry_entry(), &hosted_entry()); + let lone = lock_with(&[&hosted_entry()]); + assert_eq!( + revert(&lone, &edit).unwrap().unwrap(), + lock_with(&[®istry_entry()]) + ); + let sibling = "\"~npm~zz@1.0.0\": [0,\"zz\",\"sha512-z\"]"; + let moved = lock_with(&[&hosted_entry(), sibling]); + assert_eq!( + revert(&moved, &edit).unwrap().unwrap(), + lock_with(&[®istry_entry(), sibling]) + ); + } + + #[test] + fn revert_keeps_a_changed_flag_and_new_trailing_slots() { + let edit = vlt_edit(®istry_entry(), &hosted_entry()); + let relaid = lock_with(&[&format!( + "\"{ID}\": [2,\"left-pad\",\"{SHA}\",\"{URL}\",null,null,null,null,{{ \"lp\": \"bin.js\"}}]" + )]); + assert_eq!( + revert(&relaid, &edit).unwrap().unwrap(), + lock_with(&[&format!( + "\"{ID}\": [2,\"left-pad\",\"{REG_SHA}\",\"{REG_URL}\",null,null,null,null,{{ \"lp\": \"bin.js\"}}]" + )]) + ); + } + + #[test] + fn revert_of_a_three_tuple_original() { + let original = format!("\"{ID}\": [0,\"left-pad\",\"{REG_SHA}\"]"); + let edit = vlt_edit(&original, &hosted_entry()); + assert_eq!( + revert(&lock_with(&[&hosted_entry()]), &edit) + .unwrap() + .unwrap(), + lock_with(&[&original]) + ); + let five = lock_with(&[&format!( + "\"{ID}\": [1,\"left-pad\",\"{SHA}\",\"{URL}\",\"lib\"]" + )]); + assert_eq!( + revert(&five, &edit).unwrap().unwrap(), + lock_with(&[&format!( + "\"{ID}\": [1,\"left-pad\",\"{REG_SHA}\",null,\"lib\"]" + )]) + ); + } + + #[test] + fn revert_after_an_lf_resave_of_a_crlf_lock() { + let edit = vlt_edit(®istry_entry(), &hosted_entry()); + let crlf = lock_with(&[&hosted_entry()]).replace('\n', "\r\n"); + assert_eq!( + revert(&crlf, &edit).unwrap().unwrap(), + lock_with(&[®istry_entry()]).replace('\n', "\r\n") + ); + let lf = lock_with(&[&hosted_entry()]); + assert_eq!( + revert(&lf, &edit).unwrap().unwrap(), + lock_with(&[®istry_entry()]) + ); + } + + #[test] + fn revert_already_reverted() { + let edit = vlt_edit(®istry_entry(), &hosted_entry()); + assert_eq!(revert(&lock_with(&[®istry_entry()]), &edit), Ok(None)); + let relaid = lock_with(&[&format!( + "\"{ID}\": [2,\"left-pad\",\"{REG_SHA}\",\"{REG_URL}\"]" + )]); + assert_eq!(revert(&relaid, &edit), Ok(None)); + let relocked = lock_with(&[&format!( + "\"~npm~left-pad@1.3.0~peer.1\": [0,\"left-pad\",\"{REG_SHA}\",\"{REG_URL}\"]" + )]); + assert_eq!(revert(&relocked, &edit), Ok(None)); + } + + #[test] + fn revert_refuses_drift() { + let edit = vlt_edit(®istry_entry(), &hosted_entry()); + let other = lock_with(&[&format!( + "\"{ID}\": [0,\"left-pad\",\"sha512-OTHER==\",\"{URL}\"]" + )]); + assert!(revert(&other, &edit).unwrap_err().contains("drifted")); + let moved = lock_with(&[&format!( + "\"~npm~left-pad@1.3.0~peer.1\": [0,\"left-pad\",\"{SHA}\",\"{URL}\"]" + )]); + let err = revert(&moved, &edit).unwrap_err(); + assert!(err.contains("still pins its hosted URL"), "{err}"); + assert!(err.contains("restore the registry pin for ~npm~left-pad@1.3.0 manually")); + let twice = format!( + "{{\n \"nodes\": {{\n {},\n {}\n }}\n}}\n", + hosted_entry(), + hosted_entry() + ); + assert!(revert(&twice, &edit) + .unwrap_err() + .contains("more than once")); + let multi = format!("{{\n \"nodes\": {{\n \"{ID}\": [\n 0\n ]\n }}\n}}\n"); + assert!(revert(&multi, &edit).unwrap_err().contains("node grammar")); + assert!(revert("{\"nodes\": {}}", &edit) + .unwrap_err() + .contains("canonical")); + } + + #[test] + fn revert_refuses_a_malformed_ledger_edit() { + let mut edit = vlt_edit(®istry_entry(), &hosted_entry()); + edit.original = None; + assert!(revert(&lock_with(&[&hosted_entry()]), &edit).is_err()); + let edit = vlt_edit("not an entry", &hosted_entry()); + assert!(revert(&lock_with(&[&hosted_entry()]), &edit).is_err()); + let edit = vlt_edit( + &format!("\"~npm~other@1.0.0\": [0,\"other\",\"{REG_SHA}\"]"), + &hosted_entry(), + ); + assert!(revert(&lock_with(&[&hosted_entry()]), &edit) + .unwrap_err() + .contains("two different DepIDs")); + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/expected-edits.json new file mode 100644 index 00000000..5a1e33aa --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\",\"https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz\"]", + "new": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/expected/vlt-lock.json new file mode 100644 index 00000000..8a73d87c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/expected/vlt-lock.json @@ -0,0 +1,14 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d lp-alias": "prod npm:left-pad@1.3.0 ~npm~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/input/vlt-lock.json new file mode 100644 index 00000000..880d850a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/input/vlt-lock.json @@ -0,0 +1,14 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d lp-alias": "prod npm:left-pad@1.3.0 ~npm~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-edge/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/expected-edits.json new file mode 100644 index 00000000..bb1e0d02 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~acme~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"~acme~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/expected/vlt-lock.json new file mode 100644 index 00000000..4a6fe722 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/expected/vlt-lock.json @@ -0,0 +1,16 @@ +{ + "lockfileVersion": 1, + "options": { + "registry": "https://registry.example.com", + "registries": { + "acme": "https://registry.example.com", + "npm": "https://registry.example.com/" + } + }, + "nodes": { + "~acme~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod acme:left-pad@1.3.0 ~acme~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/input/vlt-lock.json new file mode 100644 index 00000000..1afb400d --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/input/vlt-lock.json @@ -0,0 +1,16 @@ +{ + "lockfileVersion": 1, + "options": { + "registry": "https://registry.example.com", + "registries": { + "acme": "https://registry.example.com", + "npm": "https://registry.example.com/" + } + }, + "nodes": { + "~acme~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="] + }, + "edges": { + "file~_d left-pad": "prod acme:left-pad@1.3.0 ~acme~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/alias-url-equals-registry/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/expected-edits.json new file mode 100644 index 00000000..5a1e33aa --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\",\"https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz\"]", + "new": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/expected/vlt-lock.json new file mode 100644 index 00000000..db1afaba --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/expected/vlt-lock.json @@ -0,0 +1,16 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/input/vlt-lock.json new file mode 100644 index 00000000..e81e65c3 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/input/vlt-lock.json @@ -0,0 +1,16 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/basic/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/expected-confirmation.json new file mode 100644 index 00000000..eea9f40c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/expected-confirmation.json @@ -0,0 +1,8 @@ +{ + "confirmed": [ + "00000000-0000-4000-8000-000000000000", + "ffffffff-ffff-4fff-8fff-ffffffffffff" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/expected-edits.json new file mode 100644 index 00000000..b76e7094 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/expected-edits.json @@ -0,0 +1,18 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "semver@7.6.0", + "original": "\"~npm~semver@7.6.0\": [0,\"semver\",\"sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==\",null,null,null,null,null,{ \"semver\": \"bin/semver.js\"}]", + "new": "\"~npm~semver@7.6.0\": [0,\"semver\",\"sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz\",null,null,null,null,{ \"semver\": \"bin/semver.js\"}]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "fsevents@2.3.3", + "original": "\"~npm~fsevents@2.3.3\": [1,\"fsevents\",\"sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==\",null,null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]", + "new": "\"~npm~fsevents@2.3.3\": [1,\"fsevents\",\"sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz\",null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/expected/vlt-lock.json new file mode 100644 index 00000000..bef3ee6a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/expected/vlt-lock.json @@ -0,0 +1,12 @@ +{ + "lockfileVersion": 1, + "options": {}, + "nodes": { + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz",null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~semver@7.6.0": [0,"semver","sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}] + }, + "edges": { + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/input/vlt-lock.json new file mode 100644 index 00000000..0e9093de --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/input/vlt-lock.json @@ -0,0 +1,12 @@ +{ + "lockfileVersion": 1, + "options": {}, + "nodes": { + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",null,null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",null,null,null,null,null,{ "semver": "bin/semver.js"}] + }, + "edges": { + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/overrides.json new file mode 100644 index 00000000..c73060b3 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bins-and-platform-slots/overrides.json @@ -0,0 +1,24 @@ +[ + { + "ecosystem": "npm", + "name": "semver", + "version": "7.6.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "ffffffff-ffff-4fff-8fff-ffffffffffff", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz", + "integrity": { + "sha512": "sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==" + } + }, + { + "ecosystem": "npm", + "name": "fsevents", + "version": "2.3.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "00000000-0000-4000-8000-000000000000", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz", + "integrity": { + "sha512": "sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/expected-confirmation.json new file mode 100644 index 00000000..473f0bc6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/expected-confirmation.json @@ -0,0 +1,5 @@ +{ + "confirmed": [], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/expected-edits.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/expected-edits.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/expected-warnings.json new file mode 100644 index 00000000..5331bd1a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_lock_unsupported" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/input/vlt-lock.json new file mode 100644 index 00000000..165bf9ac --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/input/vlt-lock.json @@ -0,0 +1,16 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/bom-refusal/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/expected-confirmation.json new file mode 100644 index 00000000..60ce7a3c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/expected-confirmation.json @@ -0,0 +1,11 @@ +{ + "confirmed": [ + "00000000-0000-4000-8000-000000000000", + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "ffffffff-ffff-4fff-8fff-ffffffffffff" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/expected-edits.json new file mode 100644 index 00000000..a1749b56 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/expected-edits.json @@ -0,0 +1,42 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "@isaacs/string-locale-compare@1.1.0", + "original": "\"··@isaacs§string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==\"]", + "new": "\"··@isaacs§string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "use-sync-external-store@1.2.0", + "original": "\"··use-sync-external-store@1.2.0\": [0,\"use-sync-external-store\",\"sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==\"]", + "new": "\"··use-sync-external-store@1.2.0\": [0,\"use-sync-external-store\",\"sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "semver@7.6.0", + "original": "\"··semver@7.6.0\": [0,\"semver\",\"sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==\"]", + "new": "\"··semver@7.6.0\": [0,\"semver\",\"sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "fsevents@2.3.3", + "original": "\"··fsevents@2.3.3\": [1,\"fsevents\",\"sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==\"]", + "new": "\"··fsevents@2.3.3\": [1,\"fsevents\",\"sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/expected-warnings.json new file mode 100644 index 00000000..3f4080a3 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/expected-warnings.json @@ -0,0 +1,5 @@ +[ + "redirect_vlt_lockfile_version_missing", + "redirect_vlt_old_lockfile_ignored", + "redirect_vlt_entry_not_found" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/expected/vlt-lock.json new file mode 100644 index 00000000..f0a9278d --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/expected/vlt-lock.json @@ -0,0 +1,39 @@ +{ + "options": {}, + "nodes": { + "··@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz"], + "··debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "··fsevents@2.3.3": [1,"fsevents","sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz"], + "··js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "··left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "··loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "··lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "··ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "··react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "··react@18.3.1": [0,"react","sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "··semver@7.6.0": [0,"semver","sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz"], + "··use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz"], + "··yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "·npm·left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz": [0,"left-pad"] + }, + "edges": { + "file·. ms": "dev 2.1.2 ··ms@2.1.2", + "file·. fsevents": "optional 2.3.3 ··fsevents@2.3.3", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ··@isaacs§string-locale-compare@1.1.0", + "file·. debug": "prod 4.3.4 ··debug@4.3.4", + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0", + "file·. react": "prod 18.2.0 ··react@18.2.0", + "file·. semver": "prod 7.6.0 ··semver@7.6.0", + "file·. use-sync-external-store": "prod 1.2.0 ··use-sync-external-store@1.2.0", + "file·. lp-alias": "prod npm:left-pad@1.1.3 ·npm·left-pad@1.1.3", + "file·. lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "··debug@4.3.4 ms": "prod 2.1.2 ··ms@2.1.2", + "··loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ··js-tokens@4.0.0", + "··lru-cache@6.0.0 yallist": "prod ^4.0.0 ··yallist@4.0.0", + "··react@18.2.0 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··react@18.3.1 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··semver@7.6.0 lru-cache": "prod ^6.0.0 ··lru-cache@6.0.0", + "··use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ··react@18.3.1" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/input/vlt-lock.json new file mode 100644 index 00000000..830dbce7 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/input/vlt-lock.json @@ -0,0 +1,39 @@ +{ + "options": {}, + "nodes": { + "··@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "··debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "··fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "··js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "··left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "··loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "··lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "··ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "··react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "··react@18.3.1": [0,"react","sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "··semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg=="], + "··use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "··yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "·npm·left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz": [0,"left-pad"] + }, + "edges": { + "file·. ms": "dev 2.1.2 ··ms@2.1.2", + "file·. fsevents": "optional 2.3.3 ··fsevents@2.3.3", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ··@isaacs§string-locale-compare@1.1.0", + "file·. debug": "prod 4.3.4 ··debug@4.3.4", + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0", + "file·. react": "prod 18.2.0 ··react@18.2.0", + "file·. semver": "prod 7.6.0 ··semver@7.6.0", + "file·. use-sync-external-store": "prod 1.2.0 ··use-sync-external-store@1.2.0", + "file·. lp-alias": "prod npm:left-pad@1.1.3 ·npm·left-pad@1.1.3", + "file·. lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "··debug@4.3.4 ms": "prod 2.1.2 ··ms@2.1.2", + "··loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ··js-tokens@4.0.0", + "··lru-cache@6.0.0 yallist": "prod ^4.0.0 ··yallist@4.0.0", + "··react@18.2.0 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··react@18.3.1 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··semver@7.6.0 lru-cache": "prod ^6.0.0 ··lru-cache@6.0.0", + "··use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ··react@18.3.1" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/overrides.json new file mode 100644 index 00000000..701102fe --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-1/overrides.json @@ -0,0 +1,69 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + }, + { + "ecosystem": "npm", + "name": "string-locale-compare", + "namespace": "@isaacs", + "version": "1.1.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz", + "integrity": { + "sha512": "sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==" + } + }, + { + "ecosystem": "npm", + "name": "use-sync-external-store", + "version": "1.2.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz", + "integrity": { + "sha512": "sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==" + } + }, + { + "ecosystem": "npm", + "name": "ms", + "version": "2.1.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz", + "integrity": { + "sha512": "sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==" + } + }, + { + "ecosystem": "npm", + "name": "semver", + "version": "7.6.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "ffffffff-ffff-4fff-8fff-ffffffffffff", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz", + "integrity": { + "sha512": "sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==" + } + }, + { + "ecosystem": "npm", + "name": "fsevents", + "version": "2.3.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "00000000-0000-4000-8000-000000000000", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz", + "integrity": { + "sha512": "sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/expected-confirmation.json new file mode 100644 index 00000000..4b065991 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/expected-confirmation.json @@ -0,0 +1,12 @@ +{ + "confirmed": [ + "00000000-0000-4000-8000-000000000000", + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "ffffffff-ffff-4fff-8fff-ffffffffffff" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/expected-edits.json new file mode 100644 index 00000000..97a8033a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/expected-edits.json @@ -0,0 +1,50 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "@isaacs/string-locale-compare@1.1.0", + "original": "\"··@isaacs§string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==\"]", + "new": "\"··@isaacs§string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "use-sync-external-store@1.2.0", + "original": "\"··use-sync-external-store@1.2.0\": [0,\"use-sync-external-store\",\"sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==\"]", + "new": "\"··use-sync-external-store@1.2.0\": [0,\"use-sync-external-store\",\"sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "ms@2.1.3~%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "original": "\"··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms\": [0,\"ms\",\"sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==\"]", + "new": "\"··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms\": [0,\"ms\",\"sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "semver@7.6.0", + "original": "\"··semver@7.6.0\": [0,\"semver\",\"sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==\"]", + "new": "\"··semver@7.6.0\": [0,\"semver\",\"sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "fsevents@2.3.3", + "original": "\"··fsevents@2.3.3\": [1,\"fsevents\",\"sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==\"]", + "new": "\"··fsevents@2.3.3\": [1,\"fsevents\",\"sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/expected-warnings.json new file mode 100644 index 00000000..7111bf57 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_lockfile_version_missing" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/expected/vlt-lock.json new file mode 100644 index 00000000..88056f32 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/expected/vlt-lock.json @@ -0,0 +1,44 @@ +{ + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "··@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz"], + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "··fsevents@2.3.3": [1,"fsevents","sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz"], + "··js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "··left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "··loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "··lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "··ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"ms","sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz"], + "··react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "··react@18.3.1": [0,"react","sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "··semver@7.6.0": [0,"semver","sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz"], + "··use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz"], + "··yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "·npm·left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz": [0,"left-pad"] + }, + "edges": { + "file·. ms": "dev 2.1.2 ··ms@2.1.2", + "file·. fsevents": "optional 2.3.3 ··fsevents@2.3.3", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ··@isaacs§string-locale-compare@1.1.0", + "file·. debug": "prod 4.3.4 ··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0", + "file·. react": "prod 18.2.0 ··react@18.2.0", + "file·. semver": "prod 7.6.0 ··semver@7.6.0", + "file·. use-sync-external-store": "prod 1.2.0 ··use-sync-external-store@1.2.0", + "file·. lp-alias": "prod npm:left-pad@1.1.3 ·npm·left-pad@1.1.3", + "file·. lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms ms": "prod 2.1.3 ··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "··loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ··js-tokens@4.0.0", + "··lru-cache@6.0.0 yallist": "prod ^4.0.0 ··yallist@4.0.0", + "··react@18.2.0 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··react@18.3.1 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··semver@7.6.0 lru-cache": "prod ^6.0.0 ··lru-cache@6.0.0", + "··use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ··react@18.3.1" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/input/vlt-lock.json new file mode 100644 index 00000000..b5829580 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/input/vlt-lock.json @@ -0,0 +1,44 @@ +{ + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "··@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "··fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "··js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "··left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "··loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "··lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "··ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "··react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "··react@18.3.1": [0,"react","sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "··semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg=="], + "··use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "··yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "·npm·left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz": [0,"left-pad"] + }, + "edges": { + "file·. ms": "dev 2.1.2 ··ms@2.1.2", + "file·. fsevents": "optional 2.3.3 ··fsevents@2.3.3", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ··@isaacs§string-locale-compare@1.1.0", + "file·. debug": "prod 4.3.4 ··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0", + "file·. react": "prod 18.2.0 ··react@18.2.0", + "file·. semver": "prod 7.6.0 ··semver@7.6.0", + "file·. use-sync-external-store": "prod 1.2.0 ··use-sync-external-store@1.2.0", + "file·. lp-alias": "prod npm:left-pad@1.1.3 ·npm·left-pad@1.1.3", + "file·. lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms ms": "prod 2.1.3 ··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "··loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ··js-tokens@4.0.0", + "··lru-cache@6.0.0 yallist": "prod ^4.0.0 ··yallist@4.0.0", + "··react@18.2.0 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··react@18.3.1 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··semver@7.6.0 lru-cache": "prod ^6.0.0 ··lru-cache@6.0.0", + "··use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ··react@18.3.1" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/input/vlt.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/input/vlt.json new file mode 100644 index 00000000..5f7ee1f6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/input/vlt.json @@ -0,0 +1 @@ +{"modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/overrides.json new file mode 100644 index 00000000..701102fe --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-16/overrides.json @@ -0,0 +1,69 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + }, + { + "ecosystem": "npm", + "name": "string-locale-compare", + "namespace": "@isaacs", + "version": "1.1.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz", + "integrity": { + "sha512": "sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==" + } + }, + { + "ecosystem": "npm", + "name": "use-sync-external-store", + "version": "1.2.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz", + "integrity": { + "sha512": "sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==" + } + }, + { + "ecosystem": "npm", + "name": "ms", + "version": "2.1.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz", + "integrity": { + "sha512": "sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==" + } + }, + { + "ecosystem": "npm", + "name": "semver", + "version": "7.6.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "ffffffff-ffff-4fff-8fff-ffffffffffff", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz", + "integrity": { + "sha512": "sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==" + } + }, + { + "ecosystem": "npm", + "name": "fsevents", + "version": "2.3.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "00000000-0000-4000-8000-000000000000", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz", + "integrity": { + "sha512": "sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/expected-confirmation.json new file mode 100644 index 00000000..4b065991 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/expected-confirmation.json @@ -0,0 +1,12 @@ +{ + "confirmed": [ + "00000000-0000-4000-8000-000000000000", + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "ffffffff-ffff-4fff-8fff-ffffffffffff" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/expected-edits.json new file mode 100644 index 00000000..97a8033a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/expected-edits.json @@ -0,0 +1,50 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "@isaacs/string-locale-compare@1.1.0", + "original": "\"··@isaacs§string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==\"]", + "new": "\"··@isaacs§string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "use-sync-external-store@1.2.0", + "original": "\"··use-sync-external-store@1.2.0\": [0,\"use-sync-external-store\",\"sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==\"]", + "new": "\"··use-sync-external-store@1.2.0\": [0,\"use-sync-external-store\",\"sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "ms@2.1.3~%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "original": "\"··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms\": [0,\"ms\",\"sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==\"]", + "new": "\"··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms\": [0,\"ms\",\"sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "semver@7.6.0", + "original": "\"··semver@7.6.0\": [0,\"semver\",\"sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==\"]", + "new": "\"··semver@7.6.0\": [0,\"semver\",\"sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "fsevents@2.3.3", + "original": "\"··fsevents@2.3.3\": [1,\"fsevents\",\"sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==\"]", + "new": "\"··fsevents@2.3.3\": [1,\"fsevents\",\"sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/expected/vlt-lock.json new file mode 100644 index 00000000..98a40486 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/expected/vlt-lock.json @@ -0,0 +1,45 @@ +{ + "lockfileVersion": 0, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "··@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz"], + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "··fsevents@2.3.3": [1,"fsevents","sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz"], + "··js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "··left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "··loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "··lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "··ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"ms","sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz"], + "··react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "··react@18.3.1": [0,"react","sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "··semver@7.6.0": [0,"semver","sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz"], + "··use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz"], + "··yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "·npm·left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz": [0,"left-pad"] + }, + "edges": { + "file·. ms": "dev 2.1.2 ··ms@2.1.2", + "file·. fsevents": "optional 2.3.3 ··fsevents@2.3.3", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ··@isaacs§string-locale-compare@1.1.0", + "file·. debug": "prod 4.3.4 ··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0", + "file·. react": "prod 18.2.0 ··react@18.2.0", + "file·. semver": "prod 7.6.0 ··semver@7.6.0", + "file·. use-sync-external-store": "prod 1.2.0 ··use-sync-external-store@1.2.0", + "file·. lp-alias": "prod npm:left-pad@1.1.3 ·npm·left-pad@1.1.3", + "file·. lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms ms": "prod 2.1.3 ··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "··loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ··js-tokens@4.0.0", + "··lru-cache@6.0.0 yallist": "prod ^4.0.0 ··yallist@4.0.0", + "··react@18.2.0 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··react@18.3.1 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··semver@7.6.0 lru-cache": "prod ^6.0.0 ··lru-cache@6.0.0", + "··use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ··react@18.3.1" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/input/vlt-lock.json new file mode 100644 index 00000000..9725a9fb --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/input/vlt-lock.json @@ -0,0 +1,45 @@ +{ + "lockfileVersion": 0, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "··@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "··fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "··js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "··left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "··loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "··lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "··ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "··react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "··react@18.3.1": [0,"react","sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "··semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg=="], + "··use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "··yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "·npm·left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz": [0,"left-pad"] + }, + "edges": { + "file·. ms": "dev 2.1.2 ··ms@2.1.2", + "file·. fsevents": "optional 2.3.3 ··fsevents@2.3.3", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ··@isaacs§string-locale-compare@1.1.0", + "file·. debug": "prod 4.3.4 ··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0", + "file·. react": "prod 18.2.0 ··react@18.2.0", + "file·. semver": "prod 7.6.0 ··semver@7.6.0", + "file·. use-sync-external-store": "prod 1.2.0 ··use-sync-external-store@1.2.0", + "file·. lp-alias": "prod npm:left-pad@1.1.3 ·npm·left-pad@1.1.3", + "file·. lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms ms": "prod 2.1.3 ··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "··loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ··js-tokens@4.0.0", + "··lru-cache@6.0.0 yallist": "prod ^4.0.0 ··yallist@4.0.0", + "··react@18.2.0 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··react@18.3.1 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··semver@7.6.0 lru-cache": "prod ^6.0.0 ··lru-cache@6.0.0", + "··use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ··react@18.3.1" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/input/vlt.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/input/vlt.json new file mode 100644 index 00000000..5f7ee1f6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/input/vlt.json @@ -0,0 +1 @@ +{"modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/overrides.json new file mode 100644 index 00000000..701102fe --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-19/overrides.json @@ -0,0 +1,69 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + }, + { + "ecosystem": "npm", + "name": "string-locale-compare", + "namespace": "@isaacs", + "version": "1.1.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz", + "integrity": { + "sha512": "sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==" + } + }, + { + "ecosystem": "npm", + "name": "use-sync-external-store", + "version": "1.2.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz", + "integrity": { + "sha512": "sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==" + } + }, + { + "ecosystem": "npm", + "name": "ms", + "version": "2.1.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz", + "integrity": { + "sha512": "sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==" + } + }, + { + "ecosystem": "npm", + "name": "semver", + "version": "7.6.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "ffffffff-ffff-4fff-8fff-ffffffffffff", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz", + "integrity": { + "sha512": "sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==" + } + }, + { + "ecosystem": "npm", + "name": "fsevents", + "version": "2.3.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "00000000-0000-4000-8000-000000000000", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz", + "integrity": { + "sha512": "sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/expected-confirmation.json new file mode 100644 index 00000000..4b065991 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/expected-confirmation.json @@ -0,0 +1,12 @@ +{ + "confirmed": [ + "00000000-0000-4000-8000-000000000000", + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "ffffffff-ffff-4fff-8fff-ffffffffffff" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/expected-edits.json new file mode 100644 index 00000000..5bdff734 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/expected-edits.json @@ -0,0 +1,50 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "@isaacs/string-locale-compare@1.1.0", + "original": "\"··@isaacs§string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==\"]", + "new": "\"··@isaacs§string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "use-sync-external-store@1.2.0", + "original": "\"··use-sync-external-store@1.2.0\": [0,\"use-sync-external-store\",\"sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==\"]", + "new": "\"··use-sync-external-store@1.2.0\": [0,\"use-sync-external-store\",\"sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "ms@2.1.3~%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "original": "\"··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms\": [0,\"ms\",\"sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==\"]", + "new": "\"··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms\": [0,\"ms\",\"sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "semver@7.6.0", + "original": "\"··semver@7.6.0\": [0,\"semver\",\"sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==\"]", + "new": "\"··semver@7.6.0\": [0,\"semver\",\"sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "fsevents@2.3.3", + "original": "\"··fsevents@2.3.3\": [1,\"fsevents\",\"sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==\",null,null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]", + "new": "\"··fsevents@2.3.3\": [1,\"fsevents\",\"sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz\",null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/expected/vlt-lock.json new file mode 100644 index 00000000..2ffd3e19 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/expected/vlt-lock.json @@ -0,0 +1,43 @@ +{ + "lockfileVersion": 0, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "··@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz"], + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "··fsevents@2.3.3": [1,"fsevents","sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz",null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "··js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "··left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "··loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "··lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "··ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"ms","sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz"], + "··react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "··semver@7.6.0": [0,"semver","sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz"], + "··use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz"], + "··yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "·npm·left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz": [0,"left-pad"] + }, + "edges": { + "file·. ms": "dev 2.1.2 ··ms@2.1.2", + "file·. fsevents": "optional 2.3.3 ··fsevents@2.3.3", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ··@isaacs§string-locale-compare@1.1.0", + "file·. debug": "prod 4.3.4 ··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0", + "file·. react": "prod 18.2.0 ··react@18.2.0", + "file·. semver": "prod 7.6.0 ··semver@7.6.0", + "file·. use-sync-external-store": "prod 1.2.0 ··use-sync-external-store@1.2.0", + "file·. lp-alias": "prod npm:left-pad@1.1.3 ·npm·left-pad@1.1.3", + "file·. lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms ms": "prod 2.1.3 ··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "··loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ··js-tokens@4.0.0", + "··lru-cache@6.0.0 yallist": "prod ^4.0.0 ··yallist@4.0.0", + "··react@18.2.0 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··semver@7.6.0 lru-cache": "prod ^6.0.0 ··lru-cache@6.0.0", + "··use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ··react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/input/vlt-lock.json new file mode 100644 index 00000000..beae2b78 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/input/vlt-lock.json @@ -0,0 +1,43 @@ +{ + "lockfileVersion": 0, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "··@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "··fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",null,null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "··js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "··left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "··loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "··lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "··ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "··react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "··semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg=="], + "··use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "··yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "·npm·left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz": [0,"left-pad"] + }, + "edges": { + "file·. ms": "dev 2.1.2 ··ms@2.1.2", + "file·. fsevents": "optional 2.3.3 ··fsevents@2.3.3", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ··@isaacs§string-locale-compare@1.1.0", + "file·. debug": "prod 4.3.4 ··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0", + "file·. react": "prod 18.2.0 ··react@18.2.0", + "file·. semver": "prod 7.6.0 ··semver@7.6.0", + "file·. use-sync-external-store": "prod 1.2.0 ··use-sync-external-store@1.2.0", + "file·. lp-alias": "prod npm:left-pad@1.1.3 ·npm·left-pad@1.1.3", + "file·. lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms ms": "prod 2.1.3 ··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "··loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ··js-tokens@4.0.0", + "··lru-cache@6.0.0 yallist": "prod ^4.0.0 ··yallist@4.0.0", + "··react@18.2.0 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··semver@7.6.0 lru-cache": "prod ^6.0.0 ··lru-cache@6.0.0", + "··use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ··react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/input/vlt.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/input/vlt.json new file mode 100644 index 00000000..5f7ee1f6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/input/vlt.json @@ -0,0 +1 @@ +{"modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/overrides.json new file mode 100644 index 00000000..701102fe --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-0.0.0-32/overrides.json @@ -0,0 +1,69 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + }, + { + "ecosystem": "npm", + "name": "string-locale-compare", + "namespace": "@isaacs", + "version": "1.1.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz", + "integrity": { + "sha512": "sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==" + } + }, + { + "ecosystem": "npm", + "name": "use-sync-external-store", + "version": "1.2.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz", + "integrity": { + "sha512": "sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==" + } + }, + { + "ecosystem": "npm", + "name": "ms", + "version": "2.1.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz", + "integrity": { + "sha512": "sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==" + } + }, + { + "ecosystem": "npm", + "name": "semver", + "version": "7.6.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "ffffffff-ffff-4fff-8fff-ffffffffffff", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz", + "integrity": { + "sha512": "sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==" + } + }, + { + "ecosystem": "npm", + "name": "fsevents", + "version": "2.3.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "00000000-0000-4000-8000-000000000000", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz", + "integrity": { + "sha512": "sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/expected-confirmation.json new file mode 100644 index 00000000..4b065991 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/expected-confirmation.json @@ -0,0 +1,12 @@ +{ + "confirmed": [ + "00000000-0000-4000-8000-000000000000", + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "ffffffff-ffff-4fff-8fff-ffffffffffff" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/expected-edits.json new file mode 100644 index 00000000..40c485a0 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/expected-edits.json @@ -0,0 +1,50 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"·npm·left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"·npm·left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "@isaacs/string-locale-compare@1.1.0", + "original": "\"·npm·@isaacs§string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==\"]", + "new": "\"·npm·@isaacs§string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "use-sync-external-store@1.2.0", + "original": "\"·npm·use-sync-external-store@1.2.0\": [0,\"use-sync-external-store\",\"sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==\"]", + "new": "\"·npm·use-sync-external-store@1.2.0\": [0,\"use-sync-external-store\",\"sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "ms@2.1.3~%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "original": "\"·npm·ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms\": [0,\"ms\",\"sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==\"]", + "new": "\"·npm·ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms\": [0,\"ms\",\"sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "semver@7.6.0", + "original": "\"·npm·semver@7.6.0\": [0,\"semver\",\"sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==\",null,null,null,null,null,{ \"semver\": \"bin/semver.js\"}]", + "new": "\"·npm·semver@7.6.0\": [0,\"semver\",\"sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz\",null,null,null,null,{ \"semver\": \"bin/semver.js\"}]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "fsevents@2.3.3", + "original": "\"·npm·fsevents@2.3.3\": [1,\"fsevents\",\"sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==\",null,null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]", + "new": "\"·npm·fsevents@2.3.3\": [1,\"fsevents\",\"sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz\",null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/expected/vlt-lock.json new file mode 100644 index 00000000..6e21a14a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/expected/vlt-lock.json @@ -0,0 +1,43 @@ +{ + "lockfileVersion": 0, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "·npm·@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz"], + "·npm·debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "·npm·fsevents@2.3.3": [1,"fsevents","sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz",null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "·npm·js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "·npm·left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "·npm·left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "·npm·loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "·npm·lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "·npm·ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "·npm·ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"ms","sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz"], + "·npm·react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "·npm·semver@7.6.0": [0,"semver","sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "·npm·use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz"], + "·npm·yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file·. ms": "dev 2.1.2 ·npm·ms@2.1.2", + "file·. fsevents": "optional 2.3.3 ·npm·fsevents@2.3.3", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ·npm·@isaacs§string-locale-compare@1.1.0", + "file·. debug": "prod 4.3.4 ·npm·debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "file·. lp-alias": "prod npm:left-pad@1.1.3 ·npm·left-pad@1.1.3", + "file·. left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "file·. react": "prod 18.2.0 ·npm·react@18.2.0", + "file·. semver": "prod 7.6.0 ·npm·semver@7.6.0", + "file·. use-sync-external-store": "prod 1.2.0 ·npm·use-sync-external-store@1.2.0", + "file·. lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "·npm·debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms ms": "prod 2.1.3 ·npm·ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ·npm·js-tokens@4.0.0", + "·npm·lru-cache@6.0.0 yallist": "prod ^4.0.0 ·npm·yallist@4.0.0", + "·npm·react@18.2.0 loose-envify": "prod ^1.1.0 ·npm·loose-envify@1.4.0", + "·npm·semver@7.6.0 lru-cache": "prod ^6.0.0 ·npm·lru-cache@6.0.0", + "·npm·use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ·npm·react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/input/vlt-lock.json new file mode 100644 index 00000000..9b570793 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/input/vlt-lock.json @@ -0,0 +1,43 @@ +{ + "lockfileVersion": 0, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "·npm·@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "·npm·debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "·npm·fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",null,null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "·npm·js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "·npm·left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "·npm·left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "·npm·loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "·npm·lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "·npm·ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "·npm·ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "·npm·react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "·npm·semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",null,null,null,null,null,{ "semver": "bin/semver.js"}], + "·npm·use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "·npm·yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file·. ms": "dev 2.1.2 ·npm·ms@2.1.2", + "file·. fsevents": "optional 2.3.3 ·npm·fsevents@2.3.3", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ·npm·@isaacs§string-locale-compare@1.1.0", + "file·. debug": "prod 4.3.4 ·npm·debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "file·. lp-alias": "prod npm:left-pad@1.1.3 ·npm·left-pad@1.1.3", + "file·. left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "file·. react": "prod 18.2.0 ·npm·react@18.2.0", + "file·. semver": "prod 7.6.0 ·npm·semver@7.6.0", + "file·. use-sync-external-store": "prod 1.2.0 ·npm·use-sync-external-store@1.2.0", + "file·. lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "·npm·debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms ms": "prod 2.1.3 ·npm·ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ·npm·js-tokens@4.0.0", + "·npm·lru-cache@6.0.0 yallist": "prod ^4.0.0 ·npm·yallist@4.0.0", + "·npm·react@18.2.0 loose-envify": "prod ^1.1.0 ·npm·loose-envify@1.4.0", + "·npm·semver@7.6.0 lru-cache": "prod ^6.0.0 ·npm·lru-cache@6.0.0", + "·npm·use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ·npm·react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/input/vlt.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/input/vlt.json new file mode 100644 index 00000000..5f7ee1f6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/input/vlt.json @@ -0,0 +1 @@ +{"modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/overrides.json new file mode 100644 index 00000000..701102fe --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.14/overrides.json @@ -0,0 +1,69 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + }, + { + "ecosystem": "npm", + "name": "string-locale-compare", + "namespace": "@isaacs", + "version": "1.1.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz", + "integrity": { + "sha512": "sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==" + } + }, + { + "ecosystem": "npm", + "name": "use-sync-external-store", + "version": "1.2.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz", + "integrity": { + "sha512": "sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==" + } + }, + { + "ecosystem": "npm", + "name": "ms", + "version": "2.1.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz", + "integrity": { + "sha512": "sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==" + } + }, + { + "ecosystem": "npm", + "name": "semver", + "version": "7.6.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "ffffffff-ffff-4fff-8fff-ffffffffffff", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz", + "integrity": { + "sha512": "sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==" + } + }, + { + "ecosystem": "npm", + "name": "fsevents", + "version": "2.3.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "00000000-0000-4000-8000-000000000000", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz", + "integrity": { + "sha512": "sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/expected-confirmation.json new file mode 100644 index 00000000..4b065991 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/expected-confirmation.json @@ -0,0 +1,12 @@ +{ + "confirmed": [ + "00000000-0000-4000-8000-000000000000", + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "ffffffff-ffff-4fff-8fff-ffffffffffff" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/expected-edits.json new file mode 100644 index 00000000..222128b0 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/expected-edits.json @@ -0,0 +1,50 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "@isaacs/string-locale-compare@1.1.0", + "original": "\"~npm~@isaacs+string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==\"]", + "new": "\"~npm~@isaacs+string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "use-sync-external-store@1.2.0", + "original": "\"~npm~use-sync-external-store@1.2.0\": [0,\"use-sync-external-store\",\"sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==\"]", + "new": "\"~npm~use-sync-external-store@1.2.0\": [0,\"use-sync-external-store\",\"sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "original": "\"~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms\": [0,\"ms\",\"sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==\"]", + "new": "\"~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms\": [0,\"ms\",\"sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "semver@7.6.0", + "original": "\"~npm~semver@7.6.0\": [0,\"semver\",\"sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==\",null,null,null,null,null,{ \"semver\": \"bin/semver.js\"}]", + "new": "\"~npm~semver@7.6.0\": [0,\"semver\",\"sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz\",null,null,null,null,{ \"semver\": \"bin/semver.js\"}]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "fsevents@2.3.3", + "original": "\"~npm~fsevents@2.3.3\": [1,\"fsevents\",\"sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==\",null,null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]", + "new": "\"~npm~fsevents@2.3.3\": [1,\"fsevents\",\"sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz\",null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/expected/vlt-lock.json new file mode 100644 index 00000000..b37d8cdd --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/expected/vlt-lock.json @@ -0,0 +1,43 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz",null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "~npm~ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "~npm~semver@7.6.0": [0,"semver","sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file~_d ms": "dev 2.1.2 ~npm~ms@2.1.2", + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "file~_d lp-alias": "prod npm:left-pad@1.1.3 ~npm~left-pad@1.1.3", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/input/vlt-lock.json new file mode 100644 index 00000000..657f3548 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/input/vlt-lock.json @@ -0,0 +1,43 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",null,null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "~npm~ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",null,null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file~_d ms": "dev 2.1.2 ~npm~ms@2.1.2", + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "file~_d lp-alias": "prod npm:left-pad@1.1.3 ~npm~left-pad@1.1.3", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/input/vlt.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/input/vlt.json new file mode 100644 index 00000000..5f7ee1f6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/input/vlt.json @@ -0,0 +1 @@ +{"modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/overrides.json new file mode 100644 index 00000000..701102fe --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.15/overrides.json @@ -0,0 +1,69 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + }, + { + "ecosystem": "npm", + "name": "string-locale-compare", + "namespace": "@isaacs", + "version": "1.1.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz", + "integrity": { + "sha512": "sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==" + } + }, + { + "ecosystem": "npm", + "name": "use-sync-external-store", + "version": "1.2.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz", + "integrity": { + "sha512": "sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==" + } + }, + { + "ecosystem": "npm", + "name": "ms", + "version": "2.1.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz", + "integrity": { + "sha512": "sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==" + } + }, + { + "ecosystem": "npm", + "name": "semver", + "version": "7.6.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "ffffffff-ffff-4fff-8fff-ffffffffffff", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz", + "integrity": { + "sha512": "sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==" + } + }, + { + "ecosystem": "npm", + "name": "fsevents", + "version": "2.3.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "00000000-0000-4000-8000-000000000000", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz", + "integrity": { + "sha512": "sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/expected-confirmation.json new file mode 100644 index 00000000..4b065991 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/expected-confirmation.json @@ -0,0 +1,12 @@ +{ + "confirmed": [ + "00000000-0000-4000-8000-000000000000", + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "ffffffff-ffff-4fff-8fff-ffffffffffff" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/expected-edits.json new file mode 100644 index 00000000..222128b0 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/expected-edits.json @@ -0,0 +1,50 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "@isaacs/string-locale-compare@1.1.0", + "original": "\"~npm~@isaacs+string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==\"]", + "new": "\"~npm~@isaacs+string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "use-sync-external-store@1.2.0", + "original": "\"~npm~use-sync-external-store@1.2.0\": [0,\"use-sync-external-store\",\"sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==\"]", + "new": "\"~npm~use-sync-external-store@1.2.0\": [0,\"use-sync-external-store\",\"sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "original": "\"~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms\": [0,\"ms\",\"sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==\"]", + "new": "\"~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms\": [0,\"ms\",\"sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "semver@7.6.0", + "original": "\"~npm~semver@7.6.0\": [0,\"semver\",\"sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==\",null,null,null,null,null,{ \"semver\": \"bin/semver.js\"}]", + "new": "\"~npm~semver@7.6.0\": [0,\"semver\",\"sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz\",null,null,null,null,{ \"semver\": \"bin/semver.js\"}]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "fsevents@2.3.3", + "original": "\"~npm~fsevents@2.3.3\": [1,\"fsevents\",\"sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==\",null,null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]", + "new": "\"~npm~fsevents@2.3.3\": [1,\"fsevents\",\"sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz\",null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/expected/vlt-lock.json new file mode 100644 index 00000000..b37d8cdd --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/expected/vlt-lock.json @@ -0,0 +1,43 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz",null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "~npm~ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "~npm~semver@7.6.0": [0,"semver","sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file~_d ms": "dev 2.1.2 ~npm~ms@2.1.2", + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "file~_d lp-alias": "prod npm:left-pad@1.1.3 ~npm~left-pad@1.1.3", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/input/vlt-lock.json new file mode 100644 index 00000000..657f3548 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/input/vlt-lock.json @@ -0,0 +1,43 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",null,null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "~npm~ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",null,null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file~_d ms": "dev 2.1.2 ~npm~ms@2.1.2", + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "file~_d lp-alias": "prod npm:left-pad@1.1.3 ~npm~left-pad@1.1.3", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/input/vlt.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/input/vlt.json new file mode 100644 index 00000000..5f7ee1f6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/input/vlt.json @@ -0,0 +1 @@ +{"modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/overrides.json new file mode 100644 index 00000000..701102fe --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.32/overrides.json @@ -0,0 +1,69 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + }, + { + "ecosystem": "npm", + "name": "string-locale-compare", + "namespace": "@isaacs", + "version": "1.1.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz", + "integrity": { + "sha512": "sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==" + } + }, + { + "ecosystem": "npm", + "name": "use-sync-external-store", + "version": "1.2.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz", + "integrity": { + "sha512": "sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==" + } + }, + { + "ecosystem": "npm", + "name": "ms", + "version": "2.1.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz", + "integrity": { + "sha512": "sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==" + } + }, + { + "ecosystem": "npm", + "name": "semver", + "version": "7.6.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "ffffffff-ffff-4fff-8fff-ffffffffffff", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz", + "integrity": { + "sha512": "sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==" + } + }, + { + "ecosystem": "npm", + "name": "fsevents", + "version": "2.3.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "00000000-0000-4000-8000-000000000000", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz", + "integrity": { + "sha512": "sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/expected-confirmation.json new file mode 100644 index 00000000..4b065991 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/expected-confirmation.json @@ -0,0 +1,12 @@ +{ + "confirmed": [ + "00000000-0000-4000-8000-000000000000", + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "ffffffff-ffff-4fff-8fff-ffffffffffff" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/expected-edits.json new file mode 100644 index 00000000..a222abab --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/expected-edits.json @@ -0,0 +1,50 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\",\"https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz\"]", + "new": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "@isaacs/string-locale-compare@1.1.0", + "original": "\"~npm~@isaacs+string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==\",\"https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz\"]", + "new": "\"~npm~@isaacs+string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "use-sync-external-store@1.2.0", + "original": "\"~npm~use-sync-external-store@1.2.0\": [0,\"use-sync-external-store\",\"sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==\",\"https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz\"]", + "new": "\"~npm~use-sync-external-store@1.2.0\": [0,\"use-sync-external-store\",\"sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "original": "\"~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms\": [0,\"ms\",\"sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==\",\"https://registry.npmjs.org/ms/-/ms-2.1.3.tgz\"]", + "new": "\"~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms\": [0,\"ms\",\"sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "semver@7.6.0", + "original": "\"~npm~semver@7.6.0\": [0,\"semver\",\"sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==\",\"https://registry.npmjs.org/semver/-/semver-7.6.0.tgz\",null,null,null,null,{ \"semver\": \"bin/semver.js\"}]", + "new": "\"~npm~semver@7.6.0\": [0,\"semver\",\"sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz\",null,null,null,null,{ \"semver\": \"bin/semver.js\"}]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "fsevents@2.3.3", + "original": "\"~npm~fsevents@2.3.3\": [1,\"fsevents\",\"sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==\",\"https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz\",null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]", + "new": "\"~npm~fsevents@2.3.3\": [1,\"fsevents\",\"sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz\",null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/expected/vlt-lock.json new file mode 100644 index 00000000..eb2e813b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/expected/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + }, + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz",null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw==","https://registry.npmjs.org/left-pad/-/left-pad-1.1.3.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file~_d ms": "dev 2.1.2 ~npm~ms@2.1.2", + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "file~_d lp-alias": "prod npm:left-pad@1.1.3 ~npm~left-pad@1.1.3", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/input/vlt-lock.json new file mode 100644 index 00000000..16b0da72 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/input/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + }, + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==","https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw==","https://registry.npmjs.org/left-pad/-/left-pad-1.1.3.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file~_d ms": "dev 2.1.2 ~npm~ms@2.1.2", + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "file~_d lp-alias": "prod npm:left-pad@1.1.3 ~npm~left-pad@1.1.3", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/input/vlt.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/input/vlt.json new file mode 100644 index 00000000..ab3aa87e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/input/vlt.json @@ -0,0 +1 @@ +{"config": {"registries": {"npm": "https://registry.npmjs.org/"}}, "modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/overrides.json new file mode 100644 index 00000000..701102fe --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.33/overrides.json @@ -0,0 +1,69 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + }, + { + "ecosystem": "npm", + "name": "string-locale-compare", + "namespace": "@isaacs", + "version": "1.1.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz", + "integrity": { + "sha512": "sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==" + } + }, + { + "ecosystem": "npm", + "name": "use-sync-external-store", + "version": "1.2.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz", + "integrity": { + "sha512": "sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==" + } + }, + { + "ecosystem": "npm", + "name": "ms", + "version": "2.1.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz", + "integrity": { + "sha512": "sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==" + } + }, + { + "ecosystem": "npm", + "name": "semver", + "version": "7.6.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "ffffffff-ffff-4fff-8fff-ffffffffffff", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz", + "integrity": { + "sha512": "sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==" + } + }, + { + "ecosystem": "npm", + "name": "fsevents", + "version": "2.3.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "00000000-0000-4000-8000-000000000000", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz", + "integrity": { + "sha512": "sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/expected-confirmation.json new file mode 100644 index 00000000..4b065991 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/expected-confirmation.json @@ -0,0 +1,12 @@ +{ + "confirmed": [ + "00000000-0000-4000-8000-000000000000", + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "ffffffff-ffff-4fff-8fff-ffffffffffff" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/expected-edits.json new file mode 100644 index 00000000..4d7e7d72 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/expected-edits.json @@ -0,0 +1,50 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "@isaacs/string-locale-compare@1.1.0", + "original": "\"··@isaacs§string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==\"]", + "new": "\"··@isaacs§string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "use-sync-external-store@1.2.0", + "original": "\"··use-sync-external-store@1.2.0\": [0,\"use-sync-external-store\",\"sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==\"]", + "new": "\"··use-sync-external-store@1.2.0\": [0,\"use-sync-external-store\",\"sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "ms@2.1.3~%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "original": "\"··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms\": [0,\"ms\",\"sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==\"]", + "new": "\"··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms\": [0,\"ms\",\"sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "semver@7.6.0", + "original": "\"··semver@7.6.0\": [0,\"semver\",\"sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==\",null,null,null,null,null,{ \"semver\": \"bin/semver.js\"}]", + "new": "\"··semver@7.6.0\": [0,\"semver\",\"sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz\",null,null,null,null,{ \"semver\": \"bin/semver.js\"}]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "fsevents@2.3.3", + "original": "\"··fsevents@2.3.3\": [1,\"fsevents\",\"sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==\",null,null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]", + "new": "\"··fsevents@2.3.3\": [1,\"fsevents\",\"sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz\",null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/expected/vlt-lock.json new file mode 100644 index 00000000..f8dad75e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/expected/vlt-lock.json @@ -0,0 +1,43 @@ +{ + "lockfileVersion": 0, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "··@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz"], + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "··fsevents@2.3.3": [1,"fsevents","sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz",null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "··js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "··left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "··loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "··lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "··ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"ms","sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz"], + "··react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "··semver@7.6.0": [0,"semver","sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "··use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz"], + "··yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "·npm·left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz": [0,"left-pad"] + }, + "edges": { + "file·. ms": "dev 2.1.2 ··ms@2.1.2", + "file·. fsevents": "optional 2.3.3 ··fsevents@2.3.3", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ··@isaacs§string-locale-compare@1.1.0", + "file·. debug": "prod 4.3.4 ··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0", + "file·. react": "prod 18.2.0 ··react@18.2.0", + "file·. semver": "prod 7.6.0 ··semver@7.6.0", + "file·. use-sync-external-store": "prod 1.2.0 ··use-sync-external-store@1.2.0", + "file·. lp-alias": "prod npm:left-pad@1.1.3 ·npm·left-pad@1.1.3", + "file·. lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms ms": "prod 2.1.3 ··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "··loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ··js-tokens@4.0.0", + "··lru-cache@6.0.0 yallist": "prod ^4.0.0 ··yallist@4.0.0", + "··react@18.2.0 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··semver@7.6.0 lru-cache": "prod ^6.0.0 ··lru-cache@6.0.0", + "··use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ··react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/input/vlt-lock.json new file mode 100644 index 00000000..d3a36652 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/input/vlt-lock.json @@ -0,0 +1,43 @@ +{ + "lockfileVersion": 0, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "··@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "··fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",null,null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "··js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "··left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "··loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "··lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "··ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "··react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "··semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",null,null,null,null,null,{ "semver": "bin/semver.js"}], + "··use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "··yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "·npm·left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz": [0,"left-pad"] + }, + "edges": { + "file·. ms": "dev 2.1.2 ··ms@2.1.2", + "file·. fsevents": "optional 2.3.3 ··fsevents@2.3.3", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ··@isaacs§string-locale-compare@1.1.0", + "file·. debug": "prod 4.3.4 ··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0", + "file·. react": "prod 18.2.0 ··react@18.2.0", + "file·. semver": "prod 7.6.0 ··semver@7.6.0", + "file·. use-sync-external-store": "prod 1.2.0 ··use-sync-external-store@1.2.0", + "file·. lp-alias": "prod npm:left-pad@1.1.3 ·npm·left-pad@1.1.3", + "file·. lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms ms": "prod 2.1.3 ··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "··loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ··js-tokens@4.0.0", + "··lru-cache@6.0.0 yallist": "prod ^4.0.0 ··yallist@4.0.0", + "··react@18.2.0 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··semver@7.6.0 lru-cache": "prod ^6.0.0 ··lru-cache@6.0.0", + "··use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ··react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/input/vlt.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/input/vlt.json new file mode 100644 index 00000000..5f7ee1f6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/input/vlt.json @@ -0,0 +1 @@ +{"modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/overrides.json new file mode 100644 index 00000000..701102fe --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.0-rc.8/overrides.json @@ -0,0 +1,69 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + }, + { + "ecosystem": "npm", + "name": "string-locale-compare", + "namespace": "@isaacs", + "version": "1.1.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz", + "integrity": { + "sha512": "sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==" + } + }, + { + "ecosystem": "npm", + "name": "use-sync-external-store", + "version": "1.2.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz", + "integrity": { + "sha512": "sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==" + } + }, + { + "ecosystem": "npm", + "name": "ms", + "version": "2.1.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz", + "integrity": { + "sha512": "sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==" + } + }, + { + "ecosystem": "npm", + "name": "semver", + "version": "7.6.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "ffffffff-ffff-4fff-8fff-ffffffffffff", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz", + "integrity": { + "sha512": "sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==" + } + }, + { + "ecosystem": "npm", + "name": "fsevents", + "version": "2.3.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "00000000-0000-4000-8000-000000000000", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz", + "integrity": { + "sha512": "sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/expected-confirmation.json new file mode 100644 index 00000000..4b065991 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/expected-confirmation.json @@ -0,0 +1,12 @@ +{ + "confirmed": [ + "00000000-0000-4000-8000-000000000000", + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "ffffffff-ffff-4fff-8fff-ffffffffffff" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/expected-edits.json new file mode 100644 index 00000000..89901272 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/expected-edits.json @@ -0,0 +1,50 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\",\"https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz\"]", + "new": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "@isaacs/string-locale-compare@1.1.0", + "original": "\"~npm~@isaacs+string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==\",\"https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz\"]", + "new": "\"~npm~@isaacs+string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "original": "\"~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba\": [0,\"use-sync-external-store\",\"sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==\",\"https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz\"]", + "new": "\"~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba\": [0,\"use-sync-external-store\",\"sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "original": "\"~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms\": [0,\"ms\",\"sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==\",\"https://registry.npmjs.org/ms/-/ms-2.1.3.tgz\"]", + "new": "\"~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms\": [0,\"ms\",\"sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "semver@7.6.0", + "original": "\"~npm~semver@7.6.0\": [0,\"semver\",\"sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==\",\"https://registry.npmjs.org/semver/-/semver-7.6.0.tgz\",null,null,null,null,{ \"semver\": \"bin/semver.js\"}]", + "new": "\"~npm~semver@7.6.0\": [0,\"semver\",\"sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz\",null,null,null,null,{ \"semver\": \"bin/semver.js\"}]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "fsevents@2.3.3", + "original": "\"~npm~fsevents@2.3.3\": [1,\"fsevents\",\"sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==\",\"https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz\",null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]", + "new": "\"~npm~fsevents@2.3.3\": [1,\"fsevents\",\"sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz\",null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/expected/vlt-lock.json new file mode 100644 index 00000000..9a54c540 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/expected/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + }, + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz",null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw==","https://registry.npmjs.org/left-pad/-/left-pad-1.1.3.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file~_d ms": "dev 2.1.2 ~npm~ms@2.1.2", + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "file~_d lp-alias": "prod npm:left-pad@1.1.3 ~npm~left-pad@1.1.3", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/input/vlt-lock.json new file mode 100644 index 00000000..54da8c22 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/input/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + }, + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==","https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw==","https://registry.npmjs.org/left-pad/-/left-pad-1.1.3.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file~_d ms": "dev 2.1.2 ~npm~ms@2.1.2", + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "file~_d lp-alias": "prod npm:left-pad@1.1.3 ~npm~left-pad@1.1.3", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/input/vlt.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/input/vlt.json new file mode 100644 index 00000000..ab3aa87e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/input/vlt.json @@ -0,0 +1 @@ +{"config": {"registries": {"npm": "https://registry.npmjs.org/"}}, "modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/overrides.json new file mode 100644 index 00000000..701102fe --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.0.10/overrides.json @@ -0,0 +1,69 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + }, + { + "ecosystem": "npm", + "name": "string-locale-compare", + "namespace": "@isaacs", + "version": "1.1.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz", + "integrity": { + "sha512": "sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==" + } + }, + { + "ecosystem": "npm", + "name": "use-sync-external-store", + "version": "1.2.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz", + "integrity": { + "sha512": "sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==" + } + }, + { + "ecosystem": "npm", + "name": "ms", + "version": "2.1.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz", + "integrity": { + "sha512": "sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==" + } + }, + { + "ecosystem": "npm", + "name": "semver", + "version": "7.6.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "ffffffff-ffff-4fff-8fff-ffffffffffff", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz", + "integrity": { + "sha512": "sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==" + } + }, + { + "ecosystem": "npm", + "name": "fsevents", + "version": "2.3.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "00000000-0000-4000-8000-000000000000", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz", + "integrity": { + "sha512": "sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/expected-confirmation.json new file mode 100644 index 00000000..4b065991 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/expected-confirmation.json @@ -0,0 +1,12 @@ +{ + "confirmed": [ + "00000000-0000-4000-8000-000000000000", + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "ffffffff-ffff-4fff-8fff-ffffffffffff" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/expected-edits.json new file mode 100644 index 00000000..89901272 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/expected-edits.json @@ -0,0 +1,50 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\",\"https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz\"]", + "new": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "@isaacs/string-locale-compare@1.1.0", + "original": "\"~npm~@isaacs+string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==\",\"https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz\"]", + "new": "\"~npm~@isaacs+string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "original": "\"~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba\": [0,\"use-sync-external-store\",\"sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==\",\"https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz\"]", + "new": "\"~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba\": [0,\"use-sync-external-store\",\"sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "original": "\"~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms\": [0,\"ms\",\"sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==\",\"https://registry.npmjs.org/ms/-/ms-2.1.3.tgz\"]", + "new": "\"~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms\": [0,\"ms\",\"sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "semver@7.6.0", + "original": "\"~npm~semver@7.6.0\": [0,\"semver\",\"sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==\",\"https://registry.npmjs.org/semver/-/semver-7.6.0.tgz\",null,null,null,null,{ \"semver\": \"bin/semver.js\"}]", + "new": "\"~npm~semver@7.6.0\": [0,\"semver\",\"sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz\",null,null,null,null,{ \"semver\": \"bin/semver.js\"}]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "fsevents@2.3.3", + "original": "\"~npm~fsevents@2.3.3\": [1,\"fsevents\",\"sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==\",\"https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz\",null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]", + "new": "\"~npm~fsevents@2.3.3\": [1,\"fsevents\",\"sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz\",null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/expected/vlt-lock.json new file mode 100644 index 00000000..9a54c540 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/expected/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + }, + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz",null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw==","https://registry.npmjs.org/left-pad/-/left-pad-1.1.3.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file~_d ms": "dev 2.1.2 ~npm~ms@2.1.2", + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "file~_d lp-alias": "prod npm:left-pad@1.1.3 ~npm~left-pad@1.1.3", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/input/vlt-lock.json new file mode 100644 index 00000000..54da8c22 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/input/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + }, + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==","https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw==","https://registry.npmjs.org/left-pad/-/left-pad-1.1.3.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file~_d ms": "dev 2.1.2 ~npm~ms@2.1.2", + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "file~_d lp-alias": "prod npm:left-pad@1.1.3 ~npm~left-pad@1.1.3", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/input/vlt.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/input/vlt.json new file mode 100644 index 00000000..ab3aa87e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/input/vlt.json @@ -0,0 +1 @@ +{"config": {"registries": {"npm": "https://registry.npmjs.org/"}}, "modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/overrides.json new file mode 100644 index 00000000..701102fe --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.1.1/overrides.json @@ -0,0 +1,69 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + }, + { + "ecosystem": "npm", + "name": "string-locale-compare", + "namespace": "@isaacs", + "version": "1.1.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz", + "integrity": { + "sha512": "sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==" + } + }, + { + "ecosystem": "npm", + "name": "use-sync-external-store", + "version": "1.2.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz", + "integrity": { + "sha512": "sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==" + } + }, + { + "ecosystem": "npm", + "name": "ms", + "version": "2.1.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz", + "integrity": { + "sha512": "sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==" + } + }, + { + "ecosystem": "npm", + "name": "semver", + "version": "7.6.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "ffffffff-ffff-4fff-8fff-ffffffffffff", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz", + "integrity": { + "sha512": "sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==" + } + }, + { + "ecosystem": "npm", + "name": "fsevents", + "version": "2.3.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "00000000-0000-4000-8000-000000000000", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz", + "integrity": { + "sha512": "sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/expected-confirmation.json new file mode 100644 index 00000000..4b065991 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/expected-confirmation.json @@ -0,0 +1,12 @@ +{ + "confirmed": [ + "00000000-0000-4000-8000-000000000000", + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "ffffffff-ffff-4fff-8fff-ffffffffffff" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/expected-edits.json new file mode 100644 index 00000000..89901272 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/expected-edits.json @@ -0,0 +1,50 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\",\"https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz\"]", + "new": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "@isaacs/string-locale-compare@1.1.0", + "original": "\"~npm~@isaacs+string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==\",\"https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz\"]", + "new": "\"~npm~@isaacs+string-locale-compare@1.1.0\": [0,\"@isaacs/string-locale-compare\",\"sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "original": "\"~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba\": [0,\"use-sync-external-store\",\"sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==\",\"https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz\"]", + "new": "\"~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba\": [0,\"use-sync-external-store\",\"sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "original": "\"~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms\": [0,\"ms\",\"sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==\",\"https://registry.npmjs.org/ms/-/ms-2.1.3.tgz\"]", + "new": "\"~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms\": [0,\"ms\",\"sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "semver@7.6.0", + "original": "\"~npm~semver@7.6.0\": [0,\"semver\",\"sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==\",\"https://registry.npmjs.org/semver/-/semver-7.6.0.tgz\",null,null,null,null,{ \"semver\": \"bin/semver.js\"}]", + "new": "\"~npm~semver@7.6.0\": [0,\"semver\",\"sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz\",null,null,null,null,{ \"semver\": \"bin/semver.js\"}]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "fsevents@2.3.3", + "original": "\"~npm~fsevents@2.3.3\": [1,\"fsevents\",\"sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==\",\"https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz\",null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]", + "new": "\"~npm~fsevents@2.3.3\": [1,\"fsevents\",\"sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz\",null,null,null,{ \"engines\": { \"node\": \"^8.16.0 || ^10.6.0 || >=11.0.0\" }, \"os\": [ \"darwin\" ]}]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/expected/vlt-lock.json new file mode 100644 index 00000000..9a54c540 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/expected/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + }, + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz",null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw==","https://registry.npmjs.org/left-pad/-/left-pad-1.1.3.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file~_d ms": "dev 2.1.2 ~npm~ms@2.1.2", + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "file~_d lp-alias": "prod npm:left-pad@1.1.3 ~npm~left-pad@1.1.3", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/input/vlt-lock.json new file mode 100644 index 00000000..54da8c22 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/input/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + }, + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==","https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw==","https://registry.npmjs.org/left-pad/-/left-pad-1.1.3.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file~_d ms": "dev 2.1.2 ~npm~ms@2.1.2", + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "file~_d lp-alias": "prod npm:left-pad@1.1.3 ~npm~left-pad@1.1.3", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/input/vlt.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/input/vlt.json new file mode 100644 index 00000000..ab3aa87e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/input/vlt.json @@ -0,0 +1 @@ +{"config": {"registries": {"npm": "https://registry.npmjs.org/"}}, "modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/overrides.json new file mode 100644 index 00000000..701102fe --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/capture-1.2.0/overrides.json @@ -0,0 +1,69 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + }, + { + "ecosystem": "npm", + "name": "string-locale-compare", + "namespace": "@isaacs", + "version": "1.1.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/string-locale-compare-1.1.0.tgz", + "integrity": { + "sha512": "sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==" + } + }, + { + "ecosystem": "npm", + "name": "use-sync-external-store", + "version": "1.2.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz", + "integrity": { + "sha512": "sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==" + } + }, + { + "ecosystem": "npm", + "name": "ms", + "version": "2.1.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz", + "integrity": { + "sha512": "sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==" + } + }, + { + "ecosystem": "npm", + "name": "semver", + "version": "7.6.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "ffffffff-ffff-4fff-8fff-ffffffffffff", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/ffffffff-ffff-4fff-8fff-ffffffffffff/semver-7.6.0.tgz", + "integrity": { + "sha512": "sha512-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF==" + } + }, + { + "ecosystem": "npm", + "name": "fsevents", + "version": "2.3.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "00000000-0000-4000-8000-000000000000", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/00000000-0000-4000-8000-000000000000/fsevents-2.3.3.tgz", + "integrity": { + "sha512": "sha512-00000000000000000000000000000000000000000000000000000000000000000000000000000000000000==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/expected-edits.json new file mode 100644 index 00000000..5a1e33aa --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\",\"https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz\"]", + "new": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/expected/vlt-lock.json new file mode 100644 index 00000000..01bda51f --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/expected/vlt-lock.json @@ -0,0 +1,16 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/input/vlt-lock.json new file mode 100644 index 00000000..01d3a58c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/input/vlt-lock.json @@ -0,0 +1,16 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/crlf/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/expected-confirmation.json new file mode 100644 index 00000000..473f0bc6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/expected-confirmation.json @@ -0,0 +1,5 @@ +{ + "confirmed": [], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/expected-edits.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/expected-edits.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/expected-warnings.json new file mode 100644 index 00000000..ebc409b6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/expected-warnings.json @@ -0,0 +1,4 @@ +[ + "redirect_vlt_custom_registry_skipped", + "redirect_vlt_entry_not_found" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/input/vlt-lock.json new file mode 100644 index 00000000..814a0008 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/input/vlt-lock.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "acme": "https://acme.example.com/", + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~acme~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://acme.example.com/left-pad/-/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod acme:left-pad@1.3.0 ~acme~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/custom-registry-skipped/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/expected-edits.json new file mode 100644 index 00000000..05c8326a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~corp~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\",\"https://corp.example.com/left-pad/-/left-pad-1.3.0.tgz\"]", + "new": "\"~corp~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/expected/vlt-lock.json new file mode 100644 index 00000000..5a686662 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/expected/vlt-lock.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 1, + "options": { + "default-registry-alias": "corp", + "registries": { + "corp": "https://corp.example.com/" + } + }, + "nodes": { + "~corp~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~corp~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/input/vlt-lock.json new file mode 100644 index 00000000..8334a617 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/input/vlt-lock.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 1, + "options": { + "default-registry-alias": "corp", + "registries": { + "corp": "https://corp.example.com/" + } + }, + "nodes": { + "~corp~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://corp.example.com/left-pad/-/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~corp~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/default-registry-alias/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/expected-confirmation.json new file mode 100644 index 00000000..e4bdb58e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/expected-confirmation.json @@ -0,0 +1,5 @@ +{ + "confirmed": [], + "refused": [], + "vltDrives": false +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/expected-edits.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/expected-edits.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/expected-warnings.json new file mode 100644 index 00000000..a73a1b0e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_no_lockfile" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/input/node_modules/.vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/input/node_modules/.vlt-lock.json new file mode 100644 index 00000000..e69de29b diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/input/vlt.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/input/vlt.json new file mode 100644 index 00000000..8898a254 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/input/vlt.json @@ -0,0 +1 @@ +{"config": {"registries": {"npm": "https://registry.npmjs.org/"}}} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/expected-confirmation.json new file mode 100644 index 00000000..473f0bc6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/expected-confirmation.json @@ -0,0 +1,5 @@ +{ + "confirmed": [], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/expected-edits.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/expected-edits.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/expected-warnings.json new file mode 100644 index 00000000..5331bd1a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_lock_unsupported" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/input/vlt-lock.json new file mode 100644 index 00000000..47b10b7f --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/input/vlt-lock.json @@ -0,0 +1,9 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad- \ No newline at end of file diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/invalid-json/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/expected-confirmation.json new file mode 100644 index 00000000..f5aa4421 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "33333333-3333-4333-8333-333333333333" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/expected-edits.json new file mode 100644 index 00000000..75809e11 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "@std/path@1.0.0", + "original": "\"~npm~@std+path@1.0.0\": [0,\"@std/path\",\"sha512-jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj==\",\"https://registry.npmjs.org/@std/path/-/path-1.0.0.tgz\"]", + "new": "\"~npm~@std+path@1.0.0\": [0,\"@std/path\",\"sha512-33333333333333333333333333333333333333333333333333333333333333333333333333333333333333==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/33333333-3333-4333-8333-333333333333/path-1.0.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/expected-warnings.json new file mode 100644 index 00000000..dfc8880b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_custom_registry_skipped" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/expected/vlt-lock.json new file mode 100644 index 00000000..6bb84ef6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/expected/vlt-lock.json @@ -0,0 +1,19 @@ +{ + "lockfileVersion": 1, + "options": { + "jsr-registries": { + "jsr": "https://npm.jsr.io/" + }, + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~jsr~@std+path@1.0.0": [0,"@std/path","sha512-jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj==","https://npm.jsr.io/~/11/@jsr/std__path/1.0.0.tgz"], + "~npm~@std+path@1.0.0": [0,"@std/path","sha512-33333333333333333333333333333333333333333333333333333333333333333333333333333333333333==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/33333333-3333-4333-8333-333333333333/path-1.0.0.tgz"] + }, + "edges": { + "file~_d @std/path": "prod jsr:@std/path@1.0.0 ~jsr~@std+path@1.0.0", + "workspace~packages+a @std/path": "prod 1.0.0 ~npm~@std+path@1.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/input/vlt-lock.json new file mode 100644 index 00000000..f13c4620 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/input/vlt-lock.json @@ -0,0 +1,19 @@ +{ + "lockfileVersion": 1, + "options": { + "jsr-registries": { + "jsr": "https://npm.jsr.io/" + }, + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~jsr~@std+path@1.0.0": [0,"@std/path","sha512-jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj==","https://npm.jsr.io/~/11/@jsr/std__path/1.0.0.tgz"], + "~npm~@std+path@1.0.0": [0,"@std/path","sha512-jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj==","https://registry.npmjs.org/@std/path/-/path-1.0.0.tgz"] + }, + "edges": { + "file~_d @std/path": "prod jsr:@std/path@1.0.0 ~jsr~@std+path@1.0.0", + "workspace~packages+a @std/path": "prod 1.0.0 ~npm~@std+path@1.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/overrides.json new file mode 100644 index 00000000..798fb122 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/jsr-skipped/overrides.json @@ -0,0 +1,14 @@ +[ + { + "ecosystem": "npm", + "name": "path", + "namespace": "@std", + "version": "1.0.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "33333333-3333-4333-8333-333333333333", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/33333333-3333-4333-8333-333333333333/path-1.0.0.tgz", + "integrity": { + "sha512": "sha512-33333333333333333333333333333333333333333333333333333333333333333333333333333333333333==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/expected-edits.json new file mode 100644 index 00000000..837aaccf --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/expected-edits.json @@ -0,0 +1,18 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"·npm·left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"·npm·left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/expected/vlt-lock.json new file mode 100644 index 00000000..a3293a9d --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/expected/vlt-lock.json @@ -0,0 +1,12 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "··left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "·npm·left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0", + "workspace·packages§a left-pad": "prod npm:left-pad@1.3.0 ·npm·left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/input/vlt-lock.json new file mode 100644 index 00000000..886c32b7 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/input/vlt-lock.json @@ -0,0 +1,12 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "··left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "·npm·left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0", + "workspace·packages§a left-pad": "prod npm:left-pad@1.3.0 ·npm·left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/input/vlt.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/input/vlt.json new file mode 100644 index 00000000..84ead0d5 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/input/vlt.json @@ -0,0 +1 @@ +{"modifiers": {}} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/legacy-mixed-default-segments/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/expected-edits.json new file mode 100644 index 00000000..e6479a1a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/expected-warnings.json new file mode 100644 index 00000000..f3a6a174 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/expected-warnings.json @@ -0,0 +1,4 @@ +[ + "redirect_vlt_lockfile_version_missing", + "redirect_vlt_old_lockfile_ignored" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/expected/vlt-lock.json new file mode 100644 index 00000000..2c52d629 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/expected/vlt-lock.json @@ -0,0 +1,9 @@ +{ + "options": {}, + "nodes": { + "··left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/input/vlt-lock.json new file mode 100644 index 00000000..43bb8922 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/input/vlt-lock.json @@ -0,0 +1,9 @@ +{ + "options": {}, + "nodes": { + "··left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version-warning/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/expected-edits.json new file mode 100644 index 00000000..e6479a1a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/expected-warnings.json new file mode 100644 index 00000000..7111bf57 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_lockfile_version_missing" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/expected/vlt-lock.json new file mode 100644 index 00000000..2c52d629 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/expected/vlt-lock.json @@ -0,0 +1,9 @@ +{ + "options": {}, + "nodes": { + "··left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/input/vlt-lock.json new file mode 100644 index 00000000..43bb8922 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/input/vlt-lock.json @@ -0,0 +1,9 @@ +{ + "options": {}, + "nodes": { + "··left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/input/vlt.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/input/vlt.json new file mode 100644 index 00000000..84ead0d5 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/input/vlt.json @@ -0,0 +1 @@ +{"modifiers": {}} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-absent-version/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/expected-edits.json new file mode 100644 index 00000000..e6479a1a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/expected-warnings.json new file mode 100644 index 00000000..e533ff00 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_old_lockfile_ignored" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/expected/vlt-lock.json new file mode 100644 index 00000000..a3f179e4 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/expected/vlt-lock.json @@ -0,0 +1,10 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "··left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/input/vlt-lock.json new file mode 100644 index 00000000..2767564b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/input/vlt-lock.json @@ -0,0 +1,10 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "··left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/expected-edits.json new file mode 100644 index 00000000..e6479a1a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/expected/vlt-lock.json new file mode 100644 index 00000000..a3f179e4 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/expected/vlt-lock.json @@ -0,0 +1,10 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "··left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/input/vlt-lock.json new file mode 100644 index 00000000..2767564b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/input/vlt-lock.json @@ -0,0 +1,10 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "··left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/input/vlt.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/input/vlt.json new file mode 100644 index 00000000..a237a320 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/input/vlt.json @@ -0,0 +1,3 @@ +{ + "modifiers": {} +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-empty-segment/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/expected-edits.json new file mode 100644 index 00000000..eca66dc9 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"·npm·left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"·npm·left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/expected/vlt-lock.json new file mode 100644 index 00000000..d4c50950 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/expected/vlt-lock.json @@ -0,0 +1,14 @@ +{ + "lockfileVersion": 0, + "options": { + "registries": {} + }, + "nodes": { + "·npm·left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "·npm·ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "file·. ms": "prod 2.1.3 ·npm·ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/input/vlt-lock.json new file mode 100644 index 00000000..b472d35e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/input/vlt-lock.json @@ -0,0 +1,14 @@ +{ + "lockfileVersion": 0, + "options": { + "registries": {} + }, + "nodes": { + "·npm·left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "·npm·ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "file·. ms": "prod 2.1.3 ·npm·ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-npm-segment/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/expected-edits.json new file mode 100644 index 00000000..16751b0a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"·http%3A§§127.0.0.1%3A4873§·left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"·http%3A§§127.0.0.1%3A4873§·left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/expected-warnings.json new file mode 100644 index 00000000..4e6b7346 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/expected-warnings.json @@ -0,0 +1,4 @@ +[ + "redirect_vlt_old_lockfile_ignored", + "redirect_vlt_scalar_registry_ignored" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/expected/vlt-lock.json new file mode 100644 index 00000000..4f5f8c8a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/expected/vlt-lock.json @@ -0,0 +1,12 @@ +{ + "lockfileVersion": 0, + "options": { + "registry": "http://127.0.0.1:4873/" + }, + "nodes": { + "·http%3A§§127.0.0.1%3A4873§·left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ·http%3A§§127.0.0.1%3A4873§·left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/input/vlt-lock.json new file mode 100644 index 00000000..30238c6d --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/input/vlt-lock.json @@ -0,0 +1,12 @@ +{ + "lockfileVersion": 0, + "options": { + "registry": "http://127.0.0.1:4873/" + }, + "nodes": { + "·http%3A§§127.0.0.1%3A4873§·left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ·http%3A§§127.0.0.1%3A4873§·left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/expected-edits.json new file mode 100644 index 00000000..78994cde --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/expected/vlt-lock.json new file mode 100644 index 00000000..9ac5a2e9 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/expected/vlt-lock.json @@ -0,0 +1,12 @@ +{ + "lockfileVersion": 1, + "options": {}, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/input/vlt-lock.json new file mode 100644 index 00000000..237026c2 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/input/vlt-lock.json @@ -0,0 +1,12 @@ +{ + "lockfileVersion": 1, + "options": {}, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-3tuple/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/expected-edits.json new file mode 100644 index 00000000..5a1e33aa --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\",\"https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz\"]", + "new": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/expected/vlt-lock.json new file mode 100644 index 00000000..0716ebd9 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/expected/vlt-lock.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 1, + "options": { + "registry": "https://registry.npmjs.org/", + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/input/vlt-lock.json new file mode 100644 index 00000000..1f881fcb --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/input/vlt-lock.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 1, + "options": { + "registry": "https://registry.npmjs.org/", + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-both-registry-keys/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/expected-edits.json new file mode 100644 index 00000000..562cee06 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~https_c++registry.npmjs.org+~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"~https_c++registry.npmjs.org+~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/expected/vlt-lock.json new file mode 100644 index 00000000..ca1aeb36 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/expected/vlt-lock.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 1, + "options": { + "registry": "https://registry.npmjs.org/", + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~https_c++registry.npmjs.org+~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~https_c++registry.npmjs.org+~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/input/vlt-lock.json new file mode 100644 index 00000000..be30e2ff --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/input/vlt-lock.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 1, + "options": { + "registry": "https://registry.npmjs.org/", + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~https_c++registry.npmjs.org+~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~https_c++registry.npmjs.org+~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v1-scalar-registry-3tuple/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/expected-confirmation.json new file mode 100644 index 00000000..473f0bc6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/expected-confirmation.json @@ -0,0 +1,5 @@ +{ + "confirmed": [], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/expected-edits.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/expected-edits.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/expected-warnings.json new file mode 100644 index 00000000..5331bd1a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_lock_unsupported" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/input/vlt-lock.json new file mode 100644 index 00000000..f513e374 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/input/vlt-lock.json @@ -0,0 +1,16 @@ +{ + "lockfileVersion": 1e0, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-exponent/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/expected-confirmation.json new file mode 100644 index 00000000..473f0bc6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/expected-confirmation.json @@ -0,0 +1,5 @@ +{ + "confirmed": [], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/expected-edits.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/expected-edits.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/expected-warnings.json new file mode 100644 index 00000000..5331bd1a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_lock_unsupported" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/input/vlt-lock.json new file mode 100644 index 00000000..c44bc907 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/input/vlt-lock.json @@ -0,0 +1,16 @@ +{ + "lockfileVersion": 1.0, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-float/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/expected-confirmation.json new file mode 100644 index 00000000..473f0bc6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/expected-confirmation.json @@ -0,0 +1,5 @@ +{ + "confirmed": [], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/expected-edits.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/expected-edits.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/expected-warnings.json new file mode 100644 index 00000000..5331bd1a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_lock_unsupported" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/input/vlt-lock.json new file mode 100644 index 00000000..f6c53e68 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/input/vlt-lock.json @@ -0,0 +1,16 @@ +{ + "lockfileVersion": "1", + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-string/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/expected-confirmation.json new file mode 100644 index 00000000..473f0bc6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/expected-confirmation.json @@ -0,0 +1,5 @@ +{ + "confirmed": [], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/expected-edits.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/expected-edits.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/expected-warnings.json new file mode 100644 index 00000000..5331bd1a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_lock_unsupported" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/input/vlt-lock.json new file mode 100644 index 00000000..bc05b5ae --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/input/vlt-lock.json @@ -0,0 +1,16 @@ +{ + "lockfileVersion": 2, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-version-unsupported/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/expected-confirmation.json new file mode 100644 index 00000000..473f0bc6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/expected-confirmation.json @@ -0,0 +1,5 @@ +{ + "confirmed": [], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/expected-edits.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/expected-edits.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/expected-warnings.json new file mode 100644 index 00000000..5331bd1a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_lock_unsupported" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/input/vlt-lock.json new file mode 100644 index 00000000..1ba97162 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/input/vlt-lock.json @@ -0,0 +1,16 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/\ud800.js"}] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lone-surrogate-string/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/expected-edits.json new file mode 100644 index 00000000..810fca2b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\",\"https://mirror.example.com/npm/left-pad/-/left-pad-1.3.0.tgz\"]", + "new": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/expected/vlt-lock.json new file mode 100644 index 00000000..3b3ed525 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/expected/vlt-lock.json @@ -0,0 +1,14 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://mirror.example.com/npm/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/input/vlt-lock.json new file mode 100644 index 00000000..7ddcd5d8 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/input/vlt-lock.json @@ -0,0 +1,14 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://mirror.example.com/npm/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://mirror.example.com/npm/left-pad/-/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mirror-registries-npm/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/expected-confirmation.json new file mode 100644 index 00000000..37fd4d64 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [], + "refused": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/expected-edits.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/expected-edits.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/expected-warnings.json new file mode 100644 index 00000000..94996b7c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_missing_sha512" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/input/vlt-lock.json new file mode 100644 index 00000000..e81e65c3 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/input/vlt-lock.json @@ -0,0 +1,16 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/overrides.json new file mode 100644 index 00000000..a651d80a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/missing-sha512/overrides.json @@ -0,0 +1,11 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": {} + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/expected-confirmation.json new file mode 100644 index 00000000..07379947 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/expected-confirmation.json @@ -0,0 +1,8 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "dddddddd-dddd-4ddd-8ddd-dddddddddddd" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/expected-edits.json new file mode 100644 index 00000000..0ef35173 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/expected-edits.json @@ -0,0 +1,18 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\",\"https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz\"]", + "new": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "ms@2.1.3", + "original": "\"~npm~ms@2.1.3\": [0,\"ms\",\"sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==\",\"https://registry.npmjs.org/ms/-/ms-2.1.3.tgz\"]", + "new": "\"~npm~ms@2.1.3\": [0,\"ms\",\"sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/expected/vlt-lock.json new file mode 100644 index 00000000..41f81496 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/expected/vlt-lock.json @@ -0,0 +1,18 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3", + "file~_d yallist": "prod 4.0.0 ~npm~yallist@4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/input/vlt-lock.json new file mode 100644 index 00000000..54d2d69c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/input/vlt-lock.json @@ -0,0 +1,18 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3", + "file~_d yallist": "prod 4.0.0 ~npm~yallist@4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/overrides.json new file mode 100644 index 00000000..46e4417e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/mixed-eol/overrides.json @@ -0,0 +1,24 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + }, + { + "ecosystem": "npm", + "name": "ms", + "version": "2.1.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz", + "integrity": { + "sha512": "sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/expected-confirmation.json new file mode 100644 index 00000000..02f7155f --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/expected-confirmation.json @@ -0,0 +1,8 @@ +{ + "confirmed": [ + "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/expected-edits.json new file mode 100644 index 00000000..1495660a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/expected-edits.json @@ -0,0 +1,18 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "original": "\"~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms\": [0,\"ms\",\"sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==\",\"https://registry.npmjs.org/ms/-/ms-2.1.3.tgz\"]", + "new": "\"~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms\": [0,\"ms\",\"sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "original": "\"~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms\": [0,\"debug\",\"sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==\",\"https://registry.npmjs.org/debug/-/debug-4.3.4.tgz\"]", + "new": "\"~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms\": [0,\"debug\",\"sha512-EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee/debug-4.3.4.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/expected/vlt-lock.json new file mode 100644 index 00000000..27da90fc --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/expected/vlt-lock.json @@ -0,0 +1,19 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + }, + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee/debug-4.3.4.tgz"], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz"] + }, + "edges": { + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/input/vlt-lock.json new file mode 100644 index 00000000..308ae1c4 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/input/vlt-lock.json @@ -0,0 +1,19 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + }, + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"] + }, + "edges": { + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/overrides.json new file mode 100644 index 00000000..d7212771 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/modifier-extra/overrides.json @@ -0,0 +1,24 @@ +[ + { + "ecosystem": "npm", + "name": "ms", + "version": "2.1.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/dddddddd-dddd-4ddd-8ddd-dddddddddddd/ms-2.1.3.tgz", + "integrity": { + "sha512": "sha512-DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD==" + } + }, + { + "ecosystem": "npm", + "name": "debug", + "version": "4.3.4", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee/debug-4.3.4.tgz", + "integrity": { + "sha512": "sha512-EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/expected-edits.json new file mode 100644 index 00000000..5a1e33aa --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\",\"https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz\"]", + "new": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/expected/vlt-lock.json new file mode 100644 index 00000000..094fd045 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/expected/vlt-lock.json @@ -0,0 +1,16 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw==","https://registry.npmjs.org/left-pad/-/left-pad-1.1.3.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "workspace~packages+a left-pad": "prod 1.1.3 ~npm~left-pad@1.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/input/vlt-lock.json new file mode 100644 index 00000000..2080c326 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/input/vlt-lock.json @@ -0,0 +1,16 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw==","https://registry.npmjs.org/left-pad/-/left-pad-1.1.3.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "workspace~packages+a left-pad": "prod 1.1.3 ~npm~left-pad@1.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/multiple-versions/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/expected-confirmation.json new file mode 100644 index 00000000..e4bdb58e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/expected-confirmation.json @@ -0,0 +1,5 @@ +{ + "confirmed": [], + "refused": [], + "vltDrives": false +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/expected-edits.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/expected-edits.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/expected-warnings.json new file mode 100644 index 00000000..a73a1b0e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_no_lockfile" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/input/vlt.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/input/vlt.json new file mode 100644 index 00000000..edaf227f --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/input/vlt.json @@ -0,0 +1,7 @@ +{ + "config": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/no-lockfile-vlt-json/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/expected-confirmation.json new file mode 100644 index 00000000..473f0bc6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/expected-confirmation.json @@ -0,0 +1,5 @@ +{ + "confirmed": [], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/expected-edits.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/expected-edits.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/expected-warnings.json new file mode 100644 index 00000000..5331bd1a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_lock_unsupported" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/input/vlt-lock.json new file mode 100644 index 00000000..8d9d44b8 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/input/vlt-lock.json @@ -0,0 +1,26 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [ + 0, + "left-pad", + "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", + "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz" + ], + "~npm~ms@2.1.3": [ + 0, + "ms", + "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + ] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/non-canonical-layout-refusal/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/expected-confirmation.json new file mode 100644 index 00000000..473f0bc6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/expected-confirmation.json @@ -0,0 +1,5 @@ +{ + "confirmed": [], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/expected-edits.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/expected-edits.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/expected-warnings.json new file mode 100644 index 00000000..5331bd1a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_lock_unsupported" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/input/vlt-lock.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/input/vlt-lock.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/not-json-object/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/expected-confirmation.json new file mode 100644 index 00000000..f3bbf511 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "cccccccc-cccc-4ccc-8ccc-cccccccccccc" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/expected-edits.json new file mode 100644 index 00000000..ad58f974 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/expected-edits.json @@ -0,0 +1,26 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "use-sync-external-store@1.2.0~%E1%B9%97%3A3", + "original": "\"·npm·use-sync-external-store@1.2.0·%E1%B9%97%3A3\": [0,\"use-sync-external-store\",\"sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==\"]", + "new": "\"·npm·use-sync-external-store@1.2.0·%E1%B9%97%3A3\": [0,\"use-sync-external-store\",\"sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "original": "\"~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba\": [0,\"use-sync-external-store\",\"sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==\",\"https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz\"]", + "new": "\"~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba\": [0,\"use-sync-external-store\",\"sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "use-sync-external-store@1.2.0~peer.2", + "original": "\"~npm~use-sync-external-store@1.2.0~peer.2\": [0,\"use-sync-external-store\",\"sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==\",\"https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz\"]", + "new": "\"~npm~use-sync-external-store@1.2.0~peer.2\": [0,\"use-sync-external-store\",\"sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/expected/vlt-lock.json new file mode 100644 index 00000000..f9681f3f --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/expected/vlt-lock.json @@ -0,0 +1,21 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "·npm·use-sync-external-store@1.2.0·%E1%B9%97%3A3": [0,"use-sync-external-store","sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.2": [0,"use-sync-external-store","sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz"] + }, + "edges": { + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "workspace~packages+a use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.2", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0", + "~npm~use-sync-external-store@1.2.0~peer.2 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/input/vlt-lock.json new file mode 100644 index 00000000..4eff1fab --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/input/vlt-lock.json @@ -0,0 +1,21 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "·npm·use-sync-external-store@1.2.0·%E1%B9%97%3A3": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.2": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"] + }, + "edges": { + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "workspace~packages+a use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.2", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0", + "~npm~use-sync-external-store@1.2.0~peer.2 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/overrides.json new file mode 100644 index 00000000..ea2d7386 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/peer-extras/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "use-sync-external-store", + "version": "1.2.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/cccccccc-cccc-4ccc-8ccc-cccccccccccc/use-sync-external-store-1.2.0.tgz", + "integrity": { + "sha512": "sha512-CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/expected-edits.json new file mode 100644 index 00000000..4ab8f56d --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO==\",\"https://patch.socket.dev/patch/npm/22222222-2222-2222-2222-222222222222/66666666-6666-4666-8666-666666666666/left-pad-1.3.0.tgz\"]", + "new": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/expected/vlt-lock.json new file mode 100644 index 00000000..c63cd23c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/expected/vlt-lock.json @@ -0,0 +1,14 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/input/vlt-lock.json new file mode 100644 index 00000000..fb3ac2a1 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/input/vlt-lock.json @@ -0,0 +1,14 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO==","https://patch.socket.dev/patch/npm/22222222-2222-2222-2222-222222222222/66666666-6666-4666-8666-666666666666/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/re-redirect-stale-url/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/expected-edits.json new file mode 100644 index 00000000..5a1e33aa --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\",\"https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz\"]", + "new": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/expected/vlt-lock.json new file mode 100644 index 00000000..a9566142 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/expected/vlt-lock.json @@ -0,0 +1,20 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "file~vendor+left-pad": [0,"left-pad",null,"vendor/left-pad"], + "git~github_cfoo+left-pad~v1.3.0": [0,"left-pad"], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.3.0.tgz": [0,"left-pad","sha512-rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr=="], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d lp-dir": "prod file:vendor/left-pad file~vendor+left-pad", + "file~_d lp-git": "prod github:foo/left-pad#v1.3.0 git~github_cfoo+left-pad~v1.3.0", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.3.0.tgz" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/input/vlt-lock.json new file mode 100644 index 00000000..ae7a10a6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/input/vlt-lock.json @@ -0,0 +1,20 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "file~vendor+left-pad": [0,"left-pad",null,"vendor/left-pad"], + "git~github_cfoo+left-pad~v1.3.0": [0,"left-pad"], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.3.0.tgz": [0,"left-pad","sha512-rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr=="], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d lp-dir": "prod file:vendor/left-pad file~vendor+left-pad", + "file~_d lp-git": "prod github:foo/left-pad#v1.3.0 git~github_cfoo+left-pad~v1.3.0", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.3.0.tgz" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/remote-file-git-untouched/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/expected-edits.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/expected-edits.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/input/vlt-lock.json new file mode 100644 index 00000000..c63cd23c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/input/vlt-lock.json @@ -0,0 +1,14 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/rerun-noop/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/expected-confirmation.json new file mode 100644 index 00000000..37fd4d64 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [], + "refused": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/expected-edits.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/expected-edits.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/expected-warnings.json new file mode 100644 index 00000000..c91b1068 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_unsupported_lock_key" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/input/vlt-lock.json new file mode 100644 index 00000000..51f0fc25 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/input/vlt-lock.json @@ -0,0 +1,13 @@ +{ + "lockfileVersion": 1, + "options": {}, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="] + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/residual-gate-duplicate-nodes/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/expected-edits.json new file mode 100644 index 00000000..ff81330e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"·https%3A§§registry.npmjs.org§·left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"·https%3A§§registry.npmjs.org§·left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/expected-warnings.json new file mode 100644 index 00000000..13e48044 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_scalar_registry_ignored" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/expected/vlt-lock.json new file mode 100644 index 00000000..62820e0b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/expected/vlt-lock.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 0, + "options": { + "registry": "https://registry.npmjs.org/", + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "·https%3A§§registry.npmjs.org§·left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ·https%3A§§registry.npmjs.org§·left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/input/vlt-lock.json new file mode 100644 index 00000000..e90e5486 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/input/vlt-lock.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 0, + "options": { + "registry": "https://registry.npmjs.org/", + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "·https%3A§§registry.npmjs.org§·left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ·https%3A§§registry.npmjs.org§·left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/input/vlt.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/input/vlt.json new file mode 100644 index 00000000..84ead0d5 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/input/vlt.json @@ -0,0 +1 @@ +{"modifiers": {}} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-v0-with-registries-npm/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/expected-edits.json new file mode 100644 index 00000000..562cee06 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~https_c++registry.npmjs.org+~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"~https_c++registry.npmjs.org+~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/expected-warnings.json new file mode 100644 index 00000000..13e48044 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_scalar_registry_ignored" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/expected/vlt-lock.json new file mode 100644 index 00000000..a882453b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/expected/vlt-lock.json @@ -0,0 +1,12 @@ +{ + "lockfileVersion": 1, + "options": { + "registry": "https://registry.npmjs.org/" + }, + "nodes": { + "~https_c++registry.npmjs.org+~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~https_c++registry.npmjs.org+~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/input/vlt-lock.json new file mode 100644 index 00000000..517a2162 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/input/vlt-lock.json @@ -0,0 +1,12 @@ +{ + "lockfileVersion": 1, + "options": { + "registry": "https://registry.npmjs.org/" + }, + "nodes": { + "~https_c++registry.npmjs.org+~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~https_c++registry.npmjs.org+~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scalar-registry-warning/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/expected-confirmation.json new file mode 100644 index 00000000..82a6ee81 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/expected-edits.json new file mode 100644 index 00000000..850cb438 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/expected-edits.json @@ -0,0 +1,18 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "@a/b@1.0.0", + "original": "\"··@a§b@1.0.0\": [0,\"@a/b\",\"sha512-ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss==\"]", + "new": "\"··@a§b@1.0.0\": [0,\"@a/b\",\"sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/b-1.0.0.tgz\"]" + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "@a/b@1.0.0", + "original": "\"~npm~@a+b@1.0.0\": [0,\"@a/b\",\"sha512-ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss==\",\"https://registry.npmjs.org/@a/b/-/b-1.0.0.tgz\"]", + "new": "\"~npm~@a+b@1.0.0\": [0,\"@a/b\",\"sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/b-1.0.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/expected/vlt-lock.json new file mode 100644 index 00000000..a53d2f0c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/expected/vlt-lock.json @@ -0,0 +1,18 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "··@a§b@1.0.0": [0,"@a/b","sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/b-1.0.0.tgz"], + "~npm~@a+b@1.0.0": [0,"@a/b","sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/b-1.0.0.tgz"], + "~npm~b@1.0.0": [0,"b","sha512-tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt==","https://registry.npmjs.org/b/-/b-1.0.0.tgz"] + }, + "edges": { + "file~_d @a/b": "prod 1.0.0 ~npm~@a+b@1.0.0", + "file~_d b": "prod 1.0.0 ~npm~b@1.0.0", + "workspace~packages+x @a/b": "prod 1.0.0 ··@a§b@1.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/input/vlt-lock.json new file mode 100644 index 00000000..0f3b9e93 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/input/vlt-lock.json @@ -0,0 +1,18 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "··@a§b@1.0.0": [0,"@a/b","sha512-ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss=="], + "~npm~@a+b@1.0.0": [0,"@a/b","sha512-ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss==","https://registry.npmjs.org/@a/b/-/b-1.0.0.tgz"], + "~npm~b@1.0.0": [0,"b","sha512-tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt==","https://registry.npmjs.org/b/-/b-1.0.0.tgz"] + }, + "edges": { + "file~_d @a/b": "prod 1.0.0 ~npm~@a+b@1.0.0", + "file~_d b": "prod 1.0.0 ~npm~b@1.0.0", + "workspace~packages+x @a/b": "prod 1.0.0 ··@a§b@1.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/overrides.json new file mode 100644 index 00000000..94f365ad --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-package/overrides.json @@ -0,0 +1,14 @@ +[ + { + "ecosystem": "npm", + "name": "b", + "namespace": "@a", + "version": "1.0.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/b-1.0.0.tgz", + "integrity": { + "sha512": "sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/expected-confirmation.json new file mode 100644 index 00000000..82a6ee81 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/expected-edits.json new file mode 100644 index 00000000..d1ac3ba9 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "@a/b@1.0.0", + "original": "\"~npm~@a+b@1.0.0\": [0,\"@a/b\",\"sha512-ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss==\",\"https://registry.npmjs.org/@a/b/-/b-1.0.0.tgz\"]", + "new": "\"~npm~@a+b@1.0.0\": [0,\"@a/b\",\"sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/b-1.0.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/expected-warnings.json new file mode 100644 index 00000000..dfc8880b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_custom_registry_skipped" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/expected/vlt-lock.json new file mode 100644 index 00000000..dd60371a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/expected/vlt-lock.json @@ -0,0 +1,19 @@ +{ + "lockfileVersion": 1, + "options": { + "scope-registries": { + "@a": "https://scoped.example.com/" + }, + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~https_c++scoped.example.com+~@a+b@1.0.0": [0,"@a/b","sha512-ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss==","https://scoped.example.com/@a/b/-/b-1.0.0.tgz"], + "~npm~@a+b@1.0.0": [0,"@a/b","sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/b-1.0.0.tgz"] + }, + "edges": { + "file~_d @a/b": "prod 1.0.0 ~https_c++scoped.example.com+~@a+b@1.0.0", + "workspace~packages+a @a/b": "prod 1.0.0 ~npm~@a+b@1.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/input/vlt-lock.json new file mode 100644 index 00000000..1ff37802 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/input/vlt-lock.json @@ -0,0 +1,19 @@ +{ + "lockfileVersion": 1, + "options": { + "scope-registries": { + "@a": "https://scoped.example.com/" + }, + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~https_c++scoped.example.com+~@a+b@1.0.0": [0,"@a/b","sha512-ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss==","https://scoped.example.com/@a/b/-/b-1.0.0.tgz"], + "~npm~@a+b@1.0.0": [0,"@a/b","sha512-ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss==","https://registry.npmjs.org/@a/b/-/b-1.0.0.tgz"] + }, + "edges": { + "file~_d @a/b": "prod 1.0.0 ~https_c++scoped.example.com+~@a+b@1.0.0", + "workspace~packages+a @a/b": "prod 1.0.0 ~npm~@a+b@1.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/overrides.json new file mode 100644 index 00000000..94f365ad --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/scoped-registry-skipped/overrides.json @@ -0,0 +1,14 @@ +[ + { + "ecosystem": "npm", + "name": "b", + "namespace": "@a", + "version": "1.0.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/b-1.0.0.tgz", + "integrity": { + "sha512": "sha512-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected-confirmation.json new file mode 100644 index 00000000..160f829c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected-confirmation.json @@ -0,0 +1,9 @@ +{ + "confirmed": [ + "11111111-1111-4111-8111-111111111111" + ], + "refused": [ + "22222222-2222-4222-8222-222222222222" + ], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected-edits.json new file mode 100644 index 00000000..84b9dc35 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected-edits.json @@ -0,0 +1,38 @@ +[ + { + "path": "package-lock.json", + "kind": "redirect_npm_lock_entry", + "action": "rewritten", + "key": "node_modules/left-pad", + "original": { + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" + }, + "new": { + "resolved": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/11111111-1111-4111-8111-111111111111/left-pad-1.3.0.tgz", + "integrity": "sha512-11111111111111111111111111111111111111111111111111111111111111111111111111111111111111==" + } + }, + { + "path": "package-lock.json", + "kind": "redirect_npm_lock_entry", + "action": "rewritten", + "key": "node_modules/ms", + "original": { + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "new": { + "resolved": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/22222222-2222-4222-8222-222222222222/ms-2.1.3.tgz", + "integrity": "sha512-22222222222222222222222222222222222222222222222222222222222222222222222222222222222222==" + } + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\",\"https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz\"]", + "new": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-11111111111111111111111111111111111111111111111111111111111111111111111111111111111111==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/11111111-1111-4111-8111-111111111111/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected-warnings.json new file mode 100644 index 00000000..c91b1068 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_unsupported_lock_key" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected/package-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected/package-lock.json new file mode 100644 index 00000000..10f82720 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected/package-lock.json @@ -0,0 +1,26 @@ +{ + "name": "consumer", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "consumer", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3" + } + }, + "node_modules/left-pad": { + "version": "1.3.0", + "resolved": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/11111111-1111-4111-8111-111111111111/left-pad-1.3.0.tgz", + "integrity": "sha512-11111111111111111111111111111111111111111111111111111111111111111111111111111111111111==" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/22222222-2222-4222-8222-222222222222/ms-2.1.3.tgz", + "integrity": "sha512-22222222222222222222222222222222222222222222222222222222222222222222222222222222222222==" + } + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected/vlt-lock.json new file mode 100644 index 00000000..56ac9356 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/expected/vlt-lock.json @@ -0,0 +1,21 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-11111111111111111111111111111111111111111111111111111111111111111111111111111111111111==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/11111111-1111-4111-8111-111111111111/left-pad-1.3.0.tgz"], + "~npm~ms@2.1.3": [ + 0, + "ms", + "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + ] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/input/node_modules/.vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/input/node_modules/.vlt-lock.json new file mode 100644 index 00000000..e69de29b diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/input/package-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/input/package-lock.json new file mode 100644 index 00000000..8bea494b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/input/package-lock.json @@ -0,0 +1,26 @@ +{ + "name": "consumer", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "consumer", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3" + } + }, + "node_modules/left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/input/vlt-lock.json new file mode 100644 index 00000000..7c272c09 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/input/vlt-lock.json @@ -0,0 +1,21 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~ms@2.1.3": [ + 0, + "ms", + "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + ] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/overrides.json new file mode 100644 index 00000000..8d72b35d --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock-vlt-installed/overrides.json @@ -0,0 +1,24 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "11111111-1111-4111-8111-111111111111", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/11111111-1111-4111-8111-111111111111/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-11111111111111111111111111111111111111111111111111111111111111111111111111111111111111==" + } + }, + { + "ecosystem": "npm", + "name": "ms", + "version": "2.1.3", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "22222222-2222-4222-8222-222222222222", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/22222222-2222-4222-8222-222222222222/ms-2.1.3.tgz", + "integrity": { + "sha512": "sha512-22222222222222222222222222222222222222222222222222222222222222222222222222222222222222==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected-confirmation.json new file mode 100644 index 00000000..7ef8c995 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "11111111-1111-4111-8111-111111111111" + ], + "refused": [], + "vltDrives": false +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected-edits.json new file mode 100644 index 00000000..3d790caa --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected-edits.json @@ -0,0 +1,24 @@ +[ + { + "path": "package-lock.json", + "kind": "redirect_npm_lock_entry", + "action": "rewritten", + "key": "node_modules/left-pad", + "original": { + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" + }, + "new": { + "resolved": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/11111111-1111-4111-8111-111111111111/left-pad-1.3.0.tgz", + "integrity": "sha512-11111111111111111111111111111111111111111111111111111111111111111111111111111111111111==" + } + }, + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\",\"https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz\"]", + "new": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-11111111111111111111111111111111111111111111111111111111111111111111111111111111111111==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/11111111-1111-4111-8111-111111111111/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected-warnings.json new file mode 100644 index 00000000..27252497 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_sibling_lockfiles" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected/package-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected/package-lock.json new file mode 100644 index 00000000..32d172bf --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected/package-lock.json @@ -0,0 +1,26 @@ +{ + "name": "consumer", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "consumer", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3" + } + }, + "node_modules/left-pad": { + "version": "1.3.0", + "resolved": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/11111111-1111-4111-8111-111111111111/left-pad-1.3.0.tgz", + "integrity": "sha512-11111111111111111111111111111111111111111111111111111111111111111111111111111111111111==" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected/vlt-lock.json new file mode 100644 index 00000000..17466da9 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/expected/vlt-lock.json @@ -0,0 +1,16 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-11111111111111111111111111111111111111111111111111111111111111111111111111111111111111==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/11111111-1111-4111-8111-111111111111/left-pad-1.3.0.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/input/package-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/input/package-lock.json new file mode 100644 index 00000000..8bea494b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/input/package-lock.json @@ -0,0 +1,26 @@ +{ + "name": "consumer", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "consumer", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3" + } + }, + "node_modules/left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/input/vlt-lock.json new file mode 100644 index 00000000..e81e65c3 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/input/vlt-lock.json @@ -0,0 +1,16 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/overrides.json new file mode 100644 index 00000000..04a1bf4b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-package-lock/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "11111111-1111-4111-8111-111111111111", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/11111111-1111-4111-8111-111111111111/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-11111111111111111111111111111111111111111111111111111111111111111111111111111111111111==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/expected-confirmation.json new file mode 100644 index 00000000..c9cd6957 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [], + "refused": [ + "11111111-1111-4111-8111-111111111111" + ], + "vltDrives": false +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/expected-edits.json new file mode 100644 index 00000000..f01adc13 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/expected-edits.json @@ -0,0 +1,16 @@ +[ + { + "path": "package-lock.json", + "kind": "redirect_npm_lock_entry", + "action": "rewritten", + "key": "node_modules/left-pad", + "original": { + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" + }, + "new": { + "resolved": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/11111111-1111-4111-8111-111111111111/left-pad-1.3.0.tgz", + "integrity": "sha512-11111111111111111111111111111111111111111111111111111111111111111111111111111111111111==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/expected-warnings.json new file mode 100644 index 00000000..b83d20a3 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/expected-warnings.json @@ -0,0 +1,4 @@ +[ + "redirect_vlt_sibling_lockfiles", + "redirect_vlt_unsupported_lock_key" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/expected/package-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/expected/package-lock.json new file mode 100644 index 00000000..32d172bf --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/expected/package-lock.json @@ -0,0 +1,26 @@ +{ + "name": "consumer", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "consumer", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3" + } + }, + "node_modules/left-pad": { + "version": "1.3.0", + "resolved": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/11111111-1111-4111-8111-111111111111/left-pad-1.3.0.tgz", + "integrity": "sha512-11111111111111111111111111111111111111111111111111111111111111111111111111111111111111==" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/input/package-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/input/package-lock.json new file mode 100644 index 00000000..8bea494b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/input/package-lock.json @@ -0,0 +1,26 @@ +{ + "name": "consumer", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "consumer", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3" + } + }, + "node_modules/left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/input/vlt-lock.json new file mode 100644 index 00000000..f0b21682 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/input/vlt-lock.json @@ -0,0 +1,21 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [ + 0, + "left-pad", + "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", + "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz" + ], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/overrides.json new file mode 100644 index 00000000..04a1bf4b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/sibling-refused-in-vlt/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "11111111-1111-4111-8111-111111111111", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/11111111-1111-4111-8111-111111111111/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-11111111111111111111111111111111111111111111111111111111111111111111111111111111111111==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/expected-confirmation.json new file mode 100644 index 00000000..37fd4d64 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [], + "refused": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/expected-edits.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/expected-edits.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/expected-warnings.json new file mode 100644 index 00000000..c91b1068 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_unsupported_lock_key" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/input/vlt-lock.json new file mode 100644 index 00000000..69bb6b88 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/input/vlt-lock.json @@ -0,0 +1,21 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~left-pad@1.3.0~peer.1": [ + 0, + "left-pad", + "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==" + ], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/unsupported-lock-key/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/expected-edits.json new file mode 100644 index 00000000..3b857113 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~https_c++registry.example.com+~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"~https_c++registry.example.com+~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/expected/vlt-lock.json new file mode 100644 index 00000000..7c0176ed --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/expected/vlt-lock.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 1, + "options": { + "registry": "https://registry.example.com/", + "registries": { + "npm": "https://registry.example.com/" + } + }, + "nodes": { + "~https_c++registry.example.com+~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~https_c++registry.example.com+~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/input/vlt-lock.json new file mode 100644 index 00000000..b1d72cdd --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/input/vlt-lock.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 1, + "options": { + "registry": "https://registry.example.com/", + "registries": { + "npm": "https://registry.example.com/" + } + }, + "nodes": { + "~https_c++registry.example.com+~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~https_c++registry.example.com+~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-default/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/expected-edits.json new file mode 100644 index 00000000..92b64b97 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~https_c++registry.example.com~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"~https_c++registry.example.com~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/expected/vlt-lock.json new file mode 100644 index 00000000..fbb2bd73 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/expected/vlt-lock.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 1, + "options": { + "registry": "https://registry.example.com/", + "registries": { + "npm": "https://registry.example.com/" + } + }, + "nodes": { + "~https_c++registry.example.com~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~https_c++registry.example.com~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/input/vlt-lock.json new file mode 100644 index 00000000..5e9a6c0a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/input/vlt-lock.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 1, + "options": { + "registry": "https://registry.example.com/", + "registries": { + "npm": "https://registry.example.com/" + } + }, + "nodes": { + "~https_c++registry.example.com~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="] + }, + "edges": { + "file~_d left-pad": "prod 1.3.0 ~https_c++registry.example.com~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/url-segment-trailing-slash/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/expected-confirmation.json new file mode 100644 index 00000000..473f0bc6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/expected-confirmation.json @@ -0,0 +1,5 @@ +{ + "confirmed": [], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/expected-edits.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/expected-edits.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/expected-warnings.json new file mode 100644 index 00000000..673df0bc --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_entry_vendored" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/input/vlt-lock.json new file mode 100644 index 00000000..bcc50e44 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/input/vlt-lock.json @@ -0,0 +1,14 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+left-pad-1.3.0.tgz": [0,"left-pad",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0.tgz"] + }, + "edges": { + "file~_d left-pad": "prod file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0.tgz file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+left-pad-1.3.0.tgz" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry-tgz/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/expected-confirmation.json new file mode 100644 index 00000000..473f0bc6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/expected-confirmation.json @@ -0,0 +1,5 @@ +{ + "confirmed": [], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/expected-edits.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/expected-edits.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/expected-warnings.json new file mode 100644 index 00000000..673df0bc --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_entry_vendored" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/input/vlt-lock.json new file mode 100644 index 00000000..3855b985 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/input/vlt-lock.json @@ -0,0 +1,14 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+left-pad-1.3.0+node__modules+left-pad": [0,"left-pad",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad"] + }, + "edges": { + "file~_d left-pad": "prod file:.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+left-pad-1.3.0+node__modules+left-pad" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vendored-entry/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/expected-edits.json new file mode 100644 index 00000000..e6479a1a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"··left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/expected/vlt-lock.json new file mode 100644 index 00000000..a3f179e4 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/expected/vlt-lock.json @@ -0,0 +1,10 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "··left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/input/vlt-lock.json new file mode 100644 index 00000000..2767564b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/input/vlt-lock.json @@ -0,0 +1,10 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "··left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/input/vlt.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/input/vlt.json new file mode 100644 index 00000000..54f57a94 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/input/vlt.json @@ -0,0 +1,3 @@ +{ + "modifiers": {} +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/vlt-json-bom/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/expected-edits.json new file mode 100644 index 00000000..5a1e33aa --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\",\"https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz\"]", + "new": "\"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/expected/vlt-lock.json new file mode 100644 index 00000000..3d3ceb2c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/expected/vlt-lock.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "workspace~packages+a left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "workspace~packages+b left-pad": "dev ^1.3.0 ~npm~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/input/vlt-lock.json new file mode 100644 index 00000000..e3f5f4c7 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/input/vlt-lock.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"] + }, + "edges": { + "workspace~packages+a left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "workspace~packages+b left-pad": "dev ^1.3.0 ~npm~left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/input/vlt.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/input/vlt.json new file mode 100644 index 00000000..0155c44a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/input/vlt.json @@ -0,0 +1,8 @@ +{ + "workspaces": "packages/*", + "config": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/workspace/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/vex-discover-golden/redirect-npm.json b/crates/socket-patch-core/tests/fixtures/vex-discover-golden/redirect-npm.json index 3303c6ad..06f89a6c 100644 --- a/crates/socket-patch-core/tests/fixtures/vex-discover-golden/redirect-npm.json +++ b/crates/socket-patch-core/tests/fixtures/vex-discover-golden/redirect-npm.json @@ -925,6 +925,1047 @@ "elsewhere": [], "live_claims": [] }, + "redirect/npm/vlt/alias-edge/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/alias-edge/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/alias-url-equals-registry/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/alias-url-equals-registry/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/basic/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/basic/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/bins-and-platform-slots/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/bins-and-platform-slots/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/bom-refusal/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-0.0.0-1/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-0.0.0-1/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-0.0.0-16/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-0.0.0-16/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-0.0.0-19/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-0.0.0-19/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-0.0.0-32/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-0.0.0-32/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-1.0.0-rc.14/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-1.0.0-rc.14/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-1.0.0-rc.15/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-1.0.0-rc.15/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-1.0.0-rc.32/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-1.0.0-rc.32/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-1.0.0-rc.33/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-1.0.0-rc.33/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-1.0.0-rc.8/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-1.0.0-rc.8/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-1.0.10/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-1.0.10/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-1.1.1/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-1.1.1/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-1.2.0/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/capture-1.2.0/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/crlf/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/crlf/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/custom-registry-skipped/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/default-registry-alias/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/default-registry-alias/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/hidden-lock-sentinel/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/invalid-json/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/jsr-skipped/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/jsr-skipped/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/legacy-mixed-default-segments/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/legacy-mixed-default-segments/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-absent-version/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-absent-version/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-absent-version-warning/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-absent-version-warning/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-v0-empty-segment/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-v0-empty-segment/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-v0-empty-segment-old-lockfile-warning/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-v0-npm-segment/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-v0-npm-segment/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-v0-url-segment-old-lockfile-warning/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-v1-3tuple/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-v1-3tuple/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-v1-both-registry-keys/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-v1-both-registry-keys/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-v1-scalar-registry-3tuple/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-v1-scalar-registry-3tuple/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-version-exponent/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-version-float/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-version-string/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-version-unsupported/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lone-surrogate-string/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/mirror-registries-npm/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/mirror-registries-npm/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/missing-sha512/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/mixed-eol/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/mixed-eol/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/modifier-extra/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/modifier-extra/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/multiple-versions/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/multiple-versions/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/no-lockfile-vlt-json/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/non-canonical-layout-refusal/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/not-json-object/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/peer-extras/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/peer-extras/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/re-redirect-stale-url/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/re-redirect-stale-url/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/remote-file-git-untouched/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/remote-file-git-untouched/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/rerun-noop/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/residual-gate-duplicate-nodes/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/scalar-registry-v0-with-registries-npm/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/scalar-registry-v0-with-registries-npm/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/scalar-registry-warning/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/scalar-registry-warning/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/scoped-package/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/scoped-package/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/scoped-registry-skipped/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/scoped-registry-skipped/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/sibling-package-lock/expected": { + "refs": [ + { + "purl": "pkg:npm/left-pad@1.3.0", + "uuid": "11111111-1111-4111-8111-111111111111", + "mode": "hosted", + "source_file": "package-lock.json", + "artifact_rel": null, + "locked_integrity": "Sri(\"sha512-11111111111111111111111111111111111111111111111111111111111111111111111111111111111111==\")", + "integrity_required": true, + "url": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/11111111-1111-4111-8111-111111111111/left-pad-1.3.0.tgz", + "lockfile_basis_ok": true + } + ], + "diagnostics": [], + "recognized": [ + { + "uuid": "11111111-1111-1111-1111-111111111111", + "mode": "hosted", + "file": "package-lock.json" + }, + { + "uuid": "11111111-1111-4111-8111-111111111111", + "mode": "hosted", + "file": "package-lock.json" + } + ], + "unlocked_pins": [], + "elsewhere": [ + { + "purl": "pkg:npm/ms@2.1.3", + "file": "package-lock.json" + } + ], + "live_claims": [ + { + "mode": "hosted", + "uuid": "11111111-1111-4111-8111-111111111111", + "purl": "pkg:npm/left-pad@1.3.0" + } + ] + }, + "redirect/npm/vlt/sibling-package-lock/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [ + { + "purl": "pkg:npm/left-pad@1.3.0", + "file": "package-lock.json" + }, + { + "purl": "pkg:npm/ms@2.1.3", + "file": "package-lock.json" + } + ], + "live_claims": [] + }, + "redirect/npm/vlt/sibling-package-lock-vlt-installed/expected": { + "refs": [ + { + "purl": "pkg:npm/left-pad@1.3.0", + "uuid": "11111111-1111-4111-8111-111111111111", + "mode": "hosted", + "source_file": "package-lock.json", + "artifact_rel": null, + "locked_integrity": "Sri(\"sha512-11111111111111111111111111111111111111111111111111111111111111111111111111111111111111==\")", + "integrity_required": true, + "url": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/11111111-1111-4111-8111-111111111111/left-pad-1.3.0.tgz", + "lockfile_basis_ok": true + }, + { + "purl": "pkg:npm/ms@2.1.3", + "uuid": "22222222-2222-4222-8222-222222222222", + "mode": "hosted", + "source_file": "package-lock.json", + "artifact_rel": null, + "locked_integrity": "Sri(\"sha512-22222222222222222222222222222222222222222222222222222222222222222222222222222222222222==\")", + "integrity_required": true, + "url": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/22222222-2222-4222-8222-222222222222/ms-2.1.3.tgz", + "lockfile_basis_ok": true + } + ], + "diagnostics": [], + "recognized": [ + { + "uuid": "11111111-1111-1111-1111-111111111111", + "mode": "hosted", + "file": "package-lock.json" + }, + { + "uuid": "11111111-1111-4111-8111-111111111111", + "mode": "hosted", + "file": "package-lock.json" + }, + { + "uuid": "22222222-2222-4222-8222-222222222222", + "mode": "hosted", + "file": "package-lock.json" + } + ], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [ + { + "mode": "hosted", + "uuid": "11111111-1111-4111-8111-111111111111", + "purl": "pkg:npm/left-pad@1.3.0" + }, + { + "mode": "hosted", + "uuid": "22222222-2222-4222-8222-222222222222", + "purl": "pkg:npm/ms@2.1.3" + } + ] + }, + "redirect/npm/vlt/sibling-package-lock-vlt-installed/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [ + { + "purl": "pkg:npm/left-pad@1.3.0", + "file": "package-lock.json" + }, + { + "purl": "pkg:npm/ms@2.1.3", + "file": "package-lock.json" + } + ], + "live_claims": [] + }, + "redirect/npm/vlt/sibling-refused-in-vlt/expected": { + "refs": [ + { + "purl": "pkg:npm/left-pad@1.3.0", + "uuid": "11111111-1111-4111-8111-111111111111", + "mode": "hosted", + "source_file": "package-lock.json", + "artifact_rel": null, + "locked_integrity": "Sri(\"sha512-11111111111111111111111111111111111111111111111111111111111111111111111111111111111111==\")", + "integrity_required": true, + "url": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/11111111-1111-4111-8111-111111111111/left-pad-1.3.0.tgz", + "lockfile_basis_ok": true + } + ], + "diagnostics": [], + "recognized": [ + { + "uuid": "11111111-1111-1111-1111-111111111111", + "mode": "hosted", + "file": "package-lock.json" + }, + { + "uuid": "11111111-1111-4111-8111-111111111111", + "mode": "hosted", + "file": "package-lock.json" + } + ], + "unlocked_pins": [], + "elsewhere": [ + { + "purl": "pkg:npm/ms@2.1.3", + "file": "package-lock.json" + } + ], + "live_claims": [ + { + "mode": "hosted", + "uuid": "11111111-1111-4111-8111-111111111111", + "purl": "pkg:npm/left-pad@1.3.0" + } + ] + }, + "redirect/npm/vlt/sibling-refused-in-vlt/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [ + { + "purl": "pkg:npm/left-pad@1.3.0", + "file": "package-lock.json" + }, + { + "purl": "pkg:npm/ms@2.1.3", + "file": "package-lock.json" + } + ], + "live_claims": [] + }, + "redirect/npm/vlt/unsupported-lock-key/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/url-segment-default/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/url-segment-default/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/url-segment-trailing-slash/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/url-segment-trailing-slash/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/vendored-entry/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/vendored-entry-tgz/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/vlt-json-bom/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/vlt-json-bom/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/workspace/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/workspace/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, "redirect/npm/yarn-berry/basic/expected": { "refs": [ { diff --git a/crates/socket-patch-core/tests/fixtures/vex-discover-golden/vlt-locks.json b/crates/socket-patch-core/tests/fixtures/vex-discover-golden/vlt-locks.json new file mode 100644 index 00000000..9d2f41df --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vex-discover-golden/vlt-locks.json @@ -0,0 +1,98 @@ +{ + "vlt-locks/0.0.0-1": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "vlt-locks/0.0.0-16": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "vlt-locks/0.0.0-19": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "vlt-locks/0.0.0-32": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "vlt-locks/1.0.0-rc.14": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "vlt-locks/1.0.0-rc.15": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "vlt-locks/1.0.0-rc.32": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "vlt-locks/1.0.0-rc.33": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "vlt-locks/1.0.0-rc.8": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "vlt-locks/1.0.10": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "vlt-locks/1.1.1": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "vlt-locks/1.2.0": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-1/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-1/vlt-lock.json new file mode 100644 index 00000000..830dbce7 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-1/vlt-lock.json @@ -0,0 +1,39 @@ +{ + "options": {}, + "nodes": { + "··@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "··debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "··fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "··js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "··left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "··loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "··lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "··ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "··react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "··react@18.3.1": [0,"react","sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "··semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg=="], + "··use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "··yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "·npm·left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz": [0,"left-pad"] + }, + "edges": { + "file·. ms": "dev 2.1.2 ··ms@2.1.2", + "file·. fsevents": "optional 2.3.3 ··fsevents@2.3.3", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ··@isaacs§string-locale-compare@1.1.0", + "file·. debug": "prod 4.3.4 ··debug@4.3.4", + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0", + "file·. react": "prod 18.2.0 ··react@18.2.0", + "file·. semver": "prod 7.6.0 ··semver@7.6.0", + "file·. use-sync-external-store": "prod 1.2.0 ··use-sync-external-store@1.2.0", + "file·. lp-alias": "prod npm:left-pad@1.1.3 ·npm·left-pad@1.1.3", + "file·. lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "··debug@4.3.4 ms": "prod 2.1.2 ··ms@2.1.2", + "··loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ··js-tokens@4.0.0", + "··lru-cache@6.0.0 yallist": "prod ^4.0.0 ··yallist@4.0.0", + "··react@18.2.0 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··react@18.3.1 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··semver@7.6.0 lru-cache": "prod ^6.0.0 ··lru-cache@6.0.0", + "··use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ··react@18.3.1" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-16/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-16/vlt-lock.json new file mode 100644 index 00000000..b5829580 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-16/vlt-lock.json @@ -0,0 +1,44 @@ +{ + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "··@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "··fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "··js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "··left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "··loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "··lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "··ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "··react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "··react@18.3.1": [0,"react","sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "··semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg=="], + "··use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "··yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "·npm·left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz": [0,"left-pad"] + }, + "edges": { + "file·. ms": "dev 2.1.2 ··ms@2.1.2", + "file·. fsevents": "optional 2.3.3 ··fsevents@2.3.3", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ··@isaacs§string-locale-compare@1.1.0", + "file·. debug": "prod 4.3.4 ··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0", + "file·. react": "prod 18.2.0 ··react@18.2.0", + "file·. semver": "prod 7.6.0 ··semver@7.6.0", + "file·. use-sync-external-store": "prod 1.2.0 ··use-sync-external-store@1.2.0", + "file·. lp-alias": "prod npm:left-pad@1.1.3 ·npm·left-pad@1.1.3", + "file·. lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms ms": "prod 2.1.3 ··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "··loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ··js-tokens@4.0.0", + "··lru-cache@6.0.0 yallist": "prod ^4.0.0 ··yallist@4.0.0", + "··react@18.2.0 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··react@18.3.1 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··semver@7.6.0 lru-cache": "prod ^6.0.0 ··lru-cache@6.0.0", + "··use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ··react@18.3.1" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-16/vlt.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-16/vlt.json new file mode 100644 index 00000000..5f7ee1f6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-16/vlt.json @@ -0,0 +1 @@ +{"modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-19/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-19/vlt-lock.json new file mode 100644 index 00000000..9725a9fb --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-19/vlt-lock.json @@ -0,0 +1,45 @@ +{ + "lockfileVersion": 0, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "··@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "··fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "··js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "··left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "··loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "··lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "··ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "··react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "··react@18.3.1": [0,"react","sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], + "··semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg=="], + "··use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "··yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "·npm·left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz": [0,"left-pad"] + }, + "edges": { + "file·. ms": "dev 2.1.2 ··ms@2.1.2", + "file·. fsevents": "optional 2.3.3 ··fsevents@2.3.3", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ··@isaacs§string-locale-compare@1.1.0", + "file·. debug": "prod 4.3.4 ··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0", + "file·. react": "prod 18.2.0 ··react@18.2.0", + "file·. semver": "prod 7.6.0 ··semver@7.6.0", + "file·. use-sync-external-store": "prod 1.2.0 ··use-sync-external-store@1.2.0", + "file·. lp-alias": "prod npm:left-pad@1.1.3 ·npm·left-pad@1.1.3", + "file·. lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms ms": "prod 2.1.3 ··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "··loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ··js-tokens@4.0.0", + "··lru-cache@6.0.0 yallist": "prod ^4.0.0 ··yallist@4.0.0", + "··react@18.2.0 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··react@18.3.1 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··semver@7.6.0 lru-cache": "prod ^6.0.0 ··lru-cache@6.0.0", + "··use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ··react@18.3.1" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-19/vlt.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-19/vlt.json new file mode 100644 index 00000000..5f7ee1f6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-19/vlt.json @@ -0,0 +1 @@ +{"modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-32/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-32/vlt-lock.json new file mode 100644 index 00000000..beae2b78 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-32/vlt-lock.json @@ -0,0 +1,43 @@ +{ + "lockfileVersion": 0, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "··@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "··fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",null,null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "··js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "··left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "··loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "··lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "··ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "··react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "··semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg=="], + "··use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "··yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "·npm·left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz": [0,"left-pad"] + }, + "edges": { + "file·. ms": "dev 2.1.2 ··ms@2.1.2", + "file·. fsevents": "optional 2.3.3 ··fsevents@2.3.3", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ··@isaacs§string-locale-compare@1.1.0", + "file·. debug": "prod 4.3.4 ··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0", + "file·. react": "prod 18.2.0 ··react@18.2.0", + "file·. semver": "prod 7.6.0 ··semver@7.6.0", + "file·. use-sync-external-store": "prod 1.2.0 ··use-sync-external-store@1.2.0", + "file·. lp-alias": "prod npm:left-pad@1.1.3 ·npm·left-pad@1.1.3", + "file·. lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms ms": "prod 2.1.3 ··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "··loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ··js-tokens@4.0.0", + "··lru-cache@6.0.0 yallist": "prod ^4.0.0 ··yallist@4.0.0", + "··react@18.2.0 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··semver@7.6.0 lru-cache": "prod ^6.0.0 ··lru-cache@6.0.0", + "··use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ··react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-32/vlt.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-32/vlt.json new file mode 100644 index 00000000..5f7ee1f6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/0.0.0-32/vlt.json @@ -0,0 +1 @@ +{"modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.14/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.14/vlt-lock.json new file mode 100644 index 00000000..9b570793 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.14/vlt-lock.json @@ -0,0 +1,43 @@ +{ + "lockfileVersion": 0, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "·npm·@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "·npm·debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "·npm·fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",null,null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "·npm·js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "·npm·left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "·npm·left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "·npm·loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "·npm·lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "·npm·ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "·npm·ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "·npm·react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "·npm·semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",null,null,null,null,null,{ "semver": "bin/semver.js"}], + "·npm·use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "·npm·yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file·. ms": "dev 2.1.2 ·npm·ms@2.1.2", + "file·. fsevents": "optional 2.3.3 ·npm·fsevents@2.3.3", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ·npm·@isaacs§string-locale-compare@1.1.0", + "file·. debug": "prod 4.3.4 ·npm·debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "file·. lp-alias": "prod npm:left-pad@1.1.3 ·npm·left-pad@1.1.3", + "file·. left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "file·. react": "prod 18.2.0 ·npm·react@18.2.0", + "file·. semver": "prod 7.6.0 ·npm·semver@7.6.0", + "file·. use-sync-external-store": "prod 1.2.0 ·npm·use-sync-external-store@1.2.0", + "file·. lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "·npm·debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms ms": "prod 2.1.3 ·npm·ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ·npm·js-tokens@4.0.0", + "·npm·lru-cache@6.0.0 yallist": "prod ^4.0.0 ·npm·yallist@4.0.0", + "·npm·react@18.2.0 loose-envify": "prod ^1.1.0 ·npm·loose-envify@1.4.0", + "·npm·semver@7.6.0 lru-cache": "prod ^6.0.0 ·npm·lru-cache@6.0.0", + "·npm·use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ·npm·react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.14/vlt.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.14/vlt.json new file mode 100644 index 00000000..5f7ee1f6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.14/vlt.json @@ -0,0 +1 @@ +{"modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.15/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.15/vlt-lock.json new file mode 100644 index 00000000..657f3548 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.15/vlt-lock.json @@ -0,0 +1,43 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",null,null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "~npm~ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",null,null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file~_d ms": "dev 2.1.2 ~npm~ms@2.1.2", + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "file~_d lp-alias": "prod npm:left-pad@1.1.3 ~npm~left-pad@1.1.3", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.15/vlt.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.15/vlt.json new file mode 100644 index 00000000..5f7ee1f6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.15/vlt.json @@ -0,0 +1 @@ +{"modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.32/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.32/vlt-lock.json new file mode 100644 index 00000000..657f3548 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.32/vlt-lock.json @@ -0,0 +1,43 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",null,null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "~npm~ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",null,null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file~_d ms": "dev 2.1.2 ~npm~ms@2.1.2", + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "file~_d lp-alias": "prod npm:left-pad@1.1.3 ~npm~left-pad@1.1.3", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.32/vlt.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.32/vlt.json new file mode 100644 index 00000000..5f7ee1f6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.32/vlt.json @@ -0,0 +1 @@ +{"modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.33/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.33/vlt-lock.json new file mode 100644 index 00000000..16b0da72 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.33/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + }, + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==","https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw==","https://registry.npmjs.org/left-pad/-/left-pad-1.1.3.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file~_d ms": "dev 2.1.2 ~npm~ms@2.1.2", + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "file~_d lp-alias": "prod npm:left-pad@1.1.3 ~npm~left-pad@1.1.3", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.33/vlt.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.33/vlt.json new file mode 100644 index 00000000..ab3aa87e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.33/vlt.json @@ -0,0 +1 @@ +{"config": {"registries": {"npm": "https://registry.npmjs.org/"}}, "modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.8/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.8/vlt-lock.json new file mode 100644 index 00000000..d3a36652 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.8/vlt-lock.json @@ -0,0 +1,43 @@ +{ + "lockfileVersion": 0, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + } + }, + "nodes": { + "··@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "··fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",null,null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "··js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "··left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "··loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "··lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "··ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "··react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "··semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",null,null,null,null,null,{ "semver": "bin/semver.js"}], + "··use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "··yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "·npm·left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw=="], + "remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz": [0,"left-pad"] + }, + "edges": { + "file·. ms": "dev 2.1.2 ··ms@2.1.2", + "file·. fsevents": "optional 2.3.3 ··fsevents@2.3.3", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ··@isaacs§string-locale-compare@1.1.0", + "file·. debug": "prod 4.3.4 ··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0", + "file·. react": "prod 18.2.0 ··react@18.2.0", + "file·. semver": "prod 7.6.0 ··semver@7.6.0", + "file·. use-sync-external-store": "prod 1.2.0 ··use-sync-external-store@1.2.0", + "file·. lp-alias": "prod npm:left-pad@1.1.3 ·npm·left-pad@1.1.3", + "file·. lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote·https%3A§§registry.npmjs.org§left-pad§-§left-pad-1.2.0.tgz", + "··debug@4.3.4·%3Aroot%20%3E%20%23debug%20%3E%20%23ms ms": "prod 2.1.3 ··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "··loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ··js-tokens@4.0.0", + "··lru-cache@6.0.0 yallist": "prod ^4.0.0 ··yallist@4.0.0", + "··react@18.2.0 loose-envify": "prod ^1.1.0 ··loose-envify@1.4.0", + "··semver@7.6.0 lru-cache": "prod ^6.0.0 ··lru-cache@6.0.0", + "··use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ··react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.8/vlt.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.8/vlt.json new file mode 100644 index 00000000..5f7ee1f6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.0-rc.8/vlt.json @@ -0,0 +1 @@ +{"modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.10/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.10/vlt-lock.json new file mode 100644 index 00000000..54da8c22 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.10/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + }, + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==","https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw==","https://registry.npmjs.org/left-pad/-/left-pad-1.1.3.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file~_d ms": "dev 2.1.2 ~npm~ms@2.1.2", + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "file~_d lp-alias": "prod npm:left-pad@1.1.3 ~npm~left-pad@1.1.3", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.10/vlt.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.10/vlt.json new file mode 100644 index 00000000..ab3aa87e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.0.10/vlt.json @@ -0,0 +1 @@ +{"config": {"registries": {"npm": "https://registry.npmjs.org/"}}, "modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/1.1.1/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.1.1/vlt-lock.json new file mode 100644 index 00000000..54da8c22 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.1.1/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + }, + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==","https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw==","https://registry.npmjs.org/left-pad/-/left-pad-1.1.3.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file~_d ms": "dev 2.1.2 ~npm~ms@2.1.2", + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "file~_d lp-alias": "prod npm:left-pad@1.1.3 ~npm~left-pad@1.1.3", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/1.1.1/vlt.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.1.1/vlt.json new file mode 100644 index 00000000..ab3aa87e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.1.1/vlt.json @@ -0,0 +1 @@ +{"config": {"registries": {"npm": "https://registry.npmjs.org/"}}, "modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/1.2.0/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.2.0/vlt-lock.json new file mode 100644 index 00000000..54da8c22 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.2.0/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "modifiers": { + ":root > #debug > #ms": "2.1.3" + }, + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~fsevents@2.3.3": [1,"fsevents","sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==","https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",null,null,null,{ "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" }, "os": [ "darwin" ]}], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.1.3": [0,"left-pad","sha512-m3z9QHpSXmd2H8Z5jnSXbGONPty4dFQfH1QpGgivzrEzICgsi50j9S+aGc77EaLoHpbw0BzP5+k1pp2UajTRuw==","https://registry.npmjs.org/left-pad/-/left-pad-1.1.3.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [2,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz": [0,"left-pad","sha512-OQadpCyFCT/VLniZQgym8d3/ofIJtuZyw2ibsVeIUOexKgW/osn8+mMFJbwGMPeDC4GnLzD8q115WPCDx4YRWg=="] + }, + "edges": { + "file~_d ms": "dev 2.1.2 ~npm~ms@2.1.2", + "file~_d fsevents": "optional 2.3.3 ~npm~fsevents@2.3.3", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d debug": "prod 4.3.4 ~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms", + "file~_d lp-alias": "prod npm:left-pad@1.1.3 ~npm~left-pad@1.1.3", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "file~_d lp-remote": "prod https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz remote~https_c++registry.npmjs.org+left-pad+-+left-pad-1.2.0.tgz", + "~npm~debug@4.3.4~_croot_s_g_s#debug_s_g_s#ms ms": "prod 2.1.3 ~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/1.2.0/vlt.json b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.2.0/vlt.json new file mode 100644 index 00000000..ab3aa87e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/1.2.0/vlt.json @@ -0,0 +1 @@ +{"config": {"registries": {"npm": "https://registry.npmjs.org/"}}, "modifiers": {":root > #debug > #ms": "2.1.3"}} diff --git a/crates/socket-patch-core/tests/fixtures/vlt-locks/README.md b/crates/socket-patch-core/tests/fixtures/vlt-locks/README.md new file mode 100644 index 00000000..ececddc1 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vlt-locks/README.md @@ -0,0 +1,28 @@ +# Captured vlt locks + +Each `/vlt-lock.json` is the lock real vlt wrote on a cold +`vlt install` of one project (isolated XDG dirs and VLT_CACHE, +VLT_TELEMETRY=0, LANG=C, LC_ALL=C, CI=1, Node 24.21.0, public npm registry), +byte for byte. `/vlt.json` is the config it ran with (0.0.0-1 +has none). + +Project (`package.json`): + +- dependencies: "left-pad": "1.3.0", "debug": "4.3.4", + "@isaacs/string-locale-compare": "1.1.0", "react": "18.2.0", + "use-sync-external-store": "1.2.0", "lp-alias": "npm:left-pad@1.1.3", + "semver": "7.6.0", + "lp-remote": "https://registry.npmjs.org/left-pad/-/left-pad-1.2.0.tgz" +- optionalDependencies: "fsevents": "2.3.3" (captured on macOS) +- devDependencies: "ms": "2.1.2" + +`vlt.json`: `{"modifiers": {":root > #debug > #ms": "2.1.3"}}`, plus +`{"config": {"registries": {"npm": "https://registry.npmjs.org/"}}}` from +1.0.0-rc.33 (which has no default registry). + +The locks cover every era: no `lockfileVersion` (0.0.0-1, 0.0.0-16), `0` +with `··` and `·npm·` ids (0.0.0-19 to rc.14), `1` with `~` ids and +3-tuples (rc.15, rc.32), `1` with slot [3] (rc.33 on), and peer extras +(1.0.10 on), with dev/optional flags, platform [7], bins [8], a modifier, +an alias and a remote node. `tests/vlt_locks.rs` round-trips them, and the +`redirect/npm/vlt/capture-` goldens are built from them. diff --git a/crates/socket-patch-core/tests/redirect_golden.rs b/crates/socket-patch-core/tests/redirect_golden.rs index 0fc5a6ec..7a6266f1 100644 --- a/crates/socket-patch-core/tests/redirect_golden.rs +++ b/crates/socket-patch-core/tests/redirect_golden.rs @@ -25,6 +25,7 @@ const RUST_IMPLEMENTED: &[&str] = &[ "npm/yarn-classic", "npm/yarn-berry", "npm/bun", + "npm/vlt", "pypi/requirements", "pypi/uv", "cargo/cargo", @@ -162,6 +163,29 @@ fn redirect_golden_fixtures_match() { assert_eq!(got, expected, "{rel}: warning codes mismatch"); } + // Confirmation sets (`expected-confirmation.json`: `confirmed` and + // `refused` uuid lists plus `vltDrives`), the hosted confirmation + // inputs the TS twin must reproduce. Required for every vlt case, + // optional elsewhere. + let confirmation_path = case.join("expected-confirmation.json"); + if eco_flavor == "npm/vlt" { + assert!( + confirmation_path.is_file(), + "{rel}: vlt cases must ship expected-confirmation.json" + ); + } + if confirmation_path.is_file() { + let expected: serde_json::Value = + serde_json::from_str(&fs::read_to_string(&confirmation_path).unwrap()) + .unwrap_or_else(|e| panic!("{rel}: bad expected-confirmation.json: {e}")); + let got = serde_json::json!({ + "confirmed": result.confirmed_vlt_uuids, + "refused": result.refused_vlt_uuids, + "vltDrives": result.vlt_drives, + }); + assert_eq!(got, expected, "{rel}: confirmation mismatch"); + } + // Determinism: a second run yields identical bytes. let again = rewrite_registry_redirect(&files, &overrides); assert_eq!(again.files, result.files, "{rel}: non-deterministic"); diff --git a/crates/socket-patch-core/tests/redirect_golden_reverse_replay.rs b/crates/socket-patch-core/tests/redirect_golden_reverse_replay.rs new file mode 100644 index 00000000..20e15156 --- /dev/null +++ b/crates/socket-patch-core/tests/redirect_golden_reverse_replay.rs @@ -0,0 +1,220 @@ +//! Reverse replay of the vlt redirect goldens: each `npm/vlt` case's +//! `expected-edits.json`, recorded as a hosted ledger over its `expected/` +//! tree, must unwind `vlt-lock.json` to the `input/` bytes. The same edits are +//! what the depscan server writes into its PR ledgers, so this is also the +//! proof that socket-patch reverts a server-written vlt redirect. +//! +//! Each case runs three ways: as written, after vlt's LF re-save of a CRLF +//! lock, and after a re-save that appended a sibling node (which moves the +//! trailing comma). Both the whole-ledger replay and the per-purl revert are +//! exercised. + +use std::collections::BTreeMap; +use std::fs; +use std::path::{Path, PathBuf}; + +use socket_patch_core::manifest::schema::PatchRecord; +use socket_patch_core::patch::redirect::{ + revert_redirect_purl, revert_remaining_redirect_edits, FileEdit, RedirectState, +}; + +const VLT_LOCK: &str = "vlt-lock.json"; +const KIND: &str = "redirect_vlt_lock_node"; + +fn cases_root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/redirect/npm/vlt") +} + +struct Case { + name: String, + input: String, + expected: String, + edits: Vec, + purls: Vec, +} + +fn npm_purl(name: &str, namespace: Option<&str>, version: &str) -> String { + match namespace { + Some(ns) if !ns.is_empty() => format!("pkg:npm/%40{}/{name}@{version}", &ns[1..]), + _ => format!("pkg:npm/{name}@{version}"), + } +} + +fn load_cases() -> Vec { + let mut dirs: Vec = fs::read_dir(cases_root()) + .unwrap() + .map(|e| e.unwrap().path()) + .filter(|p| p.join("input").is_dir()) + .collect(); + dirs.sort(); + let mut cases = Vec::new(); + for dir in dirs { + let edits: Vec = + serde_json::from_str(&fs::read_to_string(dir.join("expected-edits.json")).unwrap()) + .unwrap(); + let edits: Vec = edits.into_iter().filter(|e| e.kind == KIND).collect(); + if edits.is_empty() { + continue; + } + let overrides: Vec = + serde_json::from_str(&fs::read_to_string(dir.join("overrides.json")).unwrap()).unwrap(); + let purls = overrides + .iter() + .map(|o| { + npm_purl( + o["name"].as_str().unwrap(), + o["namespace"].as_str(), + o["version"].as_str().unwrap(), + ) + }) + .collect(); + cases.push(Case { + name: dir.file_name().unwrap().to_string_lossy().into_owned(), + input: fs::read_to_string(dir.join("input").join(VLT_LOCK)).unwrap(), + expected: fs::read_to_string(dir.join("expected").join(VLT_LOCK)).unwrap(), + edits, + purls, + }); + } + cases +} + +fn record(uuid: &str) -> PatchRecord { + PatchRecord { + uuid: uuid.to_string(), + exported_at: "2026-09-25T00:00:00Z".to_string(), + files: Default::default(), + vulnerabilities: Default::default(), + description: String::new(), + license: String::new(), + tier: "free".to_string(), + } +} + +fn ledger(case: &Case) -> RedirectState { + let mut state = RedirectState::new(); + state.edits = case.edits.clone(); + for purl in &case.purls { + state.records.insert(purl.clone(), record("u")); + } + state +} + +/// The lock after vlt re-saved it with a node appended at the end of the +/// nodes section, so the formerly last entry gains a comma. +fn with_appended_node(text: &str) -> String { + let mut lines: Vec = text.split('\n').map(str::to_string).collect(); + let open = lines + .iter() + .position(|l| l.trim_end_matches('\r') == " \"nodes\": {") + .expect("nodes section"); + let close = (open + 1..lines.len()) + .find(|&i| matches!(lines[i].trim_end_matches('\r'), " }" | " },")) + .expect("nodes section end"); + let last = close - 1; + let cr = if lines[last].ends_with('\r') { + "\r" + } else { + "" + }; + let body = lines[last].trim_end_matches('\r').to_string(); + assert!(!body.ends_with(','), "the last node has no comma"); + lines[last] = format!("{body},{cr}"); + lines.insert( + close, + format!(" \"~npm~zzz-appended@1.0.0\": [0,\"zzz-appended\",\"sha512-zz==\"]{cr}"), + ); + lines.join("\n") +} + +fn variants(case: &Case) -> Vec<(&'static str, String, String)> { + let mut out = vec![("as-written", case.expected.clone(), case.input.clone())]; + if case.expected.contains('\r') { + out.push(( + "lf-resave", + case.expected.replace("\r\n", "\n"), + case.input.replace("\r\n", "\n"), + )); + } + out.push(( + "comma-moved", + with_appended_node(&case.expected), + with_appended_node(&case.input), + )); + out +} + +fn stage(lock: &str) -> tempfile::TempDir { + let dir = tempfile::tempdir().unwrap(); + fs::write(dir.path().join(VLT_LOCK), lock).unwrap(); + dir +} + +#[tokio::test] +async fn vlt_goldens_replay_back_to_their_input() { + let cases = load_cases(); + assert!( + cases.len() >= 30, + "only {} vlt cases with edits", + cases.len() + ); + for case in &cases { + for (variant, on_disk, want) in variants(case) { + let dir = stage(&on_disk); + let mut state = ledger(case); + let out = revert_remaining_redirect_edits(dir.path(), &mut state, false).await; + assert!( + out.fully_reverted(), + "{} {variant}: {:?}", + case.name, + out.refusals + ); + assert!(state.edits.is_empty(), "{} {variant}", case.name); + assert!(state.records.is_empty(), "{} {variant}", case.name); + assert_eq!( + fs::read_to_string(dir.path().join(VLT_LOCK)).unwrap(), + want, + "{} {variant}: replay", + case.name + ); + + let again = revert_remaining_redirect_edits(dir.path(), &mut ledger(case), false).await; + assert!(again.fully_reverted(), "{} {variant}: rerun", case.name); + assert_eq!( + fs::read_to_string(dir.path().join(VLT_LOCK)).unwrap(), + want, + "{} {variant}: an already reverted lock stays put", + case.name + ); + } + } +} + +#[tokio::test] +async fn vlt_goldens_revert_per_purl_back_to_their_input() { + for case in &load_cases() { + for (variant, on_disk, want) in variants(case) { + let dir = stage(&on_disk); + let mut state = ledger(case); + let mut touched: BTreeMap = BTreeMap::new(); + for purl in &case.purls { + let out = revert_redirect_purl(dir.path(), &mut state, purl, false) + .await + .unwrap_or_else(|e| panic!("{} {variant} {purl}: {e}", case.name)); + *touched.entry(purl.clone()).or_default() += out.reverted_files.len(); + } + assert!( + state.edits.is_empty(), + "{} {variant}: every vlt edit is claimed by its purl: {:?}", + case.name, + state.edits + ); + assert_eq!( + fs::read_to_string(dir.path().join(VLT_LOCK)).unwrap(), + want, + "{} {variant}: per-purl revert ({touched:?})", + case.name + ); + } + } +} diff --git a/crates/socket-patch-core/tests/vlt_locks.rs b/crates/socket-patch-core/tests/vlt_locks.rs new file mode 100644 index 00000000..a1ce21be --- /dev/null +++ b/crates/socket-patch-core/tests/vlt_locks.rs @@ -0,0 +1,344 @@ +//! Real vlt locks (`tests/fixtures/vlt-locks//`, captured from every +//! era) through the hosted rewriter: only the target nodes' slots [2] and [3] +//! change, and the output stays in vlt's own canonical serialization, so +//! vlt's next save leaves it byte-identical. + +use std::collections::BTreeMap; +use std::fs; +use std::path::{Path, PathBuf}; + +use serde_json::Value; +use socket_patch_core::patch::redirect::{rewrite_registry_redirect, DepOverride}; + +const TOKEN: &str = "11111111-1111-1111-1111-111111111111"; + +/// `(version, the DepIDs rewritten in override order, warning codes)`. +const CAPTURES: &[(&str, &[&str], &[&str])] = &[ + ( + "0.0.0-1", + &[ + "··left-pad@1.3.0", + "··@isaacs§string-locale-compare@1.1.0", + "··use-sync-external-store@1.2.0", + "··semver@7.6.0", + "··fsevents@2.3.3", + ], + &[ + "redirect_vlt_lockfile_version_missing", + "redirect_vlt_old_lockfile_ignored", + "redirect_vlt_entry_not_found", + ], + ), + ( + "0.0.0-16", + &[ + "··left-pad@1.3.0", + "··@isaacs§string-locale-compare@1.1.0", + "··use-sync-external-store@1.2.0", + "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "··semver@7.6.0", + "··fsevents@2.3.3", + ], + &["redirect_vlt_lockfile_version_missing"], + ), + ( + "0.0.0-19", + &[ + "··left-pad@1.3.0", + "··@isaacs§string-locale-compare@1.1.0", + "··use-sync-external-store@1.2.0", + "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "··semver@7.6.0", + "··fsevents@2.3.3", + ], + &[], + ), + ( + "0.0.0-32", + &[ + "··left-pad@1.3.0", + "··@isaacs§string-locale-compare@1.1.0", + "··use-sync-external-store@1.2.0", + "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "··semver@7.6.0", + "··fsevents@2.3.3", + ], + &[], + ), + ( + "1.0.0-rc.8", + &[ + "··left-pad@1.3.0", + "··@isaacs§string-locale-compare@1.1.0", + "··use-sync-external-store@1.2.0", + "··ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "··semver@7.6.0", + "··fsevents@2.3.3", + ], + &[], + ), + ( + "1.0.0-rc.14", + &[ + "·npm·left-pad@1.3.0", + "·npm·@isaacs§string-locale-compare@1.1.0", + "·npm·use-sync-external-store@1.2.0", + "·npm·ms@2.1.3·%3Aroot%20%3E%20%23debug%20%3E%20%23ms", + "·npm·semver@7.6.0", + "·npm·fsevents@2.3.3", + ], + &[], + ), + ( + "1.0.0-rc.15", + &[ + "~npm~left-pad@1.3.0", + "~npm~@isaacs+string-locale-compare@1.1.0", + "~npm~use-sync-external-store@1.2.0", + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~semver@7.6.0", + "~npm~fsevents@2.3.3", + ], + &[], + ), + ( + "1.0.0-rc.32", + &[ + "~npm~left-pad@1.3.0", + "~npm~@isaacs+string-locale-compare@1.1.0", + "~npm~use-sync-external-store@1.2.0", + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~semver@7.6.0", + "~npm~fsevents@2.3.3", + ], + &[], + ), + ( + "1.0.0-rc.33", + &[ + "~npm~left-pad@1.3.0", + "~npm~@isaacs+string-locale-compare@1.1.0", + "~npm~use-sync-external-store@1.2.0", + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~semver@7.6.0", + "~npm~fsevents@2.3.3", + ], + &[], + ), + ( + "1.0.10", + &[ + "~npm~left-pad@1.3.0", + "~npm~@isaacs+string-locale-compare@1.1.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~semver@7.6.0", + "~npm~fsevents@2.3.3", + ], + &[], + ), + ( + "1.1.1", + &[ + "~npm~left-pad@1.3.0", + "~npm~@isaacs+string-locale-compare@1.1.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~semver@7.6.0", + "~npm~fsevents@2.3.3", + ], + &[], + ), + ( + "1.2.0", + &[ + "~npm~left-pad@1.3.0", + "~npm~@isaacs+string-locale-compare@1.1.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "~npm~ms@2.1.3~_croot_s_g_s#debug_s_g_s#ms", + "~npm~semver@7.6.0", + "~npm~fsevents@2.3.3", + ], + &[], + ), +]; + +/// `(full name, version, patch uuid)` of the override set: a direct, a +/// scoped, a peer, a transitive (modifier) target, a bins and an optional +/// platform node. +const TARGETS: &[(&str, &str, &str)] = &[ + ("left-pad", "1.3.0", "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"), + ( + "@isaacs/string-locale-compare", + "1.1.0", + "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + ), + ( + "use-sync-external-store", + "1.2.0", + "cccccccc-cccc-4ccc-8ccc-cccccccccccc", + ), + ("ms", "2.1.3", "dddddddd-dddd-4ddd-8ddd-dddddddddddd"), + ("semver", "7.6.0", "ffffffff-ffff-4fff-8fff-ffffffffffff"), + ("fsevents", "2.3.3", "00000000-0000-4000-8000-000000000000"), +]; + +fn captures_root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/vlt-locks") +} + +fn hosted_url(name: &str, version: &str, uuid: &str) -> String { + let bare = name.rsplit('/').next().unwrap(); + format!("https://patch.socket.dev/patch/npm/{TOKEN}/{uuid}/{bare}-{version}.tgz") +} + +fn patched_sha(uuid: &str) -> String { + let tag = uuid[..1].to_ascii_uppercase(); + format!("sha512-{}==", tag.repeat(86)) +} + +fn overrides() -> Vec { + TARGETS + .iter() + .map(|(name, version, uuid)| { + let (namespace, bare) = match name.split_once('/') { + Some((scope, bare)) => (Some(scope), bare), + None => (None, *name), + }; + serde_json::from_value(serde_json::json!({ + "ecosystem": "npm", + "name": bare, + "namespace": namespace, + "version": version, + "token": TOKEN, + "patchUuid": uuid, + "artifactUrl": hosted_url(name, version, uuid), + "integrity": { "sha512": patched_sha(uuid) }, + })) + .unwrap() + }) + .collect() +} + +/// vlt `save.ts` `extraFormat(JSON.stringify(data, null, 2))`. +fn vlt_serialize(text: &str) -> String { + let value: Value = serde_json::from_str(text).unwrap(); + let pretty = format!("{}\n", serde_json::to_string_pretty(&value).unwrap()); + let marker = " \"nodes\": {"; + let mut parts = pretty.split(marker); + let mut out = parts.next().unwrap().to_string(); + for part in parts { + out.push_str(marker); + out.push_str(&part.replace("\n ", "").replace("\n ]", "]")); + } + out +} + +fn node_entry(line: &str) -> (String, Vec) { + let body = line.trim_start().trim_end_matches(','); + let (key, tuple) = body.split_once(": ").unwrap(); + let key: String = serde_json::from_str(key).unwrap(); + let tuple: Vec = serde_json::from_str(tuple).unwrap(); + (key, tuple) +} + +fn read_capture(version: &str) -> BTreeMap { + let dir = captures_root().join(version); + let mut files = BTreeMap::new(); + for name in ["vlt-lock.json", "vlt.json"] { + if let Ok(text) = fs::read_to_string(dir.join(name)) { + files.insert(name.to_string(), text); + } + } + files +} + +#[test] +fn every_capture_is_listed() { + let mut on_disk: Vec = fs::read_dir(captures_root()) + .unwrap() + .map(|e| e.unwrap().path()) + .filter(|p| p.is_dir()) + .map(|p| p.file_name().unwrap().to_string_lossy().into_owned()) + .collect(); + on_disk.sort(); + let mut listed: Vec = CAPTURES.iter().map(|(v, _, _)| v.to_string()).collect(); + listed.sort(); + assert_eq!(on_disk, listed); +} + +#[test] +fn captured_locks_are_in_vlt_canonical_form() { + for (version, _, _) in CAPTURES { + let lock = &read_capture(version)["vlt-lock.json"]; + assert_eq!(&vlt_serialize(lock), lock, "{version}"); + } +} + +#[test] +fn hosted_rewrite_changes_exactly_the_target_slots() { + let overrides = overrides(); + for (version, targets, warnings) in CAPTURES { + let files = read_capture(version); + let input = &files["vlt-lock.json"]; + let result = rewrite_registry_redirect(&files, &overrides); + let codes: Vec<&str> = result.warnings.iter().map(|w| w.code.as_str()).collect(); + assert_eq!(&codes, warnings, "{version}"); + let output = &result.files["vlt-lock.json"]; + assert_eq!(result.files.len(), 1, "{version}"); + assert_eq!(&vlt_serialize(output), output, "{version}: canonical"); + + let before: Vec<&str> = input.split('\n').collect(); + let after: Vec<&str> = output.split('\n').collect(); + assert_eq!(before.len(), after.len(), "{version}"); + let mut changed = Vec::new(); + for (old, new) in before.iter().zip(&after) { + if old == new { + continue; + } + let (old_key, old_tuple) = node_entry(old); + let (new_key, new_tuple) = node_entry(new); + assert_eq!(old_key, new_key, "{version}"); + let name = old_tuple[1].as_str().unwrap(); + let (_, target_version, uuid) = TARGETS + .iter() + .find(|(n, v, _)| *n == name && old_key.contains(&format!("@{v}"))) + .unwrap_or_else(|| panic!("{version}: {old_key} is not a target")); + assert_eq!( + new_tuple.len(), + old_tuple.len().max(4), + "{version} {old_key}" + ); + assert_eq!(new_tuple[0], old_tuple[0], "{version} {old_key}"); + assert_eq!(new_tuple[1], old_tuple[1], "{version} {old_key}"); + assert_eq!(new_tuple[2], Value::String(patched_sha(uuid))); + assert_eq!( + new_tuple[3], + Value::String(hosted_url(name, target_version, uuid)) + ); + assert_eq!( + new_tuple[4..], + old_tuple[old_tuple.len().min(4)..], + "{version} {old_key}" + ); + changed.push(old_key); + } + let mut want: Vec<&str> = targets.to_vec(); + want.sort_unstable(); + changed.sort_unstable(); + assert_eq!(changed, want, "{version}"); + let edited: Vec<&str> = result + .edits + .iter() + .map(|e| { + let original = e.original.as_ref().and_then(Value::as_str).unwrap(); + original[1..].split_once('"').unwrap().0 + }) + .collect(); + assert_eq!(&edited, targets, "{version}: edits in override order"); + + let again = rewrite_registry_redirect(&result.files, &overrides); + assert!(again.files.is_empty(), "{version}: a rerun is a no-op"); + assert!(again.edits.is_empty(), "{version}"); + } +} From 0f00b0263357f8c6c245ddfb1edcc49104ea49a5 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Fri, 25 Sep 2026 22:36:14 -0400 Subject: [PATCH 12/46] Fix vlt hosted revert order and lock advisories Rolling back a vlt hosted redirect no longer refuses when vlt re-locked one peer or modifier variant away while an earlier instance of the same package still carries the pin. Every surviving instance is restored first, and only then is a vanished one checked for a leftover hosted URL, so rollback, remove and the vendored takeover succeed whatever order the ledger holds. The "vlt ignores this old lockfile" advisory now fires only for the empty or registry-URL segments the spec names, so v0 locks that use a named registry alias no longer get a spurious warning (and a withheld VEX attestation). A bun.lockb on disk now counts as a sibling lock when deciding whether vlt drives the install, without passing its bytes as text. New tests pin the slot-level revert through both revert entry points, the install-state sentinel on its own, both advisory alias cases and a CRLF copy of every captured vlt lock. Assisted-by: Claude Code:claude-opus-5-5 --- .../src/commands/scan/hosted.rs | 1 + .../src/patch/redirect/mod.rs | 13 +- .../src/patch/redirect/replay.rs | 89 +++++++- .../src/patch/redirect/takeover.rs | 79 ++++++- .../src/patch/redirect/vlt.rs | 207 +++++++++++++----- .../src/vendor/vlt_lock_text.rs | 17 +- .../src/vex/discover/pypi_other.rs | 1 + .../expected-confirmation.json | 5 + .../expected-edits.json | 1 + .../expected-warnings.json | 3 + .../input/node_modules/.vlt-lock.json | 0 .../hidden-lock-sentinel-only/overrides.json | 13 ++ .../expected-confirmation.json | 7 + .../expected-edits.json | 10 + .../expected-warnings.json | 3 + .../expected/vlt-lock.json | 15 ++ .../input/vlt-lock.json | 15 ++ .../overrides.json | 13 ++ .../expected-confirmation.json | 7 + .../expected-edits.json | 10 + .../expected-warnings.json | 1 + .../expected/vlt-lock.json | 15 ++ .../input/vlt-lock.json | 15 ++ .../overrides.json | 13 ++ .../vex-discover-golden/redirect-npm.json | 40 ++++ .../tests/redirect_golden_reverse_replay.rs | 54 ++++- crates/socket-patch-core/tests/vlt_locks.rs | 21 +- 27 files changed, 586 insertions(+), 82 deletions(-) create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel-only/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel-only/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel-only/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel-only/input/node_modules/.vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel-only/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/overrides.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/expected-confirmation.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/expected-edits.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/expected-warnings.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/input/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/overrides.json diff --git a/crates/socket-patch-cli/src/commands/scan/hosted.rs b/crates/socket-patch-cli/src/commands/scan/hosted.rs index 2c218c53..03223412 100644 --- a/crates/socket-patch-cli/src/commands/scan/hosted.rs +++ b/crates/socket-patch-cli/src/commands/scan/hosted.rs @@ -1881,6 +1881,7 @@ pub(crate) async fn run_redirect_selected( &rewrite_overrides, &python_metadata, pipenv_major, + common.cwd.join("bun.lockb").exists(), ); if let Some(content) = binary_content { rewrite diff --git a/crates/socket-patch-core/src/patch/redirect/mod.rs b/crates/socket-patch-core/src/patch/redirect/mod.rs index fd0c6821..ed40feca 100644 --- a/crates/socket-patch-core/src/patch/redirect/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/mod.rs @@ -228,7 +228,8 @@ pub struct RewriteResult { /// the node grammar, a failed residual gate). Never confirmed, whichever /// lock drives. pub refused_vlt_uuids: std::collections::BTreeSet, - /// [`vlt::vlt_drives`] over the rewriter's input files. + /// [`vlt::vlt_drives`] over the rewriter's input files and the + /// caller's `bun_lockb_present`. pub vlt_drives: bool, } @@ -274,7 +275,7 @@ pub fn rewrite_registry_redirect_with_python_metadata( overrides: &[DepOverride], python_metadata: &BTreeMap, ) -> RewriteResult { - rewrite_registry_redirect_with_pipenv_version(files, overrides, python_metadata, None) + rewrite_registry_redirect_with_pipenv_version(files, overrides, python_metadata, None, false) } /// Whether any pypi override targets an entry of `files["Pipfile.lock"]` — @@ -325,11 +326,15 @@ fn withhold<'a>( } } +/// `bun_lockb_present` reports a `bun.lockb` in the project that `files` +/// leaves out because the caller rewrites its bytes itself; vlt counts it +/// as a sibling lock. pub fn rewrite_registry_redirect_with_pipenv_version( files: &BTreeMap, overrides: &[DepOverride], python_metadata: &BTreeMap, pipenv_major: Option, + bun_lockb_present: bool, ) -> RewriteResult { let mut result = RewriteResult::default(); // pdm runs FIRST, but only when `pdm.lock` is the project's PyPI install @@ -351,8 +356,8 @@ pub fn rewrite_registry_redirect_with_pipenv_version( rewrite_yarn_classic(files, overrides, &mut result); rewrite_yarn_berry(files, overrides, &mut result); rewrite_bun_lock(files, overrides, &mut result); - vlt::rewrite_vlt_lock(files, overrides, &mut result); - result.vlt_drives = vlt::vlt_drives(files); + vlt::rewrite_vlt_lock(files, overrides, bun_lockb_present, &mut result); + result.vlt_drives = vlt::vlt_drives(files, bun_lockb_present); requirements::rewrite(files, overrides, &mut result); rewrite_hatch(files, overrides, &mut result); rewrite_uv_lock(files, overrides, python_metadata, &mut result); diff --git a/crates/socket-patch-core/src/patch/redirect/replay.rs b/crates/socket-patch-core/src/patch/redirect/replay.rs index f47e81f6..cc642562 100644 --- a/crates/socket-patch-core/src/patch/redirect/replay.rs +++ b/crates/socket-patch-core/src/patch/redirect/replay.rs @@ -468,6 +468,7 @@ pub async fn revert_remaining_redirect_edits( let mut staged_bytes: StagedBytes = BTreeMap::new(); let mut group_drops: BTreeSet = BTreeSet::new(); let mut group_warnings: Vec<(String, String)> = Vec::new(); + let mut vanished_vlt: Vec = Vec::new(); let files: BTreeSet = indices .iter() .map(|&i| state.edits[i].path.clone()) @@ -547,11 +548,15 @@ pub async fn revert_remaining_redirect_edits( } }; match super::vlt::revert_vlt_slots(&content, edit) { - Ok(Some(restored)) => { + Ok(super::vlt::SlotRevert::Restored(restored)) => { staged.insert(edit.path.clone(), Some(restored)); group_drops.insert(idx); } - Ok(None) => { + Ok(super::vlt::SlotRevert::Unchanged) => { + group_drops.insert(idx); + } + Ok(super::vlt::SlotRevert::Vanished) => { + vanished_vlt.push(idx); group_drops.insert(idx); } Err(reason) => { @@ -1086,6 +1091,20 @@ pub async fn revert_remaining_redirect_edits( } } + for &idx in &vanished_vlt { + let edit = &state.edits[idx]; + let checked = match staged_read(&staged, project_root, &edit.path).await { + Ok(Some(content)) => super::vlt::check_vanished(&content, edit), + Ok(None) => Err(format!("{} no longer exists", edit.path)), + Err(error) => Err(error), + }; + if let Err(reason) = checked { + refuse(reason, &mut outcome); + refused_groups.insert(group); + continue 'group; + } + } + // Commit the group: flush staged files (unless dry-run) through the // shared guarded atomic writer, then mark its edits for dropping. A // flush error refuses the group late — some files may already have @@ -2188,6 +2207,72 @@ mod tests { assert!(state.records.contains_key("pkg:npm/minimist@1.2.8")); } + #[tokio::test] + async fn vlt_replay_keeps_a_relaid_flag_and_trailing_slots() { + let dir = TempDir::new().unwrap(); + let relaid = VLT_HOSTED_ENTRY.replacen("[2,", "[0,", 1).replacen( + "\"]", + "\",null,null,null,null,{ \"m\": \"bin.js\"}]", + 1, + ); + write(dir.path(), "vlt-lock.json", &vlt_lock(&relaid)).await; + let mut state = state_with(vec![vlt_edit()], &["pkg:npm/minimist@1.2.8"]); + let out = revert_remaining_redirect_edits(dir.path(), &mut state, false).await; + assert!(out.fully_reverted(), "{out:?}"); + assert_eq!( + read(dir.path(), "vlt-lock.json").await, + vlt_lock( + "\"~npm~minimist@1.2.8~peer.1\": [0,\"minimist\",\"sha512-r\",null,null,null,null,null,{ \"m\": \"bin.js\"}]" + ) + ); + assert!(state.edits.is_empty() && state.records.is_empty()); + } + + #[tokio::test] + async fn a_relocked_away_vlt_variant_reverts_whatever_the_ledger_order() { + let plain_registry = VLT_REGISTRY_ENTRY.replace("~peer.1", ""); + let plain_hosted = VLT_HOSTED_ENTRY.replace("~peer.1", ""); + let plain_edit = FileEdit { + key: Some("minimist@1.2.8".into()), + ..edit( + "vlt-lock.json", + super::super::vlt::KIND, + "rewritten", + Some(&plain_registry), + Some(&plain_hosted), + ) + }; + for edits in [ + vec![plain_edit.clone(), vlt_edit()], + vec![vlt_edit(), plain_edit.clone()], + ] { + let dir = TempDir::new().unwrap(); + write(dir.path(), "vlt-lock.json", &vlt_lock(&plain_hosted)).await; + let mut state = state_with(edits, &["pkg:npm/minimist@1.2.8"]); + let out = revert_remaining_redirect_edits(dir.path(), &mut state, false).await; + assert!(out.fully_reverted(), "{out:?}"); + assert_eq!( + read(dir.path(), "vlt-lock.json").await, + vlt_lock(&plain_registry) + ); + assert!(state.edits.is_empty() && state.records.is_empty()); + } + + let dir = TempDir::new().unwrap(); + write(dir.path(), "vlt-lock.json", &vlt_lock(&plain_hosted)).await; + let mut state = state_with(vec![vlt_edit()], &["pkg:npm/minimist@1.2.8"]); + let out = revert_remaining_redirect_edits(dir.path(), &mut state, false).await; + assert_eq!(out.refusals.len(), 1, "{out:?}"); + assert!( + out.refusals[0].reason.contains("still pins its hosted URL"), + "{out:?}" + ); + assert_eq!( + read(dir.path(), "vlt-lock.json").await, + vlt_lock(&plain_hosted) + ); + } + #[tokio::test] async fn a_vlt_edit_whose_lock_is_gone_refuses() { let dir = TempDir::new().unwrap(); diff --git a/crates/socket-patch-core/src/patch/redirect/takeover.rs b/crates/socket-patch-core/src/patch/redirect/takeover.rs index 54163894..20ea162d 100644 --- a/crates/socket-patch-core/src/patch/redirect/takeover.rs +++ b/crates/socket-patch-core/src/patch/redirect/takeover.rs @@ -921,6 +921,7 @@ pub async fn revert_npm_redirect_purl( let mut out = RedirectRevert::default(); let mut staged: Staged = Staged::new(); let mut staged_bytes: StagedBytes = StagedBytes::new(); + let mut vanished_vlt: Vec = Vec::new(); // Newest-first: the hosted flow appends edits, so reverse index order // unwinds re-redirect chains correctly (each step's `original` is the // previous step's `new`). @@ -975,9 +976,13 @@ pub async fn revert_npm_redirect_purl( )); }; if edit.kind == super::vlt::KIND { - if let Some(restored) = super::vlt::revert_vlt_slots(&content, edit)? { - staged.insert(edit.path.clone(), Some(restored)); - out.reverted_files.push(edit.path.clone()); + match super::vlt::revert_vlt_slots(&content, edit)? { + super::vlt::SlotRevert::Restored(restored) => { + staged.insert(edit.path.clone(), Some(restored)); + out.reverted_files.push(edit.path.clone()); + } + super::vlt::SlotRevert::Unchanged => {} + super::vlt::SlotRevert::Vanished => vanished_vlt.push(i), } continue; } @@ -1044,6 +1049,18 @@ pub async fn revert_npm_redirect_purl( } } + for &i in &vanished_vlt { + let edit = &state.edits[i]; + let Some(content) = staged_read(&staged, project_root, &edit.path).await? else { + return Err(format!( + "{} no longer exists; cannot revert the recorded hosted \ + redirect for {lock_key}", + edit.path + )); + }; + super::vlt::check_vanished(&content, edit)?; + } + // LAST ONE OUT: the `.npmrc` `allow-remote=all` auto-config exists only // for package-lock / shrinkwrap hosted entries (npm >= 12 refuses them // without it). When this purl's revert leaves no such entry in the @@ -2596,6 +2613,62 @@ mod tests { ); } + #[tokio::test] + async fn npm_vlt_takeover_keeps_a_relaid_flag_and_trailing_slots() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let relaid = format!( + "\"~npm~left-pad@1.3.0\": [2,\"left-pad\",\"sha512-p\",\"{NPM_URL}\",null,null,null,null,{{ \"lp\": \"bin.js\"}}]" + ); + tokio::fs::write(root.join("vlt-lock.json"), vlt_lock(&[relaid])) + .await + .unwrap(); + let mut state = RedirectState::new(); + state.records.insert(NPM_PURL.into(), record()); + state.edits = vec![vlt_node_edit("left-pad@1.3.0", "~npm~left-pad@1.3.0")]; + revert_redirect_purl(root, &mut state, NPM_PURL, false) + .await + .expect("revert succeeds"); + assert_eq!( + tokio::fs::read_to_string(root.join("vlt-lock.json")) + .await + .unwrap(), + vlt_lock(&["\"~npm~left-pad@1.3.0\": [2,\"left-pad\",\"sha512-r\",null,null,null,null,null,{ \"lp\": \"bin.js\"}]".to_string()]) + ); + assert!(state.edits.is_empty() && state.records.is_empty()); + } + + #[tokio::test] + async fn npm_vlt_takeover_reverts_a_relocked_away_variant_whatever_the_ledger_order() { + let hosted = format!("\"sha512-p\",\"{NPM_URL}\""); + let plain = vlt_node_edit("left-pad@1.3.0", "~npm~left-pad@1.3.0"); + let peer = vlt_node_edit("left-pad@1.3.0~peer.2", "~npm~left-pad@1.3.0~peer.2"); + for edits in [ + vec![plain.clone(), peer.clone()], + vec![peer.clone(), plain.clone()], + ] { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let wired = vlt_lock(&[vlt_entry("~npm~left-pad@1.3.0", &hosted)]); + tokio::fs::write(root.join("vlt-lock.json"), &wired) + .await + .unwrap(); + let mut state = RedirectState::new(); + state.records.insert(NPM_PURL.into(), record()); + state.edits = edits; + revert_redirect_purl(root, &mut state, NPM_PURL, false) + .await + .expect("revert succeeds"); + assert_eq!( + tokio::fs::read_to_string(root.join("vlt-lock.json")) + .await + .unwrap(), + vlt_lock(&[vlt_entry("~npm~left-pad@1.3.0", "\"sha512-r\"")]) + ); + assert!(state.edits.is_empty() && state.records.is_empty()); + } + } + #[tokio::test] async fn npm_unclassified_edit_naming_the_purl_refuses_the_claim_untouched() { for dry_run in [true, false] { diff --git a/crates/socket-patch-core/src/patch/redirect/vlt.rs b/crates/socket-patch-core/src/patch/redirect/vlt.rs index 36f3b921..310f1f6f 100644 --- a/crates/socket-patch-core/src/patch/redirect/vlt.rs +++ b/crates/socket-patch-core/src/patch/redirect/vlt.rs @@ -17,9 +17,9 @@ use crate::constants::npm_family::{ BUN_LOCK, BUN_LOCKB, NPM_LOCKS, PNPM_LOCK, VLT_CONFIG, VLT_HIDDEN_LOCK_REL, VLT_LOCK, }; use crate::vendor::vlt_lock_text::{ - entry_text, is_default_registry, nodes_block, parse_node_entry_text, parse_node_line, - parse_vendored_path, render_entry_line, render_tuple_with_slots, sniff_lock, split_dep_id, - split_lines, DepIdKind, LockSniff, NodeEntry, ParsedLock, SectionSpan, + entry_text, is_default_registry, is_registry_url_segment, nodes_block, parse_node_entry_text, + parse_node_line, parse_vendored_path, render_entry_line, render_tuple_with_slots, sniff_lock, + split_dep_id, split_lines, DepIdKind, LockSniff, NodeEntry, ParsedLock, SectionSpan, }; /// The ledger kind of a hosted vlt node splice. @@ -36,14 +36,26 @@ const SIBLING_LOCKS: [&str; 6] = [ BUN_LOCKB, ]; +/// The other npm-family locks present. `bun_lockb_present` reports a +/// `bun.lockb` on disk, which a caller holding its bytes keeps out of +/// `files`. +fn sibling_locks(files: &BTreeMap, bun_lockb_present: bool) -> Vec<&'static str> { + SIBLING_LOCKS + .iter() + .copied() + .filter(|lock| files.contains_key(*lock) || (*lock == BUN_LOCKB && bun_lockb_present)) + .collect() +} + /// Does vlt drive hosted confirmation and the artifact preflight? /// `vlt-lock.json` must be present, and either vlt's install state (the /// `node_modules/.vlt-lock.json` sentinel) is too, or no other npm-family -/// lock is. Otherwise both locks are rewritten and neither decides alone. -pub fn vlt_drives(files: &BTreeMap) -> bool { +/// lock is (`bun_lockb_present`: see [`sibling_locks`]). Otherwise both +/// locks are rewritten and neither decides alone. +pub fn vlt_drives(files: &BTreeMap, bun_lockb_present: bool) -> bool { files.contains_key(VLT_LOCK) && (files.contains_key(VLT_HIDDEN_LOCK_REL) - || !SIBLING_LOCKS.iter().any(|lock| files.contains_key(*lock))) + || sibling_locks(files, bun_lockb_present).is_empty()) } fn lock_unsupported(detail: &str) -> RewriteWarning { @@ -122,8 +134,7 @@ fn is_old_lockfile_ignored(lock: &HostedLock, files: &BTreeMap) nodes.keys().any(|id| { split_dep_id(id).is_some_and(|dep_id| { dep_id.kind == DepIdKind::Registry - && dep_id.first != "npm" - && is_default_registry(&dep_id.first, options) + && (dep_id.first.is_empty() || is_registry_url_segment(&dep_id.first, options)) }) }) }); @@ -149,7 +160,11 @@ fn is_scalar_registry_ignored(lock: &HostedLock) -> bool { scalar && (lock.parsed.version != Some(1) || !registries_npm) } -fn lock_level_warnings(lock: &HostedLock, files: &BTreeMap) -> Vec { +fn lock_level_warnings( + lock: &HostedLock, + files: &BTreeMap, + bun_lockb_present: bool, +) -> Vec { let mut warnings = Vec::new(); if lock.parsed.version.is_none() { warnings.push(RewriteWarning { @@ -176,12 +191,8 @@ fn lock_level_warnings(lock: &HostedLock, files: &BTreeMap) -> V .into(), }); } - if !vlt_drives(files) { - let others: Vec<&str> = SIBLING_LOCKS - .iter() - .copied() - .filter(|lock| files.contains_key(*lock)) - .collect(); + if !vlt_drives(files, bun_lockb_present) { + let others = sibling_locks(files, bun_lockb_present); warnings.push(RewriteWarning { code: "redirect_vlt_sibling_lockfiles".into(), detail: format!( @@ -409,6 +420,7 @@ fn rewrite_dep( pub(super) fn rewrite_vlt_lock( files: &BTreeMap, overrides: &[DepOverride], + bun_lockb_present: bool, result: &mut RewriteResult, ) { let npm: Vec<&DepOverride> = overrides.iter().filter(|o| o.ecosystem == "npm").collect(); @@ -433,7 +445,9 @@ pub(super) fn rewrite_vlt_lock( return; } }; - result.warnings.extend(lock_level_warnings(&lock, files)); + result + .warnings + .extend(lock_level_warnings(&lock, files, bun_lockb_present)); let mut lines: Vec = split_lines(text).into_iter().map(str::to_string).collect(); let mut changed = false; @@ -465,13 +479,21 @@ fn drift(id: &str, why: &str) -> String { ) } -/// Undo one [`KIND`] edit by slots. The line keyed by the recorded DepID -/// gets `original`'s slots [2] and [3] back while its current flags, -/// trailing slots, indent, comma and `\r` stay, so a lock vlt re-laid since -/// the rewrite still reverts. `Ok(None)` when there is nothing to revert: -/// the line already holds `original`'s slots, or the DepID and the hosted -/// URL are both gone (a re-lock). Anything else is drift. -pub(crate) fn revert_vlt_slots(text: &str, edit: &FileEdit) -> Result, String> { +/// What [`revert_vlt_slots`] found on the line keyed by an edit's DepID. +#[derive(Debug, PartialEq)] +pub(crate) enum SlotRevert { + /// The lock text with `original`'s slots back on that line. + Restored(String), + /// The line already holds `original`'s slots. + Unchanged, + /// No line has the DepID. Every instance of a `name@version` shares one + /// hosted URL, so [`check_vanished`] runs only after the transaction's + /// other edits are staged. + Vanished, +} + +/// The recorded DepID and entries of a [`KIND`] edit. +fn recorded(edit: &FileEdit) -> Result<(NodeEntry<'_>, NodeEntry<'_>), String> { fn fragment(v: &Option) -> Option<&str> { v.as_ref().and_then(Value::as_str) } @@ -487,6 +509,16 @@ pub(crate) fn revert_vlt_slots(text: &str, edit: &FileEdit) -> Result Result { + let (original, new) = recorded(edit)?; let id = original.key; let lines = split_lines(text); let Some(span) = nodes_block(&lines) else { @@ -497,22 +529,8 @@ pub(crate) fn revert_vlt_slots(text: &str, edit: &FileEdit) -> Result { - let url_left = url - .as_ref() - .and_then(Value::as_str) - .is_some_and(|url| lines.iter().any(|line| line.contains(url))); - if url_left { - Err(drift( - id, - &format!("no longer has {id}, but still pins its hosted URL"), - )) - } else { - Ok(None) - } - } + [] => Ok(SlotRevert::Vanished), [idx] => { let Some(line) = parse_node_line(lines[*idx]) else { return Err(drift( @@ -521,7 +539,7 @@ pub(crate) fn revert_vlt_slots(text: &str, edit: &FileEdit) -> Result Result = lines.iter().map(|l| (*l).to_string()).collect(); out[*idx] = render_entry_line(&entry_text(id, &tuple), line.comma, line.cr); - Ok(Some(out.join("\n"))) + Ok(SlotRevert::Restored(out.join("\n"))) } _ => Err(drift(id, &format!("has {id} more than once"))), } } +/// A [`SlotRevert::Vanished`] edit is already reverted (a re-lock dropped +/// its pin) unless its hosted URL is still on some line of `text`, the lock +/// with every other edit of the same revert already staged. +pub(crate) fn check_vanished(text: &str, edit: &FileEdit) -> Result<(), String> { + let (_, new) = recorded(edit)?; + let url_left = slot_value(&new, 3) + .as_ref() + .and_then(Value::as_str) + .is_some_and(|url| text.contains(url)); + if url_left { + Err(drift( + new.key, + &format!("no longer has {}, but still pins its hosted URL", new.key), + )) + } else { + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -588,7 +625,7 @@ mod tests { fn rewrite(lock: &str, deps: &[DepOverride]) -> RewriteResult { let mut result = RewriteResult::default(); - rewrite_vlt_lock(&files(&[(VLT_LOCK, lock)]), deps, &mut result); + rewrite_vlt_lock(&files(&[(VLT_LOCK, lock)]), deps, false, &mut result); result } @@ -621,24 +658,74 @@ mod tests { fn vlt_drives_needs_the_lock_and_the_sentinel_or_no_sibling() { let sentinel = (VLT_HIDDEN_LOCK_REL, ""); let lock = (VLT_LOCK, "{}"); - assert!(!vlt_drives(&files(&[]))); - assert!(!vlt_drives(&files(&[sentinel, (VLT_CONFIG, "{}")]))); - assert!(vlt_drives(&files(&[lock]))); - assert!(vlt_drives(&files(&[lock, (VLT_CONFIG, "{}")]))); + assert!(!vlt_drives(&files(&[]), false)); + assert!(!vlt_drives(&files(&[sentinel, (VLT_CONFIG, "{}")]), false)); + assert!(vlt_drives(&files(&[lock]), false)); + assert!(vlt_drives(&files(&[lock, (VLT_CONFIG, "{}")]), false)); for sibling in SIBLING_LOCKS { let other = (sibling, "x"); - assert!(!vlt_drives(&files(&[lock, other])), "{sibling}"); - assert!(vlt_drives(&files(&[lock, other, sentinel])), "{sibling}"); + assert!(!vlt_drives(&files(&[lock, other]), false), "{sibling}"); + assert!( + vlt_drives(&files(&[lock, other, sentinel]), false), + "{sibling}" + ); } - assert!(!vlt_drives(&files(&[ - lock, - ("package-lock.json", "x"), - ("bun.lockb", "x") - ]))); - assert!(vlt_drives(&files(&[ - lock, - ("packages/a/package-lock.json", "x") - ]))); + assert!(!vlt_drives( + &files(&[lock, ("package-lock.json", "x"), ("bun.lockb", "x")]), + false + )); + assert!(vlt_drives( + &files(&[lock, ("packages/a/package-lock.json", "x")]), + false + )); + } + + #[test] + fn a_bun_lockb_on_disk_is_a_sibling_outside_files() { + let lock = (VLT_LOCK, "{}"); + assert!(!vlt_drives(&files(&[lock]), true)); + assert!(vlt_drives(&files(&[lock, (VLT_HIDDEN_LOCK_REL, "")]), true)); + assert!(!vlt_drives(&files(&[]), true)); + + let text = lock_with(&[®istry_entry()]); + let deps = [dep("left-pad", "1.3.0", Some(SHA))]; + let mut result = RewriteResult::default(); + rewrite_vlt_lock(&files(&[(VLT_LOCK, &text)]), &deps, true, &mut result); + assert_eq!(codes(&result), ["redirect_vlt_sibling_lockfiles"]); + assert!(result.warnings[0].detail.contains("and bun.lockb are both")); + assert!(result.confirmed_vlt_uuids.contains("uuid-left-pad")); + } + + #[test] + fn old_lockfile_ignored_counts_only_empty_and_registry_url_segments() { + let r = "https://registry.example.com/"; + let v0 = |options: &str, id: &str| { + format!( + "{{\n \"lockfileVersion\": 0,\n \"options\": {options},\n \"nodes\": {{\n \"{id}\": [0,\"left-pad\",\"{REG_SHA}\"]\n }},\n \"edges\": {{}}\n}}\n" + ) + }; + let old_lockfile = |lock: &str| { + codes(&rewrite(lock, &[dep("left-pad", "1.3.0", Some(SHA))])) + .contains(&"redirect_vlt_old_lockfile_ignored") + }; + assert!(old_lockfile(&v0("{}", "··left-pad@1.3.0"))); + let url_segment = format!( + "·{}·left-pad@1.3.0", + r.replace(':', "%3A").replace('/', "§") + ); + assert!(old_lockfile(&v0( + &format!("{{\"registry\": \"{r}\"}}"), + &url_segment + ))); + assert!(!old_lockfile(&v0("{}", "·npm·left-pad@1.3.0"))); + assert!(!old_lockfile(&v0( + "{\"default-registry-alias\": \"corp\"}", + "·corp·left-pad@1.3.0" + ))); + assert!(!old_lockfile(&v0( + &format!("{{\"registry\": \"{r}\", \"registries\": {{\"acme\": \"{r}\"}}}}"), + "·acme·left-pad@1.3.0" + ))); } #[test] @@ -760,12 +847,16 @@ mod tests { let mut other = dep("left-pad", "1.3.0", Some(SHA)); other.ecosystem = "pypi".into(); let mut result = RewriteResult::default(); - rewrite_vlt_lock(&files(&[(VLT_CONFIG, "{}")]), &[other], &mut result); + rewrite_vlt_lock(&files(&[(VLT_CONFIG, "{}")]), &[other], false, &mut result); assert!(result.warnings.is_empty()); } fn revert(lock: &str, edit: &FileEdit) -> Result, String> { - revert_vlt_slots(lock, edit) + match revert_vlt_slots(lock, edit)? { + SlotRevert::Restored(text) => Ok(Some(text)), + SlotRevert::Unchanged => Ok(None), + SlotRevert::Vanished => check_vanished(lock, edit).map(|()| None), + } } #[test] diff --git a/crates/socket-patch-core/src/vendor/vlt_lock_text.rs b/crates/socket-patch-core/src/vendor/vlt_lock_text.rs index 187c66f1..684c3454 100644 --- a/crates/socket-patch-core/src/vendor/vlt_lock_text.rs +++ b/crates/socket-patch-core/src/vendor/vlt_lock_text.rs @@ -372,16 +372,27 @@ pub(crate) fn is_default_registry(segment: &str, options: Option<&Map>) -> bool { + let Some(registry) = options + .and_then(|o| o.get("registry")) + .and_then(Value::as_str) + else { + return false; + }; reqwest::Url::parse(segment).is_ok_and(|url| matches!(url.scheme(), "http" | "https")) - && with_trailing_slash(segment) == registry + && with_trailing_slash(segment) == with_trailing_slash(registry) } // ── lock-level sniff ───────────────────────────────────────────────────── diff --git a/crates/socket-patch-core/src/vex/discover/pypi_other.rs b/crates/socket-patch-core/src/vex/discover/pypi_other.rs index ce0bb6dc..989fd17d 100644 --- a/crates/socket-patch-core/src/vex/discover/pypi_other.rs +++ b/crates/socket-patch-core/src/vex/discover/pypi_other.rs @@ -676,6 +676,7 @@ mod tests { &[dep], &BTreeMap::new(), major, + false, ); let lock = result.files.get("Pipfile.lock").expect("lock rewritten"); assert!(lock.contains(&format!("\"{key}\": ")), "{lock}"); diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel-only/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel-only/expected-confirmation.json new file mode 100644 index 00000000..e4bdb58e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel-only/expected-confirmation.json @@ -0,0 +1,5 @@ +{ + "confirmed": [], + "refused": [], + "vltDrives": false +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel-only/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel-only/expected-edits.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel-only/expected-edits.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel-only/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel-only/expected-warnings.json new file mode 100644 index 00000000..a73a1b0e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel-only/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_no_lockfile" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel-only/input/node_modules/.vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel-only/input/node_modules/.vlt-lock.json new file mode 100644 index 00000000..e69de29b diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel-only/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel-only/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/hidden-lock-sentinel-only/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/expected-edits.json new file mode 100644 index 00000000..03fafbb8 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"·acme·left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"·acme·left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/expected-warnings.json new file mode 100644 index 00000000..13e48044 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/expected-warnings.json @@ -0,0 +1,3 @@ +[ + "redirect_vlt_scalar_registry_ignored" +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/expected/vlt-lock.json new file mode 100644 index 00000000..ca5bec4e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/expected/vlt-lock.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 0, + "options": { + "registry": "http://127.0.0.1:4873/", + "registries": { + "acme": "http://127.0.0.1:4873/" + } + }, + "nodes": { + "·acme·left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ·acme·left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/input/vlt-lock.json new file mode 100644 index 00000000..713952a4 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/input/vlt-lock.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 0, + "options": { + "registry": "http://127.0.0.1:4873/", + "registries": { + "acme": "http://127.0.0.1:4873/" + } + }, + "nodes": { + "·acme·left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ·acme·left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-alias-url-equals-registry/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/expected-confirmation.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/expected-confirmation.json new file mode 100644 index 00000000..7cfc5070 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/expected-confirmation.json @@ -0,0 +1,7 @@ +{ + "confirmed": [ + "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + ], + "refused": [], + "vltDrives": true +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/expected-edits.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/expected-edits.json new file mode 100644 index 00000000..781a3d18 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/expected-edits.json @@ -0,0 +1,10 @@ +[ + { + "path": "vlt-lock.json", + "kind": "redirect_vlt_lock_node", + "action": "rewritten", + "key": "left-pad@1.3.0", + "original": "\"·corp·left-pad@1.3.0\": [0,\"left-pad\",\"sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==\"]", + "new": "\"·corp·left-pad@1.3.0\": [0,\"left-pad\",\"sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==\",\"https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz\"]" + } +] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/expected-warnings.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/expected-warnings.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/expected-warnings.json @@ -0,0 +1 @@ +[] diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/expected/vlt-lock.json new file mode 100644 index 00000000..6215661d --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/expected/vlt-lock.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 0, + "options": { + "default-registry-alias": "corp", + "registries": { + "corp": "https://corp.example.com/" + } + }, + "nodes": { + "·corp·left-pad@1.3.0": [0,"left-pad","sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ·corp·left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/input/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/input/vlt-lock.json new file mode 100644 index 00000000..69225cce --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/input/vlt-lock.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 0, + "options": { + "default-registry-alias": "corp", + "registries": { + "corp": "https://corp.example.com/" + } + }, + "nodes": { + "·corp·left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="] + }, + "edges": { + "file·. left-pad": "prod 1.3.0 ·corp·left-pad@1.3.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/overrides.json b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/overrides.json new file mode 100644 index 00000000..10e9e18a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/redirect/npm/vlt/lock-v0-default-registry-alias/overrides.json @@ -0,0 +1,13 @@ +[ + { + "ecosystem": "npm", + "name": "left-pad", + "version": "1.3.0", + "token": "11111111-1111-1111-1111-111111111111", + "patchUuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "artifactUrl": "https://patch.socket.dev/patch/npm/11111111-1111-1111-1111-111111111111/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz", + "integrity": { + "sha512": "sha512-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + } + } +] diff --git a/crates/socket-patch-core/tests/fixtures/vex-discover-golden/redirect-npm.json b/crates/socket-patch-core/tests/fixtures/vex-discover-golden/redirect-npm.json index 06f89a6c..144e514d 100644 --- a/crates/socket-patch-core/tests/fixtures/vex-discover-golden/redirect-npm.json +++ b/crates/socket-patch-core/tests/fixtures/vex-discover-golden/redirect-npm.json @@ -1237,6 +1237,14 @@ "elsewhere": [], "live_claims": [] }, + "redirect/npm/vlt/hidden-lock-sentinel-only/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, "redirect/npm/vlt/invalid-json/input": { "refs": [], "diagnostics": [], @@ -1309,6 +1317,38 @@ "elsewhere": [], "live_claims": [] }, + "redirect/npm/vlt/lock-v0-alias-url-equals-registry/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-v0-alias-url-equals-registry/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-v0-default-registry-alias/expected": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, + "redirect/npm/vlt/lock-v0-default-registry-alias/input": { + "refs": [], + "diagnostics": [], + "recognized": [], + "unlocked_pins": [], + "elsewhere": [], + "live_claims": [] + }, "redirect/npm/vlt/lock-v0-empty-segment/expected": { "refs": [], "diagnostics": [], diff --git a/crates/socket-patch-core/tests/redirect_golden_reverse_replay.rs b/crates/socket-patch-core/tests/redirect_golden_reverse_replay.rs index 20e15156..8c45b54c 100644 --- a/crates/socket-patch-core/tests/redirect_golden_reverse_replay.rs +++ b/crates/socket-patch-core/tests/redirect_golden_reverse_replay.rs @@ -4,10 +4,11 @@ //! what the depscan server writes into its PR ledgers, so this is also the //! proof that socket-patch reverts a server-written vlt redirect. //! -//! Each case runs three ways: as written, after vlt's LF re-save of a CRLF -//! lock, and after a re-save that appended a sibling node (which moves the -//! trailing comma). Both the whole-ledger replay and the per-purl revert are -//! exercised. +//! Each case runs up to four ways: as written, after vlt's LF re-save of a +//! CRLF lock, after a re-save that appended a sibling node (which moves the +//! trailing comma), and after a re-lock that dropped the last node while +//! earlier instances of the same package may still carry its hosted URL. +//! Both the whole-ledger replay and the per-purl revert are exercised. use std::collections::BTreeMap; use std::fs; @@ -104,13 +105,7 @@ fn ledger(case: &Case) -> RedirectState { /// nodes section, so the formerly last entry gains a comma. fn with_appended_node(text: &str) -> String { let mut lines: Vec = text.split('\n').map(str::to_string).collect(); - let open = lines - .iter() - .position(|l| l.trim_end_matches('\r') == " \"nodes\": {") - .expect("nodes section"); - let close = (open + 1..lines.len()) - .find(|&i| matches!(lines[i].trim_end_matches('\r'), " }" | " },")) - .expect("nodes section end"); + let (_, close) = nodes_section(&lines); let last = close - 1; let cr = if lines[last].ends_with('\r') { "\r" @@ -127,6 +122,37 @@ fn with_appended_node(text: &str) -> String { lines.join("\n") } +/// The nodes section's line range: the opening line and the closing one. +fn nodes_section(lines: &[String]) -> (usize, usize) { + let open = lines + .iter() + .position(|l| l.trim_end_matches('\r') == " \"nodes\": {") + .expect("nodes section"); + let close = (open + 1..lines.len()) + .find(|&i| matches!(lines[i].trim_end_matches('\r'), " }" | " },")) + .expect("nodes section end"); + (open, close) +} + +/// The lock after vlt re-locked its last node away (the new last entry +/// loses its comma), or `None` when fewer than two nodes remain. +fn without_last_node(text: &str) -> Option { + let mut lines: Vec = text.split('\n').map(str::to_string).collect(); + let (open, close) = nodes_section(&lines); + if close - open < 3 { + return None; + } + lines.remove(close - 1); + let last = &mut lines[close - 2]; + let cr = last.ends_with('\r'); + let body = last + .trim_end_matches('\r') + .trim_end_matches(',') + .to_string(); + *last = if cr { format!("{body}\r") } else { body }; + Some(lines.join("\n")) +} + fn variants(case: &Case) -> Vec<(&'static str, String, String)> { let mut out = vec![("as-written", case.expected.clone(), case.input.clone())]; if case.expected.contains('\r') { @@ -141,6 +167,12 @@ fn variants(case: &Case) -> Vec<(&'static str, String, String)> { with_appended_node(&case.expected), with_appended_node(&case.input), )); + if let (Some(on_disk), Some(want)) = ( + without_last_node(&case.expected), + without_last_node(&case.input), + ) { + out.push(("last-node-relocked-away", on_disk, want)); + } out } diff --git a/crates/socket-patch-core/tests/vlt_locks.rs b/crates/socket-patch-core/tests/vlt_locks.rs index a1ce21be..151ae319 100644 --- a/crates/socket-patch-core/tests/vlt_locks.rs +++ b/crates/socket-patch-core/tests/vlt_locks.rs @@ -1,7 +1,8 @@ //! Real vlt locks (`tests/fixtures/vlt-locks//`, captured from every //! era) through the hosted rewriter: only the target nodes' slots [2] and [3] //! change, and the output stays in vlt's own canonical serialization, so -//! vlt's next save leaves it byte-identical. +//! vlt's next save leaves it byte-identical. A CRLF checkout of each capture +//! gets the same edits, with every line keeping its `\r`. use std::collections::BTreeMap; use std::fs; @@ -340,5 +341,23 @@ fn hosted_rewrite_changes_exactly_the_target_slots() { let again = rewrite_registry_redirect(&result.files, &overrides); assert!(again.files.is_empty(), "{version}: a rerun is a no-op"); assert!(again.edits.is_empty(), "{version}"); + + let crlf: BTreeMap = files + .iter() + .map(|(name, text)| (name.clone(), text.replace('\n', "\r\n"))) + .collect(); + let crlf_result = rewrite_registry_redirect(&crlf, &overrides); + let crlf_codes: Vec<&str> = crlf_result + .warnings + .iter() + .map(|w| w.code.as_str()) + .collect(); + assert_eq!(&crlf_codes, warnings, "{version}: CRLF"); + assert_eq!( + crlf_result.files["vlt-lock.json"], + output.replace('\n', "\r\n"), + "{version}: CRLF" + ); + assert_eq!(crlf_result.edits, result.edits, "{version}: CRLF"); } } From 10420baae06b7b7683390531928bde3da30b7027 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Fri, 25 Sep 2026 23:16:45 -0400 Subject: [PATCH 13/46] Redirect vlt projects in hosted mode `scan --mode hosted` and `get --mode hosted` now repoint vlt-lock.json at Socket's hosted patches: every default-registry node of a patched package keeps its DepID and gets the patched sha512 and hosted URL, and vlt drives confirmation when its install state is present or no other npm-family lock is. Before anything is written, each artifact is fetched the way vlt fetches it. A response vlt would reject (re-gzipped, wrong sha512, HTTP error, unreachable) withholds the package instead of pinning a lock `vlt ci` cannot install. vlt never refreshes an installed copy, so after the rewrite socket-patch removes stale store entries and the hidden lock, and rollback and remove do the same once the registry pins are back. --no-vlt-install-cleanup (SOCKET_NO_VLT_INSTALL_CLEANUP) keeps them; the redirect_vlt_reinstall_required advisory says what to run. A same-run --vex no longer attests a vlt package whose installed copy is stale or unchecked, whose lock an older vlt may ignore, or which also resolves from another registry. Assisted-by: Claude Code:claude-opus-5-5 --- CHANGELOG.md | 21 + crates/socket-patch-cli/CLI_CONTRACT.md | 9 + crates/socket-patch-cli/src/args.rs | 18 + .../socket-patch-cli/src/commands/rollback.rs | 25 + .../src/commands/scan/hosted.rs | 355 +++++- .../src/commands/scan/hosted/vlt.rs | 483 ++++++++ .../socket-patch-cli/src/commands/scan/mod.rs | 62 +- .../socket-patch-cli/tests/cli_global_args.rs | 13 +- .../socket-patch-cli/tests/cli_parse_get.rs | 12 + .../tests/cli_parse_repair.rs | 12 + .../socket-patch-cli/tests/cli_parse_scan.rs | 27 + .../tests/cli_parse_vendor.rs | 14 + .../socket-patch-cli/tests/cli_parse_vex.rs | 19 + .../tests/covgap_commands_rollback.rs | 112 +- .../tests/covgap_commands_scan_hosted.rs | 129 ++ .../tests/hosted_symlinked_files.rs | 81 +- .../tests/in_process_get_hosted_ecosystems.rs | 62 + .../tests/in_process_redirect.rs | 4 + .../tests/in_process_redirect/vlt.rs | 1057 +++++++++++++++++ .../tests/in_process_rollback_hosted.rs | 4 + .../tests/in_process_rollback_hosted/vlt.rs | 191 +++ .../tests/remove_rollback_api_overrides.rs | 1 + .../tests/vlt_hosted_common/mod.rs | 509 ++++++++ crates/socket-patch-core/src/api/client.rs | 8 +- .../src/patch/redirect/mod.rs | 34 +- .../src/patch/redirect/vlt.rs | 66 +- .../src/patch/redirect/vlt_heal.rs | 903 ++++++++++++++ .../src/patch/redirect/vlt_preflight.rs | 503 ++++++++ 28 files changed, 4666 insertions(+), 68 deletions(-) create mode 100644 crates/socket-patch-cli/src/commands/scan/hosted/vlt.rs create mode 100644 crates/socket-patch-cli/tests/in_process_redirect/vlt.rs create mode 100644 crates/socket-patch-cli/tests/in_process_rollback_hosted/vlt.rs create mode 100644 crates/socket-patch-cli/tests/vlt_hosted_common/mod.rs create mode 100644 crates/socket-patch-core/src/patch/redirect/vlt_heal.rs create mode 100644 crates/socket-patch-core/src/patch/redirect/vlt_preflight.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ebc467e..006973ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -287,6 +287,27 @@ into the new version's section — see docs/releasing.md. slot, CRLF re-saved as LF). A node vlt has since re-locked away is already reverted; any other change refuses with the `vlt-lock.json` remedy. Peer and modifier variants are claimed per `name@version`. +- **`scan --mode hosted` and `get --mode hosted` redirect vlt projects.** + `vlt-lock.json` default-registry nodes of a patched `name@version` (every + peer and modifier variant, in every DepID era and CRLF lock) keep their + DepID and get the patched sha512 and hosted URL; `vlt.json` is read only. + vlt drives confirmation when its install state is present or no other + npm-family lock is; otherwise both locks are rewritten + (`redirect_vlt_sibling_lockfiles`). Before anything is written, each + artifact is fetched as vlt fetches it: a response vlt would reject + (re-gzipped, wrong sha512, HTTP error, unreachable) withholds the dep + (`redirect_vlt_artifact_unverifiable`) instead of pinning a lock `vlt ci` + cannot install. After the write, stale installed copies of the + Socket-owned nodes (`node_modules/.vlt-lock.json` and their + `node_modules/.vlt/` entries) are removed so the next `vlt install` + extracts the patched packages; `rollback` and `remove` do the same for + the registry bytes. New `--no-vlt-install-cleanup` / + `SOCKET_NO_VLT_INSTALL_CLEANUP` keeps them, and the + `redirect_vlt_reinstall_required` advisory says what to run. A same-run + `--vex` does not attest a vlt package whose installed copy is stale or + unchecked, whose lock a vlt release may ignore, or which also resolves + from a non-default registry. vlt ledgers require the socket-patch + release that adds vlt support. - **`redirect_yarn_berry_mixed_line_endings` and `vendor_yarn_berry_mixed_line_endings`.** A `yarn.lock` (or, vendored, a root `package.json`) that mixes CRLF and LF line endings — or holds a bare diff --git a/crates/socket-patch-cli/CLI_CONTRACT.md b/crates/socket-patch-cli/CLI_CONTRACT.md index 6168dd2c..706583ed 100644 --- a/crates/socket-patch-cli/CLI_CONTRACT.md +++ b/crates/socket-patch-cli/CLI_CONTRACT.md @@ -58,6 +58,7 @@ In v3.0 every subcommand accepts the same set of "global" flags via a single sha | `--no-telemetry` | — | `SOCKET_TELEMETRY_DISABLED` | `false` | bool | Disable anonymous usage telemetry | | `--no-trust-lockfile-config` | — | `SOCKET_NO_TRUST_LOCKFILE_CONFIG` | `false` | bool | Opt out of hosted mode's automatic `trustLockfile: true` write to `pnpm-workspace.yaml` (see the pnpm trust-config note under the scan arguments) | | `--no-npm-allow-remote-config` | — | `SOCKET_NO_NPM_ALLOW_REMOTE_CONFIG` | `false` | bool | Opt out of hosted mode's automatic `allow-remote=all` write to the project `.npmrc` (see the npm allow-remote note under the scan arguments). Read by `scan --mode hosted` and `get --mode hosted`; other subcommands accept it silently | +| `--no-vlt-install-cleanup` | — | `SOCKET_NO_VLT_INSTALL_CLEANUP` | `false` | bool | Opt out of hosted mode's warm-tree heal for vlt: stale installed copies (`node_modules/.vlt-lock.json` and the stale `node_modules/.vlt/` entries) are left in place after `vlt-lock.json` is repointed (`scan`/`get --mode hosted`) or restored (`rollback`/`remove`), and the `redirect_vlt_reinstall_required` advisory tells you to run `vlt ci` instead. Other subcommands accept it silently | The `--offline` semantics unified in v3.0. Previously `apply` enforced strict airgap, `repair` skipped network ops, and `rollback` failed when blobs were missing. All three now mean the same thing: never contact the network, fail loudly when a required local source is missing. On `repair`, `--offline` and `--download-only` are mutually exclusive (exit 2). `scan` and `get` need remote data for their core function (patch discovery / patch fetch), so `--offline` refuses them up front — exit 1 with an error naming the offline gate (JSON: `status: "error"`), before any crawl, client build, or network contact. This covers `scan --vendor` too: offline vendored staging is `vendor --offline`'s job. @@ -1287,6 +1288,14 @@ Every `--json` invocation emits a single JSON object that follows the **unified | `redirect_bun_workspace_unsupported` | `redirect.warnings[]` (warning) | scan/get `--mode hosted` (bun): a lockfileVersion-0 lock (Bun 1.1.39–1.1.45 `--save-text-lockfile`) holds `workspace:` packages; frozen installs of that grammar cannot keep the hosted tuple. Detail: "Bun version-0 workspace locks cannot preserve hosted tarballs on frozen installs; delete bun.lock and re-run `bun install` with Bun >= 1.2 (which writes lockfileVersion 1, accepted by hosted mode) — a plain in-place `bun install` bumps the version only when a workspace depends on another workspace (e.g. root -> member); otherwise it keeps version 0 or fails to resolve" (measured: Bun 1.2.0 keeps 0, 1.2.23–1.4.2 exit 1 "failed to resolve" on a root that does not depend on its members). Version-1/2 workspace locks are rewritten. Exit 0. | | `redirect_bun_lockb_invalid` | `redirect.warnings[]` (warning) | scan/get `--mode hosted`: the native binary lock is malformed, unreadable, unsupported or cannot be rewritten safely. No installer is spawned and no binary or sibling npm lock edit or takeover occurs; dry-run reports the same format error. Exit 0, `redirected: 0`. | | `redirect_bun_entry_not_found` / `redirect_bun_missing_sha512` | `redirect.warnings[]` (warning) | scan/get `--mode hosted` (bun): the lock has no rewritable entry at the granted version (re-resolved, or occupied by an unowned URL/file spec) / the grant carries no sha512 integrity. Per-dep; nothing rewritten for it; exit 0. NOT emitted for the digest-less 2-tuple Bun 1.1.39–1.3.9 re-save our URL tuple as — that entry counts as redirected and is healed. | +| `redirect_vlt_lock_unsupported` | `redirect.warnings[]` (warning) | scan/get `--mode hosted` (vlt): `vlt-lock.json` has a `lockfileVersion` other than absent, `0` or `1` (decided on the raw JSON token), is not a JSON object, starts with a UTF-8 BOM, or its `nodes` section is not vlt's one-node-per-line layout. Nothing rewritten; also refuses a vendored → hosted takeover of a `flavor: "vlt"` entry before its revert (`redirect.skipped[].reason`). Exit 0. | +| `redirect_vlt_missing_sha512` / `redirect_vlt_entry_not_found` / `redirect_vlt_entry_vendored` / `redirect_vlt_unsupported_lock_key` | `redirect.warnings[]` (warning) | scan/get `--mode hosted` (vlt): the grant has no sha512 / the lock has no default-registry node for `name@version` / the only match is a vendored `file` node under `.socket/vendor/npm//` / a default-registry instance is outside vlt's node-line grammar or still unpatched after the splice. Per dep; none of the dep's instances is written, and a refused dep is never confirmed, whichever lock drives. Exit 0. | +| `redirect_vlt_custom_registry_skipped` | `redirect.warnings[]` (warning) | scan/get `--mode hosted` (vlt): same-`name@version` nodes under a named alias, a scoped registry or jsr were left untouched (hosted mode only redirects vlt's default registry). The dep is still redirected, but the run's `--vex` does not attest it. | +| `redirect_vlt_lockfile_version_missing` / `redirect_vlt_old_lockfile_ignored` / `redirect_vlt_scalar_registry_ignored` | `redirect.warnings[]` (warning) | scan/get `--mode hosted` (vlt): the lock has no `lockfileVersion` (vlt ≥ 1.0.0-rc.15 re-resolves it) / a legacy default-registry id without `"modifiers"` in `vlt.json` (vlt 0.0.0-16 … 0.0.0-24 ignore the lock) / a scalar `registry` option that vlt 1.0.0-rc.7 … rc.29 honor over the lock. The deps stay redirected, but the run's `--vex` does not attest them. | +| `redirect_vlt_sibling_lockfiles` | `redirect.warnings[]` (warning) | scan/get `--mode hosted` (vlt): `vlt-lock.json` and another npm-family lock are both present and vlt's install state (`node_modules/.vlt-lock.json` or `node_modules/.vlt/`) is not, so both locks were rewritten and the other lock's rules confirm. | +| `redirect_vlt_no_lockfile` | `redirect.warnings[]` (warning) | scan/get `--mode hosted` (vlt): `vlt.json` or vlt's install state is present without `vlt-lock.json`; replaces `redirect_npm_no_lockfile` for vlt projects. | +| `redirect_vlt_artifact_unverifiable` | `redirect.warnings[]` (warning), `redirect.skipped[].reason` | scan/get `--mode hosted` (vlt): before any takeover or rewrite (dry runs included), each granted artifact with a default-registry instance in `vlt-lock.json` is fetched once as vlt fetches it (`accept-encoding: gzip;q=1.0, identity;q=0.5`, no `Authorization`, up to 10 redirects) and must return 200 with no content encoding (or `identity`) and the granted sha512. On failure (`content-encoding `, `sha512 mismatch`, `http `, `fetch error `, `offline`) the dep is withheld from every rewriter when vlt drives (from the vlt rewrite only otherwise). A lock already pinned by an earlier run is left pinned, and neither confirmed nor attested. Projects without `vlt-lock.json` make no such request. Exit 0. | +| `redirect_vlt_reinstall_required` | `redirect.warnings[]` (advisory); rollback/remove `warnings[]` (+ human stderr) | vlt: `vlt-lock.json` pins (or, after rollback/remove, no longer pins) Socket-patched packages, and vlt never refreshes an installed copy. The heal removes `node_modules/.vlt-lock.json` and each stale `node_modules/.vlt/` of a Socket-owned node (never a link's target, never outside the project, never a copy it cannot judge) unless `--no-vlt-install-cleanup` or `--dry-run`; the detail says whether copies were removed, left stale, could not be checked, or none were stale. Stale or unchecked copies are not attested by the run's `--vex`. Invalidation failures only warn. | | `vendor_prebuilt_stub_invalid` | `failed` / `skipped` (warning) | vendor (gem, `--vendor-source`): the served stub gemspec fails the rubygems `summary`/`authors` bar, so bundler would refuse the vendored path source at install time. `service`: refusal naming the missing attributes; `auto`: loud warning + local-build fallback — or, when the gem is also not installed locally (no stub to derive), a refusal naming the served defect and the install-the-gem remedy. | | `gem_spec_invalid` | `failed` | vendor (gem): the LOCAL `specifications/` stub gemspec fails the same rubygems `summary`/`authors` bar (a corrupted or hand-edited gem home); the refusal names the file — reinstall the gem (`gem pristine ` / fresh `bundle install`). | | `vendor_*` / `pypi_*` / `gemfile_*` / `lock_*` / `locked_version_mismatch` / `user_authored_*` / `native_extensions_unsupported` / `platform_gem_unsupported` | `failed`/`skipped` | vendor: per-ecosystem refusal + drift vocabulary; see the Vendor command contract section. New tags are additive (MINOR). | diff --git a/crates/socket-patch-cli/src/args.rs b/crates/socket-patch-cli/src/args.rs index fcd45dcb..baa46e67 100644 --- a/crates/socket-patch-cli/src/args.rs +++ b/crates/socket-patch-cli/src/args.rs @@ -362,6 +362,22 @@ pub struct GlobalArgs { value_parser = parse_bool_flag, )] pub no_npm_allow_remote_config: bool, + + /// Hosted mode (`scan`/`get --mode hosted`, and `rollback`/`remove` of + /// hosted redirects): do NOT remove stale vlt installed copies + /// (`node_modules/.vlt-lock.json` and the stale `node_modules/.vlt` + /// entries) after `vlt-lock.json` is repointed or restored. vlt never + /// refreshes an installed copy on its own, so opting out means running + /// `vlt ci` instead (the run's `redirect_vlt_reinstall_required` + /// advisory says so). Other subcommands accept it silently. + #[arg( + help_heading = GLOBAL_OPTIONS, + long = "no-vlt-install-cleanup", + env = "SOCKET_NO_VLT_INSTALL_CLEANUP", + default_value_t = false, + value_parser = parse_bool_flag, + )] + pub no_vlt_install_cleanup: bool, } impl GlobalArgs { @@ -550,6 +566,7 @@ pub const GLOBAL_ARG_ENV_VARS: &[&str] = &[ "SOCKET_TELEMETRY_DISABLED", "SOCKET_NO_TRUST_LOCKFILE_CONFIG", "SOCKET_NO_NPM_ALLOW_REMOTE_CONFIG", + "SOCKET_NO_VLT_INSTALL_CLEANUP", ]; /// Every env var a **subcommand-local** flag binds (one per `env = "..."` @@ -642,6 +659,7 @@ impl Default for GlobalArgs { no_telemetry: false, no_trust_lockfile_config: false, no_npm_allow_remote_config: false, + no_vlt_install_cleanup: false, } } } diff --git a/crates/socket-patch-cli/src/commands/rollback.rs b/crates/socket-patch-cli/src/commands/rollback.rs index 4a3aefe7..ff42597e 100644 --- a/crates/socket-patch-cli/src/commands/rollback.rs +++ b/crates/socket-patch-cli/src/commands/rollback.rs @@ -1042,6 +1042,20 @@ pub(crate) async fn run_hosted_leg( }; let mut out = HostedLegOutcome::default(); + // The vlt nodes the unwound purls pin, read before the revert drops + // their edits: the heal below invalidates the patched installed copies + // once the registry pins are back. + let vlt_scope: Vec = if replay_eligible { + purls + .iter() + .cloned() + .chain(state.records.keys().cloned()) + .collect() + } else { + purls.to_vec() + }; + let vlt_targets = + socket_patch_core::patch::redirect::vlt_heal::ledger_targets(state, &vlt_scope); // When the whole-ledger replay will run anyway (the scope covers every // record), npm purls on Bun projects defer to it so all lockfile edits // are staged together atomically. A scoped unwind of one of several @@ -1138,6 +1152,17 @@ pub(crate) async fn run_hosted_leg( } } } + let unwound: Vec<_> = vlt_targets + .into_iter() + .filter(|t| { + out.reverted.iter().any(|p| { + socket_patch_core::utils::purl::canonical_purl(p) + == socket_patch_core::utils::purl::canonical_purl(&t.purl) + }) || (replay_eligible && !out.failed.iter().any(|(p, _)| p.starts_with("group:"))) + }) + .collect(); + out.warnings + .extend(crate::commands::scan::vlt_rollback_heal(common, &unwound).await); out } diff --git a/crates/socket-patch-cli/src/commands/scan/hosted.rs b/crates/socket-patch-cli/src/commands/scan/hosted.rs index 03223412..412e4f20 100644 --- a/crates/socket-patch-cli/src/commands/scan/hosted.rs +++ b/crates/socket-patch-cli/src/commands/scan/hosted.rs @@ -16,13 +16,20 @@ use crate::commands::vex::generate_vex_from_manifest_path; use super::{discover_selected, ScanArgs}; mod python; +mod vlt; + +pub(crate) use vlt::rollback_heal as vlt_rollback_heal; /// Candidate lockfiles / registry configs the redirect rewriters may touch — /// read from the project when present and handed to `rewrite_registry_redirect`. /// Fragment-edit kinds whose lockfile the package manager re-lays in place /// (keeping the Socket source) — a re-scan REBASES their ledger edits instead /// of appending; see the ledger merge below. -const REBASE_KINDS: &[&str] = &["redirect_poetry_lock_package", "redirect_pdm_lock_package"]; +const REBASE_KINDS: &[&str] = &[ + "redirect_poetry_lock_package", + "redirect_pdm_lock_package", + socket_patch_core::patch::redirect::vlt::KIND, +]; const REDIRECT_CANDIDATE_FILES: &[&str] = &[ "package-lock.json", @@ -37,6 +44,12 @@ const REDIRECT_CANDIDATE_FILES: &[&str] = &[ ".yarnrc.yml", "bun.lock", "bun.lockb", + // vlt: the lock is rewritten, vlt.json is read-only (the old-lockfile + // advisory), and the hidden lock is only stat'ed as the install-state + // sentinel. + "vlt-lock.json", + "vlt.json", + "node_modules/.vlt-lock.json", "requirements.txt", "uv.lock", "poetry.lock", @@ -1069,7 +1082,7 @@ pub(crate) async fn run_redirect_selected( ) -> i32 { use socket_patch_core::manifest::schema::PatchRecord; use socket_patch_core::patch::redirect::{ - rewrite_registry_redirect_with_pipenv_version, RedirectState, + rewrite_registry_redirect_withholding_vlt, RedirectState, }; let mut skipped: Vec = Vec::new(); @@ -1238,6 +1251,31 @@ pub(crate) async fn run_redirect_selected( return 1; } + // vlt artifact preflight: before any takeover or rewrite (dry runs + // included), each in-scope artifact is fetched as vlt fetches it. A + // failure while vlt drives withholds the dep from every rewriter; + // otherwise only from the vlt rewrite. + let vlt_preflight = { + let deps: Vec<(&str, &DepOverride)> = candidates + .iter() + .filter(|c| c.dep.ecosystem == "npm") + .map(|c| (c.purl.as_str(), &c.dep)) + .collect(); + vlt::artifact_preflight(common, api_client, &deps).await + }; + if !vlt_preflight.withheld_everywhere.is_empty() { + for (uuid, purl) in &vlt_preflight.withheld_everywhere { + skipped.push(serde_json::json!({ + "purl": purl, "uuid": uuid, "reason": vlt::WITHHELD_REASON, + })); + } + candidates.retain(|c| { + !vlt_preflight + .withheld_everywhere + .contains_key(&c.dep.patch_uuid) + }); + } + // The apply lock (see `acquire_hosted_lock`), taken only by a WET run // that holds at least one granted reference — the only runs that can // write anything: the takeover pre-reverts (lockfiles + the vendored @@ -1453,10 +1491,39 @@ pub(crate) async fn run_redirect_selected( } else { None }; + // vlt twin: the hosted rewriter's lock-level refusal must be known + // before a vendored vlt entry is reverted, or the revert strips the + // live vendored patch and the rewrite then refuses the lock. + let vlt_entry = |entry: &socket_patch_core::vendor::VendorEntry| { + entry.ecosystem == "npm" && entry.flavor.as_deref() == Some("vlt") + }; + let vlt_takeover_refusal = if takeover + .iter() + .any(|(_, entry)| entry.as_ref().is_some_and(vlt_entry)) + { + match socket_patch_core::utils::fs::read_regular_to_string( + &common + .cwd + .join(socket_patch_core::constants::npm_family::VLT_LOCK), + ) + .await + { + Ok(lock) => { + let files = std::collections::BTreeMap::from([( + socket_patch_core::constants::npm_family::VLT_LOCK.to_string(), + lock, + )]); + socket_patch_core::patch::redirect::vlt::preflight_vlt_hosted(&files).err() + } + Err(_) => None, + } + } else { + None + }; // The takeover refusal (if any) for one candidate: bun gates every - // npm purl, berry only its vendored-berry entries. A refused purl is - // never dispatched (see the loop), so its wiring is not a write - // target here. + // npm purl, berry and vlt only their own vendored entries. A refused + // purl is never dispatched (see the loop), so its wiring is not a + // write target here. let takeover_refusal = |c: &Candidate, entry: Option<&socket_patch_core::vendor::VendorEntry>| @@ -1464,11 +1531,18 @@ pub(crate) async fn run_redirect_selected( if !c.purl.starts_with("pkg:npm/") { return None; } - bun_takeover_refusal.as_ref().or_else(|| { - berry_takeover_refusal - .as_ref() - .filter(|_| entry.is_some_and(berry_entry)) - }) + bun_takeover_refusal + .as_ref() + .or_else(|| { + berry_takeover_refusal + .as_ref() + .filter(|_| entry.is_some_and(berry_entry)) + }) + .or_else(|| { + vlt_takeover_refusal + .as_ref() + .filter(|_| entry.is_some_and(vlt_entry)) + }) }; // SYMLINK PRE-CHECK for the takeover reverts — the same rule as the // SYMLINK GUARD below, applied to the files the reverts rewrite @@ -1704,6 +1778,12 @@ pub(crate) async fn run_redirect_selected( if *name == "bun.lockb" { continue; } + if *name == socket_patch_core::constants::npm_family::VLT_HIDDEN_LOCK_REL { + if vlt::install_state_present(&common.cwd) { + files.insert((*name).to_string(), String::new()); + } + continue; + } if let Ok(content) = read_regular_to_string(&common.cwd.join(name)).await { files.insert((*name).to_string(), content); } @@ -1876,12 +1956,13 @@ pub(crate) async fn run_redirect_selected( .filter(|o| !(binary_content.as_ref().is_some_and(Result::is_err) && o.ecosystem == "npm")) .cloned() .collect(); - let mut rewrite = rewrite_registry_redirect_with_pipenv_version( + let mut rewrite = rewrite_registry_redirect_withholding_vlt( &files, &rewrite_overrides, &python_metadata, pipenv_major, common.cwd.join("bun.lockb").exists(), + &vlt_preflight.withheld_from_vlt, ); if let Some(content) = binary_content { rewrite @@ -2351,6 +2432,14 @@ pub(crate) async fn run_redirect_selected( .filter(|c| { let purl = c.purl.as_str(); let uuid = c.dep.patch_uuid.as_str(); + // vlt decides before the binary-bun rule, so `bun.lockb` beside + // a vlt-driven `vlt-lock.json` never confirms an npm purl. + if rewrite.refused_vlt_uuids.contains(uuid) { + return false; + } + if rewrite.vlt_drives && purl.starts_with("pkg:npm/") { + return rewrite.confirmed_vlt_uuids.contains(uuid); + } if binary_bun && purl.starts_with("pkg:npm/") { return rewrite.confirmed_bun_binary_uuids.contains(uuid); } @@ -2538,12 +2627,18 @@ pub(crate) async fn run_redirect_selected( // fragment) and adopting the fresh `new` keeps the chain a single // invertible link: replay swaps the fragment this run wrote back to // the fragment the very first run found. + let vlt_merged = rebase_vlt_edits( + &mut ledger.edits, + &rewrite.edits, + files + .get(socket_patch_core::constants::npm_family::VLT_LOCK) + .map(String::as_str), + ); let mut rebased: Vec = Vec::new(); - for edit in rewrite - .edits - .iter() - .filter(|e| REBASE_KINDS.contains(&e.kind.as_str())) - { + for edit in rewrite.edits.iter().filter(|e| { + REBASE_KINDS.contains(&e.kind.as_str()) + && e.kind != socket_patch_core::patch::redirect::vlt::KIND + }) { let siblings: Vec = ledger .edits .iter() @@ -2600,7 +2695,10 @@ pub(crate) async fn run_redirect_selected( // them made `remove` leave the second pin (and its registry // block) in place while reporting success. let recorded = ledger.edits.len(); - for edit in &rewrite.edits { + for (i, edit) in rewrite.edits.iter().enumerate() { + if vlt_merged[i] { + continue; + } let is_rebased = REBASE_KINDS.contains(&edit.kind.as_str()) && rebased.iter().any(|&t| { let old = &ledger.edits[t]; @@ -2705,6 +2803,33 @@ pub(crate) async fn run_redirect_selected( .await }; + // vlt warm-tree heal (DESIGN §3.9): stale installed copies of the + // Socket-owned nodes are invalidated (classified only on a dry run or + // with --no-vlt-install-cleanup), and every confirmed vlt purl whose + // installed or next-installed bytes are not known to be patched is + // withheld from the in-run VEX attestation. + let vlt_stale = { + let rewrite_codes: Vec<&str> = rewrite.warnings.iter().map(|w| w.code.as_str()).collect(); + let lock_key = socket_patch_core::constants::npm_family::VLT_LOCK; + vlt::heal_after_rewrite( + common, + &vlt::HealInputs { + final_lock: rewrite + .files + .get(lock_key) + .or_else(|| files.get(lock_key)) + .map(String::as_str), + preflight: &vlt_preflight, + records: &ledger.records, + confirmed: &confirmed, + confirmed_vlt: &rewrite.confirmed_vlt_uuids, + foreign: &rewrite.vlt_foreign_uuids, + rewrite_warning_codes: &rewrite_codes, + }, + ) + .await + }; + // Cross-mode takeover: a committed vendored ledger (`.socket/vendor/state.json`) // may still claim package(s) this project also has a hosted redirect ledger // for — their tarballs would then be orphaned and that ledger stale. But the @@ -2776,12 +2901,20 @@ pub(crate) async fn run_redirect_selected( .iter() .map(|(purl, _)| purl.clone()) .filter(|purl| { - !gem_stale.stale_purls.contains(purl) && !python_stale.stale_purls.contains(purl) + !gem_stale.stale_purls.contains(purl) + && !python_stale.stale_purls.contains(purl) + && !vlt_stale.stale_purls.contains(purl) }) .collect(); // A healthy copy in another interpreter must not override a stale - // Python tree found by the probe, including with --vex-no-verify. - params.known_stale = python_stale.stale_purls.iter().cloned().collect(); + // Python tree found by the probe, including with --vex-no-verify; + // likewise a vlt copy the heal could not prove patched. + params.known_stale = python_stale + .stale_purls + .iter() + .chain(&vlt_stale.stale_purls) + .cloned() + .collect(); let manifest_path = common.resolved_manifest_path(); match generate_vex_from_manifest_path(common, ¶ms, &manifest_path).await { Ok(summary) => { @@ -2808,12 +2941,14 @@ pub(crate) async fn run_redirect_selected( }) }) .collect(); + warnings.extend(vlt_preflight.warnings.iter().cloned()); warnings.extend(record_warnings.iter().cloned()); warnings.extend(rush_warnings.iter().cloned()); warnings.extend(pnpm_warnings.iter().cloned()); warnings.extend(npm_warnings.iter().cloned()); warnings.extend(gem_stale.warnings.iter().cloned()); warnings.extend(python_stale.warnings.iter().cloned()); + warnings.extend(vlt_stale.warnings.iter().cloned()); warnings.extend(takeover_pre_warnings.iter().cloned()); warnings.extend(takeover_warnings.iter().cloned()); warnings.extend(prune_warnings.iter().cloned()); @@ -2997,7 +3132,8 @@ const TAKEOVER_INFO_CODES: &[&str] = &[ /// sentence (`pnpm >=11 rejects…` must not become `Pnpm`). const LOWERCASE_TOOLS: &[&str] = &[ "npm", "pnpm", "yarn", "bun", "cargo", "pip", "pipenv", "uv", "poetry", "pdm", "hatch", "go", - "gem", "bundler", "bundle", "composer", "mvn", "gradle", "dotnet", "deno", "rush", + "gem", "bundler", "bundle", "composer", "mvn", "gradle", "dotnet", "deno", "rush", "vlt", + "vlx", "vlr", ]; /// Capitalize the first letter of a message for an `Error:`/`Warning:` @@ -3175,6 +3311,12 @@ fn describe_skip_reason(reason: &str) -> String { "redirect_bun_lock_unsupported" | "redirect_bun_lockb_invalid" => { "the Bun lockfile blocks the vendored-to-hosted migration (see the warning)".into() } + "redirect_vlt_lock_unsupported" => { + "vlt-lock.json blocks the vendored-to-hosted migration (see the warning)".into() + } + "redirect_vlt_artifact_unverifiable" => { + "vlt could not verify the hosted artifact (see the warning)".into() + } other => format!("server status `{other}`"), } } @@ -3287,13 +3429,70 @@ fn format_next_steps( .iter() .any(|f| f == "package-lock.json" || f == "npm-shrinkwrap.json"); let hint = if npm { " (e.g. `npm ci`)" } else { "" }; - vec![ + let mut steps = vec![ format!("Commit {} to keep the redirect.", join_names(&commit, 6)), format!( "Reinstall from the updated lockfile{hint} so the installed packages pick up the \ patched artifacts, then run `socket-patch vex` to verify them." ), - ] + ]; + if files + .iter() + .any(|f| f == socket_patch_core::constants::npm_family::VLT_LOCK) + { + steps.push("vlt: commit vlt-lock.json; CI should run `vlt ci`".to_string()); + } + steps +} + +/// Merge this run's vlt node edits into the recorded ones. A fresh edit +/// for the same `key` and DepID keeps the recorded `original` (the +/// pristine registry entry) and takes the fresh `new`; one whose recorded +/// same-key edits all name DepIDs the pre-run lock no longer holds (a +/// re-lock, or a new id grammar after a vlt upgrade) replaces them, +/// `original` included. Returns, per fresh edit, whether it was merged +/// (anything else is appended as usual). +fn rebase_vlt_edits( + ledger: &mut Vec, + fresh: &[socket_patch_core::patch::redirect::FileEdit], + before_lock: Option<&str>, +) -> Vec { + use socket_patch_core::patch::redirect::vlt::{edit_dep_id, lock_node_ids, KIND}; + let live = before_lock.and_then(lock_node_ids).unwrap_or_default(); + let mut merged = vec![false; fresh.len()]; + for (i, edit) in fresh.iter().enumerate() { + if edit.kind != KIND { + continue; + } + let id = edit_dep_id(edit); + let same_key: Vec = ledger + .iter() + .enumerate() + .filter(|(_, old)| old.kind == KIND && old.path == edit.path && old.key == edit.key) + .map(|(j, _)| j) + .collect(); + if let Some(&j) = same_key + .iter() + .find(|&&j| id.is_some() && edit_dep_id(&ledger[j]) == id) + { + ledger[j].new = edit.new.clone(); + ledger[j].action = edit.action.clone(); + merged[i] = true; + continue; + } + let vanished: Vec = same_key + .into_iter() + .filter(|&j| edit_dep_id(&ledger[j]).is_none_or(|old| !live.contains(&old))) + .collect(); + if let Some((&first, rest)) = vanished.split_first() { + ledger[first] = edit.clone(); + for &j in rest.iter().rev() { + ledger.remove(j); + } + merged[i] = true; + } + } + merged } /// Transient-frame boxed constructor for [`run_redirect_selected`] — the @@ -3338,8 +3537,9 @@ mod tests { pnpm_lock_may_need_store_flag, pnpm_trust_rerun_reminder, sentence_case, split_sentences, wrap_tokens, wrap_words, TAKEOVER_INFO_CODES, }; + use super::{rebase_vlt_edits, REBASE_KINDS}; use socket_patch_core::constants::npm_family; - use socket_patch_core::patch::redirect::DepOverride; + use socket_patch_core::patch::redirect::{DepOverride, FileEdit}; /// Lock-head version sniff against the byte-real heads the 2026-08-18 /// matrix captured from pnpm 7/8/9-12: quoted `'9.0'` and `'6.0'`, @@ -4528,6 +4728,14 @@ mod tests { describe_skip_reason("redirect_bun_lockb_invalid"), describe_skip_reason("redirect_bun_lock_unsupported") ); + assert_eq!( + describe_skip_reason("redirect_vlt_artifact_unverifiable"), + "vlt could not verify the hosted artifact (see the warning)" + ); + assert_eq!( + describe_skip_reason("redirect_vlt_lock_unsupported"), + "vlt-lock.json blocks the vendored-to-hosted migration (see the warning)" + ); assert_eq!(describe_skip_reason("mystery"), "server status `mystery`"); for code in [ "not_found", @@ -4619,6 +4827,12 @@ mod tests { "The redirect ledger ./a is malformed" ); assert_eq!(sentence_case("pnpm >=11 rejects"), "pnpm >=11 rejects"); + for tool in ["vlt", "vlx", "vlr"] { + assert_eq!( + sentence_case(&format!("{tool} ci fails")), + format!("{tool} ci fails") + ); + } assert_eq!( sentence_case("pnpm-lock.yaml was repointed"), "pnpm-lock.yaml was repointed" @@ -4819,6 +5033,99 @@ mod tests { assert!(!steps[1].contains("npm ci"), "{}", steps[1]); } + #[test] + fn next_steps_add_the_vlt_ci_line_only_for_a_rewritten_vlt_lock() { + let steps = format_next_steps(&["vlt-lock.json".to_string()], true, false); + assert_eq!( + steps.last().map(String::as_str), + Some("vlt: commit vlt-lock.json; CI should run `vlt ci`") + ); + assert!( + !format_next_steps(&["package-lock.json".to_string()], true, false) + .iter() + .any(|s| s.starts_with("vlt:")) + ); + } + + fn vlt_edit(key: &str, id: &str, slot2: &str, slot3: &str) -> FileEdit { + FileEdit { + path: "vlt-lock.json".into(), + kind: socket_patch_core::patch::redirect::vlt::KIND.into(), + action: "rewritten".into(), + key: Some(key.into()), + original: Some(serde_json::Value::String(format!( + "\"{id}\": [0,\"x\",\"sha512-reg\",null]" + ))), + new: Some(serde_json::Value::String(format!( + "\"{id}\": [0,\"x\",\"{slot2}\",\"{slot3}\"]" + ))), + } + } + + fn vlt_lock_with(ids: &[&str]) -> String { + let nodes: Vec = ids + .iter() + .map(|id| format!(" \"{id}\": [0,\"x\"]")) + .collect(); + format!( + "{{\n \"lockfileVersion\": 1,\n \"nodes\": {{\n{}\n }},\n \"edges\": {{}}\n}}\n", + nodes.join(",\n") + ) + } + + #[test] + fn vlt_rerun_keeps_the_pristine_original_for_the_same_dep_id() { + assert!(REBASE_KINDS.contains(&socket_patch_core::patch::redirect::vlt::KIND)); + let mut ledger = vec![vlt_edit("x@1.0.0", "~npm~x@1.0.0", "sha512-p1", "u1")]; + let mut fresh = vlt_edit("x@1.0.0", "~npm~x@1.0.0", "sha512-p2", "u2"); + fresh.original = ledger[0].new.clone(); + let merged = rebase_vlt_edits( + &mut ledger, + std::slice::from_ref(&fresh), + Some(&vlt_lock_with(&["~npm~x@1.0.0"])), + ); + assert_eq!(merged, [true]); + assert_eq!(ledger.len(), 1); + assert_eq!( + ledger[0].original, + vlt_edit("x@1.0.0", "~npm~x@1.0.0", "", "").original + ); + assert_eq!(ledger[0].new, fresh.new); + } + + #[test] + fn vlt_relocked_dep_id_supersedes_the_recorded_edits() { + let mut ledger = vec![ + vlt_edit("x@1.0.0", "··x@1.0.0", "sha512-p", "u"), + vlt_edit("x@1.0.0", "·npm·x@1.0.0", "sha512-p", "u"), + vlt_edit("y@1.0.0", "·npm·y@1.0.0", "sha512-p", "u"), + ]; + let fresh = vlt_edit("x@1.0.0", "~npm~x@1.0.0", "sha512-p", "u"); + let merged = rebase_vlt_edits( + &mut ledger, + std::slice::from_ref(&fresh), + Some(&vlt_lock_with(&["~npm~x@1.0.0", "·npm·y@1.0.0"])), + ); + assert_eq!(merged, [true]); + assert_eq!( + ledger, + [fresh, vlt_edit("y@1.0.0", "·npm·y@1.0.0", "sha512-p", "u")] + ); + } + + #[test] + fn vlt_edit_of_a_live_sibling_dep_id_is_appended() { + let mut ledger = vec![vlt_edit("x@1.0.0", "··x@1.0.0", "sha512-p", "u")]; + let fresh = vlt_edit("x@1.0.0", "·npm·x@1.0.0", "sha512-p", "u"); + let merged = rebase_vlt_edits( + &mut ledger, + std::slice::from_ref(&fresh), + Some(&vlt_lock_with(&["··x@1.0.0", "·npm·x@1.0.0"])), + ); + assert_eq!(merged, [false]); + assert_eq!(ledger.len(), 1); + } + #[test] fn next_steps_after_a_takeover_name_the_removed_vendored_state() { assert_eq!( diff --git a/crates/socket-patch-cli/src/commands/scan/hosted/vlt.rs b/crates/socket-patch-cli/src/commands/scan/hosted/vlt.rs new file mode 100644 index 00000000..62fa3981 --- /dev/null +++ b/crates/socket-patch-cli/src/commands/scan/hosted/vlt.rs @@ -0,0 +1,483 @@ +//! The vlt steps of the hosted flow: the artifact preflight, run before any +//! takeover or rewrite, and the warm-tree heal with its +//! `redirect_vlt_reinstall_required` advisory, run after the writes (and by +//! rollback/remove after the vlt pins are restored). + +use std::collections::{BTreeMap, BTreeSet}; +use std::path::Path; + +use socket_patch_core::constants::npm_family::{ + BUN_LOCK, BUN_LOCKB, NPM_LOCKS, PNPM_LOCK, VLT_HIDDEN_LOCK_REL, VLT_LOCK, VLT_STORE_DIR, +}; +use socket_patch_core::manifest::schema::PatchRecord; +use socket_patch_core::patch::redirect::vlt_heal::{ + self, classify_target, read_install_state, Expected, LedgerTarget, Target, TargetState, +}; +use socket_patch_core::patch::redirect::vlt_preflight::{self, OFFLINE_REASON}; +use socket_patch_core::patch::redirect::{vlt, DepOverride}; + +use super::StaleInstallOutcome; + +pub(super) const REINSTALL_REQUIRED: &str = "redirect_vlt_reinstall_required"; +const ARTIFACT_UNVERIFIABLE: &str = "redirect_vlt_artifact_unverifiable"; + +/// The lock-level warnings that say vlt may discard the redirect. +const DISCARDING_LOCK_WARNINGS: [&str; 3] = [ + "redirect_vlt_lockfile_version_missing", + "redirect_vlt_old_lockfile_ignored", + "redirect_vlt_scalar_registry_ignored", +]; + +/// Whether vlt's install state exists: the hidden lock as a regular file, +/// or the store as a real directory. Stat only; the hidden lock can be +/// megabytes and is never read into the rewriter's input. +pub(super) fn install_state_present(cwd: &Path) -> bool { + let is = |rel: &str, dir: bool| { + std::fs::symlink_metadata(cwd.join(rel)).is_ok_and(|m| { + if dir { + m.file_type().is_dir() + } else { + m.file_type().is_file() + } + }) + }; + is(VLT_HIDDEN_LOCK_REL, false) || is(VLT_STORE_DIR, true) +} + +/// What the artifact preflight decided for this run's npm candidates. +#[derive(Default)] +pub(super) struct Preflight { + /// Failed while vlt drives: withheld from every rewriter. + pub(super) withheld_everywhere: BTreeMap, + /// Failed while another npm-family lock may drive: kept out of the vlt + /// rewrite only. + pub(super) withheld_from_vlt: BTreeSet, + pub(super) passed: BTreeSet, + /// Artifact bytes by URL, for the heal's no-record comparison. + pub(super) artifacts: BTreeMap>, + pub(super) warnings: Vec, +} + +/// The files `vlt_drives` and the preflight scope read: `vlt-lock.json` +/// itself, and presence-only entries for the sibling locks and vlt's +/// install state. Empty without a readable `vlt-lock.json`. +async fn vlt_inputs(cwd: &Path) -> BTreeMap { + let mut files = BTreeMap::new(); + let Ok(lock) = socket_patch_core::utils::fs::read_regular_to_string(&cwd.join(VLT_LOCK)).await + else { + return files; + }; + files.insert(VLT_LOCK.to_string(), lock); + for sibling in [NPM_LOCKS[0], NPM_LOCKS[1], "yarn.lock", PNPM_LOCK, BUN_LOCK] { + if std::fs::metadata(cwd.join(sibling)).is_ok_and(|m| m.is_file()) { + files.insert(sibling.to_string(), String::new()); + } + } + if install_state_present(cwd) { + files.insert(VLT_HIDDEN_LOCK_REL.to_string(), String::new()); + } + files +} + +fn unverifiable_detail(url: &str, reason: &str, purl: &str, already_pinned: bool) -> String { + if already_pinned { + format!( + "vlt would fail to verify {url}: {reason}; {purl} was left pinned by an earlier run \ + and `vlt ci` will fail until the artifact verifies" + ) + } else { + format!("vlt would fail to verify {url}: {reason}; nothing was written for {purl}") + } +} + +/// Fetch each in-scope artifact the way vlt does (once per distinct URL, +/// `offline` making no request) and decide which deps may be pinned in +/// `vlt-lock.json`. Projects without `vlt-lock.json` make no request. +pub(super) async fn artifact_preflight( + common: &crate::args::GlobalArgs, + api_client: &socket_patch_core::api::client::ApiClient, + deps: &[(&str, &DepOverride)], +) -> Preflight { + let mut out = Preflight::default(); + let files = vlt_inputs(&common.cwd).await; + if files.is_empty() { + return out; + } + let overrides: Vec = deps.iter().map(|(_, dep)| (*dep).clone()).collect(); + let scope = vlt_preflight::preflight_scope(&files, &overrides); + if scope.is_empty() { + return out; + } + let drives = vlt::vlt_drives(&files, common.cwd.join(BUN_LOCKB).exists()); + let urls: BTreeSet = scope.iter().map(|d| d.artifact_url.clone()).collect(); + let probes = if common.offline { + BTreeMap::new() + } else { + vlt_preflight::probe_artifacts(api_client, &urls).await + }; + for dep in &scope { + let reason = match probes.get(&dep.artifact_url) { + None => Some(OFFLINE_REASON.to_string()), + Some(probe) => probe.failure(&dep.sha512), + }; + let Some(reason) = reason else { + out.passed.insert(dep.patch_uuid.clone()); + if let Some(body) = probes.get(&dep.artifact_url).and_then(|p| p.body.clone()) { + out.artifacts.insert(dep.artifact_url.clone(), body); + } + continue; + }; + let purl = deps + .iter() + .find(|(_, d)| d.patch_uuid == dep.patch_uuid) + .map_or("", |(purl, _)| *purl); + out.warnings.push(serde_json::json!({ + "code": ARTIFACT_UNVERIFIABLE, + "detail": unverifiable_detail(&dep.artifact_url, &reason, purl, dep.already_pinned), + })); + if drives { + out.withheld_everywhere + .insert(dep.patch_uuid.clone(), purl.to_string()); + } else { + out.withheld_from_vlt.insert(dep.patch_uuid.clone()); + } + } + out +} + +/// The skip `reason` of a dep the preflight withheld from every rewriter. +pub(super) const WITHHELD_REASON: &str = ARTIFACT_UNVERIFIABLE; + +fn patch_server_origins(common: &crate::args::GlobalArgs) -> Vec { + common + .patch_server_url + .iter() + .chain(common.api_url.iter()) + .filter(|url| !url.trim().is_empty()) + .cloned() + .collect() +} + +fn cleanup_disabled(common: &crate::args::GlobalArgs) -> bool { + common.dry_run || common.no_vlt_install_cleanup +} + +/// How a heal ended, for the advisory wording. +#[derive(Debug, Default, PartialEq, Eq)] +struct HealTally { + invalidated: usize, + stale_left: usize, + undeterminable: usize, + stale_uuids: BTreeSet, + undeterminable_uuids: BTreeSet, +} + +/// Classify `targets` against `expected` and invalidate the stale ones +/// unless cleanup is off. `(dep_id, name, lock sha512, record, artifact, +/// uuid)` per target. +async fn heal_targets( + common: &crate::args::GlobalArgs, + targets: &[(Target<'_>, &str)], + expected: Expected, +) -> HealTally { + let mut tally = HealTally::default(); + if targets.is_empty() { + return tally; + } + let state = read_install_state(&common.cwd).await; + let mut stale: Vec<(String, &str)> = Vec::new(); + for (target, uuid) in targets { + match classify_target(&state, &common.cwd, target, expected).await { + TargetState::Stale => stale.push((target.dep_id.to_string(), uuid)), + TargetState::Undeterminable => { + tally.undeterminable += 1; + tally.undeterminable_uuids.insert((*uuid).to_string()); + } + TargetState::Healthy => {} + } + } + if stale.is_empty() { + return tally; + } + if cleanup_disabled(common) { + tally.stale_left = stale.len(); + tally + .stale_uuids + .extend(stale.iter().map(|(_, uuid)| (*uuid).to_string())); + return tally; + } + let ids: Vec = stale.iter().map(|(id, _)| id.clone()).collect(); + let result = vlt_heal::invalidate(&common.cwd, &state, &ids).await; + let hidden_failed = result + .failed + .iter() + .any(|(id, _)| id == VLT_HIDDEN_LOCK_REL); + for (id, uuid) in &stale { + let removed = result.removed.contains(id); + if removed && !hidden_failed { + tally.invalidated += 1; + } else { + tally.stale_left += 1; + tally.stale_uuids.insert((*uuid).to_string()); + } + } + tally +} + +fn reinstall_detail(tally: &HealTally) -> String { + if tally.stale_left > 0 { + format!( + "vlt-lock.json pins Socket-patched packages, but node_modules still holds {} \ + unpatched copies and `vlt install` will not refresh them; run `vlt ci` (or re-run \ + without --no-vlt-install-cleanup).", + tally.stale_left + ) + } else if tally.undeterminable > 0 { + undeterminable_detail(tally.undeterminable) + } else if tally.invalidated > 0 { + format!( + "vlt-lock.json pins Socket-patched packages; socket-patch removed {} stale installed \ + copies (node_modules/.vlt-lock.json and node_modules/.vlt entries), so node_modules \ + is incomplete until you run `vlt install` (or `vlt ci`), which installs the patched \ + packages. Note: `vlt update` re-resolves from the registry and drops these \ + redirects.", + tally.invalidated + ) + } else { + "vlt-lock.json pins Socket-patched packages; fresh checkouts install them with `vlt ci` \ + or `vlt install --frozen-lockfile`. Note: `vlt update` re-resolves from the registry \ + and drops these redirects." + .to_string() + } +} + +fn undeterminable_detail(n: usize) -> String { + format!( + "vlt-lock.json pins Socket-patched packages, but socket-patch could not check {n} \ + installed copies (node_modules is a link, or no patch record or artifact was \ + available); run `vlt ci` to be sure the patched packages are installed." + ) +} + +/// What this run's vlt rewrite needs from the rest of the hosted flow. +pub(super) struct HealInputs<'a> { + /// The final `vlt-lock.json` (as written, or as a dry run would write it). + pub(super) final_lock: Option<&'a str>, + pub(super) preflight: &'a Preflight, + /// This run's fetched records merged with the ledger's, keyed by purl. + pub(super) records: &'a BTreeMap, + pub(super) confirmed: &'a [(String, String)], + pub(super) confirmed_vlt: &'a BTreeSet, + pub(super) foreign: &'a BTreeSet, + pub(super) rewrite_warning_codes: &'a [&'a str], +} + +/// The heal after a hosted rewrite, the advisory, and the purls whose +/// installed or next-installed bytes are not known to be patched (removed +/// from the same run's in-run VEX attestation). +pub(super) async fn heal_after_rewrite( + common: &crate::args::GlobalArgs, + inputs: &HealInputs<'_>, +) -> StaleInstallOutcome { + let mut out = StaleInstallOutcome::default(); + let owned: Vec = inputs + .final_lock + .map(|lock| vlt_heal::socket_owned_instances(lock, &patch_server_origins(common))) + .unwrap_or_default() + .into_iter() + .filter(|i| inputs.preflight.passed.contains(&i.patch_uuid)) + .collect(); + let mut tally = HealTally::default(); + if !owned.is_empty() { + let targets: Vec<(Target<'_>, &str)> = owned + .iter() + .map(|i| { + let record = inputs.records.values().find(|r| r.uuid == i.patch_uuid); + ( + Target { + dep_id: &i.dep_id, + name: &i.name, + lock_sha512: i.sha512.as_deref(), + record, + artifact: inputs.preflight.artifacts.get(&i.url).map(Vec::as_slice), + }, + i.patch_uuid.as_str(), + ) + }) + .collect(); + tally = heal_targets(common, &targets, Expected::Patched).await; + out.warnings.push(serde_json::json!({ + "code": REINSTALL_REQUIRED, + "detail": reinstall_detail(&tally), + })); + } + let lock_discards = inputs + .rewrite_warning_codes + .iter() + .any(|code| DISCARDING_LOCK_WARNINGS.contains(code)); + for (purl, uuid) in inputs.confirmed { + if !inputs.confirmed_vlt.contains(uuid) { + continue; + } + if lock_discards + || inputs.foreign.contains(uuid) + || tally.stale_uuids.contains(uuid) + || tally.undeterminable_uuids.contains(uuid) + { + out.stale_purls.insert(purl.clone()); + } + } + out +} + +/// The heal after rollback or remove restored the registry pins of +/// `targets` (the ledger's vlt nodes of the unwound purls, collected before +/// the revert): patched store copies are invalidated so the next install +/// extracts the registry bytes. Returns `(code, detail)` warnings. +pub(crate) async fn rollback_heal( + common: &crate::args::GlobalArgs, + targets: &[LedgerTarget], +) -> Vec<(String, String)> { + if targets.is_empty() || common.dry_run { + return Vec::new(); + } + let lock = socket_patch_core::utils::fs::read_regular_to_string(&common.cwd.join(VLT_LOCK)) + .await + .ok(); + let shas: Vec> = targets + .iter() + .map(|t| { + lock.as_deref() + .and_then(|l| vlt_heal::lock_sha512(l, &t.dep_id)) + }) + .collect(); + let classified: Vec<(Target<'_>, &str)> = targets + .iter() + .zip(&shas) + .map(|(t, sha)| { + ( + Target { + dep_id: &t.dep_id, + name: &t.name, + lock_sha512: sha.as_deref(), + record: t.record.as_ref(), + artifact: None, + }, + t.purl.as_str(), + ) + }) + .collect(); + let tally = heal_targets(common, &classified, Expected::Pristine).await; + let restored: BTreeSet<&str> = targets.iter().map(|t| t.purl.as_str()).collect(); + let detail = if tally.stale_left > 0 { + format!( + "restored registry pins for {} packages, but node_modules still holds {} patched \ + copies and `vlt install` will not refresh them; run `vlt ci` (or re-run without \ + --no-vlt-install-cleanup)", + restored.len(), + tally.stale_left + ) + } else if tally.undeterminable > 0 { + undeterminable_detail(tally.undeterminable) + } else if tally.invalidated > 0 { + format!( + "restored registry pins for {} packages; removed the patched installed copies, so \ + node_modules is incomplete until you run `vlt install` (or `vlt ci`)", + restored.len() + ) + } else { + return Vec::new(); + }; + vec![(REINSTALL_REQUIRED.to_string(), detail)] +} + +#[cfg(test)] +mod tests { + use super::*; + + fn test_api() -> socket_patch_core::api::client::ApiClient { + socket_patch_core::api::client::ApiClient::new( + socket_patch_core::api::client::ApiClientOptions { + api_url: "http://127.0.0.1:9".into(), + api_token: Some("secret".into()), + use_public_proxy: false, + org_slug: Some("org".into()), + }, + ) + } + + #[test] + fn advisory_variants_follow_severity_order() { + let mut tally = HealTally::default(); + assert!(reinstall_detail(&tally).contains("fresh checkouts install them with `vlt ci`")); + tally.invalidated = 2; + assert!(reinstall_detail(&tally).contains("socket-patch removed 2 stale installed copies")); + tally.undeterminable = 1; + assert!(reinstall_detail(&tally).contains("could not check 1 installed copies")); + tally.stale_left = 3; + assert!(reinstall_detail(&tally).contains("still holds 3 unpatched copies")); + } + + #[tokio::test] + async fn offline_withholds_every_probed_dep_without_a_request() { + let server = wiremock::MockServer::start().await; + let tmp = tempfile::tempdir().unwrap(); + let url = format!("{}/patch/npm/t/u/left-pad-1.3.0.tgz", server.uri()); + std::fs::write( + tmp.path().join(VLT_LOCK), + "{\n \"lockfileVersion\": 1,\n \"options\": {},\n \"nodes\": {\n \ + \"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-old\",\"https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz\"]\n },\n \"edges\": {}\n}\n", + ) + .unwrap(); + let dep = DepOverride { + ecosystem: "npm".into(), + name: "left-pad".into(), + namespace: None, + version: "1.3.0".into(), + token: String::new(), + patch_uuid: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa".into(), + artifact_url: url.clone(), + berry_zip_url: None, + registry_override: None, + integrity: socket_patch_core::patch::redirect::Integrity { + sha512: Some("sha512-new".into()), + ..Default::default() + }, + }; + let common = crate::args::GlobalArgs { + cwd: tmp.path().to_path_buf(), + offline: true, + ..crate::args::GlobalArgs::default() + }; + let api = test_api(); + let pre = artifact_preflight(&common, &api, &[("pkg:npm/left-pad@1.3.0", &dep)]).await; + assert!(server.received_requests().await.unwrap().is_empty()); + assert!(pre.passed.is_empty()); + assert_eq!( + pre.withheld_everywhere + .get(&dep.patch_uuid) + .map(String::as_str), + Some("pkg:npm/left-pad@1.3.0") + ); + assert_eq!( + pre.warnings[0]["detail"], + format!( + "vlt would fail to verify {url}: offline; nothing was written for \ + pkg:npm/left-pad@1.3.0" + ) + ); + } + + #[tokio::test] + async fn no_vlt_lock_makes_no_request() { + let tmp = tempfile::tempdir().unwrap(); + std::fs::write(tmp.path().join("package-lock.json"), "{}").unwrap(); + let common = crate::args::GlobalArgs { + cwd: tmp.path().to_path_buf(), + ..crate::args::GlobalArgs::default() + }; + let api = test_api(); + let pre = artifact_preflight(&common, &api, &[]).await; + assert!(pre.passed.is_empty() && pre.warnings.is_empty()); + } +} diff --git a/crates/socket-patch-cli/src/commands/scan/mod.rs b/crates/socket-patch-cli/src/commands/scan/mod.rs index 0e727c38..4618af42 100644 --- a/crates/socket-patch-cli/src/commands/scan/mod.rs +++ b/crates/socket-patch-cli/src/commands/scan/mod.rs @@ -49,6 +49,7 @@ pub(crate) use self::discovery::unsupported_layout_warnings; use self::gc::gc_json; pub(crate) use self::hosted::boxed_run_redirect_selected; use self::hosted::run_redirect; +pub(crate) use self::hosted::vlt_rollback_heal; pub(crate) use self::vendor_flow::{ boxed_scan_vendor_step, preview_vendor_json, print_dry_run_refusals, }; @@ -183,7 +184,11 @@ pub fn resolve_mode_flags(args: &mut ScanArgs) -> Result<(), String> { return Err(format!( "{} cannot be used with --mode hosted: global installs have no project \ lockfile to redirect", - if args.common.global { "--global" } else { "--global-prefix" }, + if args.common.global { + "--global" + } else { + "--global-prefix" + }, )); } if args.detached && args.mode != Some(ScanMode::Vendored) { @@ -396,7 +401,10 @@ async fn embed_vex_human( // Dry-run twin of the JSON guard above: no generation, no file write. if common.dry_run { if !common.silent { - println!("{}", crate::commands::vex::format_vex_dry_run_skip("applied")); + println!( + "{}", + crate::commands::vex::format_vex_dry_run_skip("applied") + ); } return base_code; } @@ -756,7 +764,8 @@ fn overlap_from_states( // machinery blind to exactly that degraded ledger, so fall back to // matching the vendored purls against the recorded edit keys — npm // `node_modules/` (possibly nested), pnpm/yarn/cargo/uv - // `@`, bun `/`, gem/composer/pypi bare + // `@` (vlt `@~` for a peer or + // modifier variant), bun `/`, gem/composer/pypi bare // ``. Name-level matching can over-claim across versions, but the // direction gate in `classify_overlap_takeover` still requires the live // lock to prove one side before anything is reported. @@ -776,6 +785,7 @@ fn overlap_from_states( .any(|key| { key == name || key == format!("{name}@{version}") + || key.starts_with(&format!("{name}@{version}~")) || key.ends_with(&format!("/{name}")) }) }) @@ -1671,7 +1681,11 @@ pub async fn run(mut args: ScanArgs) -> i32 { } else { format!("{excluded_supplements} lockfile-only/vendor-ledger packages have") }, - if excluded_supplements == 1 { "was" } else { "were" }, + if excluded_supplements == 1 { + "was" + } else { + "were" + }, ), )); } @@ -2465,9 +2479,7 @@ pub async fn run(mut args: ScanArgs) -> i32 { // The rule is as wide as the table, but never wraps a terminal. let header = render::table_header(purl_w); - let cap = std::io::stdout() - .is_terminal() - .then(ui::stdout_width); + let cap = std::io::stdout().is_terminal().then(ui::stdout_width); let rule = render::ruler( std::iter::once(header.as_str()).chain(rows.iter().map(String::as_str)), cap, @@ -2708,8 +2720,10 @@ pub async fn run(mut args: ScanArgs) -> i32 { println!("\nPatches to apply:\n"); } for patch in &selected { - let severity = - ui::severity(render::highest_severity(patch).unwrap_or("unknown"), use_color); + let severity = ui::severity( + render::highest_severity(patch).unwrap_or("unknown"), + use_color, + ); // The manifest already records a different patch for this // package: say so, and warn when the new one fixes less. Agent // mode only: vendored mode never writes the manifest. @@ -2841,10 +2855,7 @@ pub async fn run(mut args: ScanArgs) -> i32 { // Download, then apply in place — or vendor (vendored mode, where the // download only saves and the vendor step below does the rest). let params = download_params( - &args, - /*save_only=*/ vendor, - /*json=*/ false, - silent, + &args, /*save_only=*/ vendor, /*json=*/ false, silent, ); let code = if vendor { @@ -3965,6 +3976,31 @@ mod tests { assert!(takeover.redirect.is_empty(), "{takeover:?}"); } + #[tokio::test] + async fn degraded_ledger_matches_a_vlt_variant_key_at_the_tilde_boundary() { + for (key, overlaps) in [ + ("minimist@1.2.2~peer.2", true), + ("minimist@1.2.2~_croot_s_g_s#a", true), + ("minimist@1.2.20~peer.2", false), + ] { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let mut edit = redirect_edit("vlt-lock.json", key); + edit.kind = socket_patch_core::patch::redirect::vlt::KIND.to_string(); + write_redirect_ledger_with_edits(root, &[], vec![edit]).await; + write_vendor_ledger_wired(root, &["pkg:npm/minimist@1.2.2"]).await; + assert_eq!( + overlapping_ledger_purls(root).await, + if overlaps { + vec!["pkg:npm/minimist@1.2.2".to_string()] + } else { + Vec::new() + }, + "{key}" + ); + } + } + #[tokio::test] async fn following_the_vendored_remediation_clears_the_warning() { // Regression (sticky warning): the vendored remediation used to name diff --git a/crates/socket-patch-cli/tests/cli_global_args.rs b/crates/socket-patch-cli/tests/cli_global_args.rs index b0ca583c..e6f5c09a 100644 --- a/crates/socket-patch-cli/tests/cli_global_args.rs +++ b/crates/socket-patch-cli/tests/cli_global_args.rs @@ -114,6 +114,9 @@ fn global_flag_cases() -> Vec<(&'static str, Option<&'static str>, fn(&GlobalArg ("--no-npm-allow-remote-config", None, |c| { assert!(c.no_npm_allow_remote_config) }), + ("--no-vlt-install-cleanup", None, |c| { + assert!(c.no_vlt_install_cleanup) + }), ("--lock-timeout", Some("30"), |c| { assert_eq!(c.lock_timeout, Some(30)) }), @@ -228,17 +231,18 @@ fn global_flag_cases_cover_every_global_field() { no_telemetry: _, no_trust_lockfile_config: _, no_npm_allow_remote_config: _, + no_vlt_install_cleanup: _, strict: _, vendor_source: _, vendor_url: _, patch_server_url: _, } = common; - // 25 fields ↔ 25 long-flag cases. Bump both this count and add a case when + // 26 fields ↔ 26 long-flag cases. Bump both this count and add a case when // the destructure above forces you to add a field. assert_eq!( global_flag_cases().len(), - 25, + 26, "every GlobalArgs field needs a long-flag case in global_flag_cases()", ); @@ -659,7 +663,7 @@ fn bool_env_vars_reject_zero_and_falsey() { #[serial_test::serial] fn empty_bool_env_var_resolves_to_false_not_crash() { // (env var, accessor) for every boolean global. - let bool_vars: [(&str, fn(&GlobalArgs) -> bool); 12] = [ + let bool_vars: [(&str, fn(&GlobalArgs) -> bool); 13] = [ ("SOCKET_OFFLINE", |c| c.offline), ("SOCKET_STRICT", |c| c.strict), ("SOCKET_GLOBAL", |c| c.global), @@ -676,6 +680,9 @@ fn empty_bool_env_var_resolves_to_false_not_crash() { ("SOCKET_NO_NPM_ALLOW_REMOTE_CONFIG", |c| { c.no_npm_allow_remote_config }), + ("SOCKET_NO_VLT_INSTALL_CLEANUP", |c| { + c.no_vlt_install_cleanup + }), ]; let saved = save_and_clear_global_env(); diff --git a/crates/socket-patch-cli/tests/cli_parse_get.rs b/crates/socket-patch-cli/tests/cli_parse_get.rs index 83919aba..43709677 100644 --- a/crates/socket-patch-cli/tests/cli_parse_get.rs +++ b/crates/socket-patch-cli/tests/cli_parse_get.rs @@ -56,6 +56,7 @@ const SOCKET_ENV_VARS: &[&str] = &[ "SOCKET_TELEMETRY_DISABLED", "SOCKET_NO_TRUST_LOCKFILE_CONFIG", "SOCKET_NO_NPM_ALLOW_REMOTE_CONFIG", + "SOCKET_NO_VLT_INSTALL_CLEANUP", // GetArgs-specific "SOCKET_SAVE_ONLY", "SOCKET_ONE_OFF", @@ -682,3 +683,14 @@ fn scrub_covers_every_global_env_var_clap_consults() { ); } } + +#[test] +#[serial_test::serial] +fn no_vlt_install_cleanup_flag_parses_for_hosted_get() { + assert!(!parse_get(&["some-id"]).common.no_vlt_install_cleanup); + assert!( + parse_get(&["some-id", "--mode", "hosted", "--no-vlt-install-cleanup"]) + .common + .no_vlt_install_cleanup + ); +} diff --git a/crates/socket-patch-cli/tests/cli_parse_repair.rs b/crates/socket-patch-cli/tests/cli_parse_repair.rs index 80a943fa..474b6fcb 100644 --- a/crates/socket-patch-cli/tests/cli_parse_repair.rs +++ b/crates/socket-patch-cli/tests/cli_parse_repair.rs @@ -63,6 +63,7 @@ const SOCKET_ENV_VARS: &[&str] = &[ "SOCKET_TELEMETRY_DISABLED", "SOCKET_NO_TRUST_LOCKFILE_CONFIG", "SOCKET_NO_NPM_ALLOW_REMOTE_CONFIG", + "SOCKET_NO_VLT_INSTALL_CLEANUP", // RepairArgs-specific "SOCKET_DOWNLOAD_ONLY", ]; @@ -547,3 +548,14 @@ fn gc_alias_parses_as_repair() { Err(e) => panic!("gc alias should parse: {e}"), } } + +#[test] +#[serial_test::serial] +fn no_vlt_install_cleanup_is_accepted_silently_by_repair() { + assert!(!parse_repair(&[]).common.no_vlt_install_cleanup); + assert!( + parse_repair(&["--no-vlt-install-cleanup"]) + .common + .no_vlt_install_cleanup + ); +} diff --git a/crates/socket-patch-cli/tests/cli_parse_scan.rs b/crates/socket-patch-cli/tests/cli_parse_scan.rs index 4c846c38..5a6f7fe9 100644 --- a/crates/socket-patch-cli/tests/cli_parse_scan.rs +++ b/crates/socket-patch-cli/tests/cli_parse_scan.rs @@ -44,6 +44,7 @@ const SCAN_ENV_VARS: &[&str] = &[ "SOCKET_MANIFEST_PATH", "SOCKET_NO_TRUST_LOCKFILE_CONFIG", "SOCKET_NO_NPM_ALLOW_REMOTE_CONFIG", + "SOCKET_NO_VLT_INSTALL_CLEANUP", "SOCKET_OFFLINE", "SOCKET_ORG_SLUG", "SOCKET_PATCH_SERVER_URL", @@ -935,3 +936,29 @@ fn detached_flag_is_hidden_from_help() { let folded = parse_and_resolve(&["--mode", "vendored", "--detached"]).expect("fold ok"); assert!(folded.detached); } + +#[test] +#[serial_test::serial] +fn no_vlt_install_cleanup_flag_and_env_parse() { + assert!( + !parse_scan(&["--mode", "hosted"]) + .common + .no_vlt_install_cleanup + ); + assert!( + parse_scan(&["--mode", "hosted", "--no-vlt-install-cleanup"]) + .common + .no_vlt_install_cleanup + ); + let from_env = with_clean_env(|| { + std::env::set_var("SOCKET_NO_VLT_INSTALL_CLEANUP", "1"); + let cli = Cli::try_parse_from(["socket-patch", "scan", "--mode", "hosted"]); + std::env::remove_var("SOCKET_NO_VLT_INSTALL_CLEANUP"); + cli + }) + .expect("parse"); + match from_env.command { + Commands::Scan(a) => assert!(a.common.no_vlt_install_cleanup), + _ => panic!("expected Scan"), + } +} diff --git a/crates/socket-patch-cli/tests/cli_parse_vendor.rs b/crates/socket-patch-cli/tests/cli_parse_vendor.rs index baf7c6e0..9106903c 100644 --- a/crates/socket-patch-cli/tests/cli_parse_vendor.rs +++ b/crates/socket-patch-cli/tests/cli_parse_vendor.rs @@ -60,6 +60,7 @@ const SOCKET_ENV_VARS: &[&str] = &[ "SOCKET_TELEMETRY_DISABLED", "SOCKET_NO_TRUST_LOCKFILE_CONFIG", "SOCKET_NO_NPM_ALLOW_REMOTE_CONFIG", + "SOCKET_NO_VLT_INSTALL_CLEANUP", // VendorArgs-specific "SOCKET_FORCE", "SOCKET_VENDOR_REVERT", @@ -631,3 +632,16 @@ fn bare_force_does_not_consume_next_token() { Err(err) => assert_eq!(err.kind(), clap::error::ErrorKind::UnknownArgument), } } + +#[test] +#[serial_test::serial] +fn no_vlt_install_cleanup_is_accepted_silently_by_vendor() { + assert!( + parse_vendor(&["--no-vlt-install-cleanup"]) + .common + .no_vlt_install_cleanup + ); + let from_env = + parse_vendor_with_env(&[("SOCKET_NO_VLT_INSTALL_CLEANUP", "true")], &[]).expect("parse"); + assert!(from_env.common.no_vlt_install_cleanup); +} diff --git a/crates/socket-patch-cli/tests/cli_parse_vex.rs b/crates/socket-patch-cli/tests/cli_parse_vex.rs index 03276e8e..d7d65fd2 100644 --- a/crates/socket-patch-cli/tests/cli_parse_vex.rs +++ b/crates/socket-patch-cli/tests/cli_parse_vex.rs @@ -59,6 +59,7 @@ const SOCKET_ENV_VARS: &[&str] = &[ "SOCKET_TELEMETRY_DISABLED", "SOCKET_NO_TRUST_LOCKFILE_CONFIG", "SOCKET_NO_NPM_ALLOW_REMOTE_CONFIG", + "SOCKET_NO_VLT_INSTALL_CLEANUP", // VexArgs / VexEmbedArgs "SOCKET_VEX", "SOCKET_VEX_OUTPUT", @@ -217,6 +218,7 @@ struct Snap { no_telemetry: bool, no_trust_lockfile_config: bool, no_npm_allow_remote_config: bool, + no_vlt_install_cleanup: bool, output: Option, product: Option, no_verify: bool, @@ -251,6 +253,7 @@ fn snapshot(a: &VexArgs) -> Snap { no_telemetry: a.common.no_telemetry, no_trust_lockfile_config: a.common.no_trust_lockfile_config, no_npm_allow_remote_config: a.common.no_npm_allow_remote_config, + no_vlt_install_cleanup: a.common.no_vlt_install_cleanup, output: a.output.clone(), product: a.product.clone(), no_verify: a.no_verify, @@ -292,6 +295,7 @@ fn expected_defaults() -> Snap { no_telemetry: false, no_trust_lockfile_config: false, no_npm_allow_remote_config: false, + no_vlt_install_cleanup: false, output: None, product: None, no_verify: false, @@ -356,3 +360,18 @@ fn bare_flags_still_parse_without_env() { _ => panic!("expected Vex"), } } + +#[test] +#[serial_test::serial] +fn no_vlt_install_cleanup_env_is_accepted_silently_by_vex() { + let cli = parse_with_env( + "SOCKET_NO_VLT_INSTALL_CLEANUP", + "1", + &["socket-patch", "vex"], + ) + .expect("SOCKET_NO_VLT_INSTALL_CLEANUP=1 must parse on vex"); + match cli.command { + Commands::Vex(a) => assert!(a.common.no_vlt_install_cleanup), + _ => panic!("expected Vex"), + } +} diff --git a/crates/socket-patch-cli/tests/covgap_commands_rollback.rs b/crates/socket-patch-cli/tests/covgap_commands_rollback.rs index d9ecc92b..185edeef 100644 --- a/crates/socket-patch-cli/tests/covgap_commands_rollback.rs +++ b/crates/socket-patch-cli/tests/covgap_commands_rollback.rs @@ -2218,9 +2218,8 @@ mod interactive { .expect("spawn socket-patch in PTY"); drop(pair.slave); - let reader_handle = crate::pty_io::PtyOutput::spawn( - pair.master.try_clone_reader().expect("clone reader"), - ); + let reader_handle = + crate::pty_io::PtyOutput::spawn(pair.master.try_clone_reader().expect("clone reader")); let mut killer = child.clone_killer(); std::thread::spawn(move || { @@ -2270,9 +2269,7 @@ mod interactive { ); assert_eq!(code, 0, "declining must exit 0; got: {output}"); assert!( - output.contains( - "Roll back 1 patch and remove it from the local manifest? [Y/n]" - ), + output.contains("Roll back 1 patch and remove it from the local manifest? [Y/n]"), "the composed confirm prompt must render verbatim; got: {output}" ); assert!( @@ -3345,3 +3342,106 @@ fn empty_manifest_announces_no_patches() { "the empty-manifest announce must print; stdout=\n{stdout}\nstderr=\n{stderr}" ); } + +// ───────────────────────── hosted vlt heal ───────────────────────── + +const VLT_UUID: &str = "88888888-8888-4888-8888-888888888888"; +const VLT_ID: &str = "~npm~left-pad@1.3.0"; +const VLT_REGISTRY_SHA: &str = "sha512-REGISTRY=="; +const VLT_PATCHED_SHA: &str = "sha512-PATCHED=="; + +fn vlt_entry(sha: &str, url: &str) -> String { + format!("\"{VLT_ID}\": [0,\"left-pad\",\"{sha}\",\"{url}\"]") +} + +fn vlt_lock_text(entry: &str) -> String { + format!( + "{{\n \"lockfileVersion\": 1,\n \"options\": {{}},\n \"nodes\": {{\n {entry}\n }},\n \"edges\": {{}}\n}}\n" + ) +} + +/// A redirected vlt project whose store holds the patched copy `vlt +/// install` extracted, with the hidden lock recording the hosted pin. +fn write_vlt_hosted_fixture(root: &Path) -> (String, PathBuf) { + let registry = vlt_entry( + VLT_REGISTRY_SHA, + "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + ); + let hosted = vlt_entry( + VLT_PATCHED_SHA, + &format!("https://patch.socket.dev/patch/npm/t/{VLT_UUID}/left-pad-1.3.0.tgz"), + ); + std::fs::write(root.join("vlt-lock.json"), vlt_lock_text(&hosted)).unwrap(); + let store = root + .join("node_modules/.vlt") + .join(VLT_ID) + .join("node_modules/left-pad"); + std::fs::create_dir_all(&store).unwrap(); + std::fs::write(store.join("index.js"), b"patched").unwrap(); + std::fs::write( + root.join("node_modules/.vlt-lock.json"), + vlt_lock_text(&hosted), + ) + .unwrap(); + let mut record = hosted_record(VLT_UUID); + record.files.insert( + "package/index.js".to_string(), + PatchFileInfo { + before_hash: git_sha256(b"pristine"), + after_hash: git_sha256(b"patched"), + }, + ); + write_hosted_ledger( + root, + vec![("pkg:npm/left-pad@1.3.0", record)], + vec![FileEdit { + path: "vlt-lock.json".to_string(), + kind: "redirect_vlt_lock_node".to_string(), + action: "rewritten".to_string(), + key: Some("left-pad@1.3.0".to_string()), + original: Some(json!(registry)), + new: Some(json!(hosted)), + }], + ); + (vlt_lock_text(®istry), store) +} + +/// A dry-run rollback previews the vlt unwind but deletes nothing and +/// says nothing about installed copies; the wet human run restores the +/// registry pin, invalidates the patched store entry and prints the +/// advisory as a warning line. +#[test] +fn vlt_hosted_rollback_dry_run_keeps_the_store_and_wet_human_run_heals() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let (registry_lock, store) = write_vlt_hosted_fixture(root); + let hosted_lock = std::fs::read_to_string(root.join("vlt-lock.json")).unwrap(); + + let (code, stdout, stderr) = run(root, &["rollback", "--dry-run", "--yes", "--offline"]); + assert_eq!(code, 0, "{stdout}\n{stderr}"); + assert!( + !stderr.contains("redirect_vlt_reinstall_required"), + "{stderr}" + ); + assert_eq!( + std::fs::read_to_string(root.join("vlt-lock.json")).unwrap(), + hosted_lock + ); + assert!(store.join("index.js").exists()); + + let (code, stdout, stderr) = run(root, &["rollback", "--yes", "--offline"]); + assert_eq!(code, 0, "{stdout}\n{stderr}"); + assert_eq!( + std::fs::read_to_string(root.join("vlt-lock.json")).unwrap(), + registry_lock + ); + assert!( + stderr.contains( + "Warning (redirect_vlt_reinstall_required): restored registry pins for 1 packages; \ + removed the patched installed copies" + ), + "{stderr}" + ); + assert!(!store.exists()); + assert!(!root.join("node_modules/.vlt-lock.json").exists()); +} diff --git a/crates/socket-patch-cli/tests/covgap_commands_scan_hosted.rs b/crates/socket-patch-cli/tests/covgap_commands_scan_hosted.rs index a90a6113..b99cb22a 100644 --- a/crates/socket-patch-cli/tests/covgap_commands_scan_hosted.rs +++ b/crates/socket-patch-cli/tests/covgap_commands_scan_hosted.rs @@ -2493,3 +2493,132 @@ async fn human_pnpm_rerun_prints_only_the_reminder_and_heal_restores_guidance() "the heal run prints the full guidance again; stderr=\n{stderr}" ); } + +// ───────────────────────────── vlt ───────────────────────────── + +/// A vendored vlt entry is never reverted for a hosted takeover the vlt +/// rewriter would then refuse: the lock-level refusal (here a BOM) is known +/// first, the purl is skipped with that code, and the vendored ledger and +/// the lock stay byte-identical. +#[tokio::test] +async fn vlt_takeover_refusal_before_revert() { + let server = MockServer::start().await; + mock_discovery(&server, PURL, UUID).await; + mock_granted_reference(&server, UUID, PURL, HOSTED_URL).await; + + let tmp = tempfile::tempdir().unwrap(); + write_npm_project(tmp.path(), NAME); + std::fs::remove_file(tmp.path().join("package-lock.json")).unwrap(); + let lock = format!( + "\u{feff}{{\n \"lockfileVersion\": 1,\n \"options\": {{}},\n \"nodes\": {{\n \ + \"~npm~{NAME}@{VERSION}\": [0,\"{NAME}\",\"{UPSTREAM_SHA512}\",\"https://registry.npmjs.org/{NAME}/-/{NAME}-{VERSION}.tgz\"]\n }},\n \"edges\": {{}}\n}}\n" + ); + std::fs::write(tmp.path().join("vlt-lock.json"), &lock).unwrap(); + write_vendor_state(tmp.path(), PURL, UUID, "vlt"); + let state_before = std::fs::read(tmp.path().join(".socket/vendor/state.json")).unwrap(); + + let (code, doc) = scan_hosted_json(tmp.path(), &server.uri(), &[], &[]); + + assert_eq!(code, 0, "{doc:#}"); + assert_eq!(doc["redirect"]["redirected"], 0, "{doc:#}"); + assert!( + doc["redirect"]["skipped"].as_array().is_some_and(|s| s + .iter() + .any(|e| e["purl"] == PURL && e["reason"] == "redirect_vlt_lock_unsupported")), + "{doc:#}" + ); + assert!(warning_detail(&doc, "redirect_vlt_lock_unsupported").contains("BOM")); + assert!(!warning_codes(&doc).contains(&"redirect_vendored_revert_failed".to_string())); + assert_eq!( + std::fs::read(tmp.path().join(".socket/vendor/state.json")).unwrap(), + state_before + ); + assert_eq!( + std::fs::read_to_string(tmp.path().join("vlt-lock.json")).unwrap(), + lock + ); +} + +/// With `bun.lockb` beside a vlt-driven `vlt-lock.json`, the vlt rules +/// decide npm confirmation before the binary-bun rule: the binary rewrite +/// lands, but the vlt rewriter refused the dep, so it is not confirmed. +#[tokio::test] +async fn vlt_decides_before_binary_bun_and_a_refused_uuid_is_never_confirmed() { + use base64::Engine as _; + use sha2::Digest as _; + let purl = "pkg:npm/minimist@1.2.2"; + let body = b"minimist tarball".to_vec(); + let sri = format!( + "sha512-{}", + base64::engine::general_purpose::STANDARD.encode(sha2::Sha512::digest(&body)) + ); + let server = MockServer::start().await; + let url = format!("{}/patch/npm/minimist-1.2.2.tgz", server.uri()); + mock_discovery(&server, purl, UUID).await; + mock_reference_results( + &server, + json!({ UUID: { + "status": "granted", "url": url, "purl": purl, + "artifacts": [{"kind": "tarball", "url": url, "integrity": {"sha512": sri}}], + "registryOverride": null, + }}), + ) + .await; + mock_view(&server, UUID, purl).await; + Mock::given(method("GET")) + .and(path("/patch/npm/minimist-1.2.2.tgz")) + .respond_with(ResponseTemplate::new(200).set_body_bytes(body)) + .mount(&server) + .await; + + let run = |off_grammar: bool| { + let tmp = tempfile::tempdir().unwrap(); + let original = + include_bytes!("../../socket-patch-core/tests/fixtures/bun-lockb/1.1.45/bun.lockb"); + std::fs::write(tmp.path().join("bun.lockb"), original).unwrap(); + std::fs::write( + tmp.path().join("package.json"), + include_bytes!("../../socket-patch-core/tests/fixtures/bun-lockb/1.1.45/package.json"), + ) + .unwrap(); + let space = if off_grammar { " " } else { "" }; + std::fs::write( + tmp.path().join("vlt-lock.json"), + format!( + "{{\n \"lockfileVersion\": 1,\n \"options\": {{}},\n \"nodes\": {{\n \ + \"~npm~is-number@7.0.0\": [0,\"is-number\"],\n \ + \"~npm~minimist@1.2.2\": [0,{space}\"minimist\",\"{UPSTREAM_SHA512}\",\"https://registry.npmjs.org/minimist/-/minimist-1.2.2.tgz\"]\n }},\n \"edges\": {{}}\n}}\n" + ), + ) + .unwrap(); + let pkg = tmp.path().join("node_modules/minimist"); + std::fs::create_dir_all(&pkg).unwrap(); + std::fs::write( + pkg.join("package.json"), + r#"{ "name": "minimist", "version": "1.2.2" }"#, + ) + .unwrap(); + std::fs::create_dir_all(tmp.path().join("node_modules/.vlt")).unwrap(); + let (code, doc) = scan_hosted_json(tmp.path(), &server.uri(), &[], &[("PATH", "")]); + assert_eq!(code, 0, "{doc:#}"); + (doc, tmp) + }; + + let (refused, tmp) = run(true); + assert!( + warning_codes(&refused).contains(&"redirect_vlt_unsupported_lock_key".to_string()), + "{refused:#}" + ); + assert!( + refused["redirect"]["rewrittenFiles"] + .as_array() + .unwrap() + .contains(&json!("bun.lockb")), + "the binary lock is still rewritten: {refused:#}" + ); + assert_eq!(refused["redirect"]["redirected"], 0, "{refused:#}"); + drop(tmp); + + let (confirmed, _tmp) = run(false); + assert_eq!(confirmed["redirect"]["redirected"], 1, "{confirmed:#}"); +} diff --git a/crates/socket-patch-cli/tests/hosted_symlinked_files.rs b/crates/socket-patch-cli/tests/hosted_symlinked_files.rs index 7bf241a0..c58107ca 100644 --- a/crates/socket-patch-cli/tests/hosted_symlinked_files.rs +++ b/crates/socket-patch-cli/tests/hosted_symlinked_files.rs @@ -30,6 +30,8 @@ use wiremock::{Mock, MockServer, ResponseTemplate}; #[path = "common/mod.rs"] mod common; +#[path = "vlt_hosted_common/mod.rs"] +mod vlt_hosted_common; const ORG: &str = "test-org"; const CODE: &str = "redirect_symlinked_file_unsupported"; @@ -166,7 +168,9 @@ fn build_wheel(name: &str, version: &str) -> (Vec, String) { .start_file(format!("{name}-{version}.dist-info/METADATA"), opts) .unwrap(); writer - .write_all(format!("Metadata-Version: 2.1\nName: {name}\nVersion: {version}\n\n").as_bytes()) + .write_all( + format!("Metadata-Version: 2.1\nName: {name}\nVersion: {version}\n\n").as_bytes(), + ) .unwrap(); writer.finish().unwrap(); } @@ -257,7 +261,10 @@ fn symlink_away(root: &Path, shared: &Path, name: &str) -> std::path::PathBuf { std::fs::create_dir_all(shared).unwrap(); let target = shared.join(name); std::fs::rename(root.join(name), &target).unwrap(); - let rel = format!("../{}/{name}", shared.file_name().unwrap().to_str().unwrap()); + let rel = format!( + "../{}/{name}", + shared.file_name().unwrap().to_str().unwrap() + ); std::os::unix::fs::symlink(&rel, root.join(name)).unwrap(); assert!(is_symlink(&root.join(name)) && root.join(name).exists()); target @@ -317,7 +324,10 @@ fn assert_refused_untouched( target: &Path, target_before: &[u8], ) { - assert_eq!(code, 1, "a symlinked rewrite target must fail the run: {doc:#}"); + assert_eq!( + code, 1, + "a symlinked rewrite target must fail the run: {doc:#}" + ); assert_eq!(doc["status"], "error", "{doc:#}"); assert_eq!(doc["errorCode"], CODE, "{doc:#}"); let message = doc["error"].as_str().unwrap_or_else(|| panic!("{doc:#}")); @@ -459,11 +469,9 @@ async fn hosted_rewrites_the_same_lock_once_it_is_a_regular_file() { let (code, doc, stderr) = scan_hosted_json(&root, &server.uri()); assert_eq!(code, 0, "{doc:#}\n{stderr}"); assert_eq!(doc["redirect"]["redirected"], 1, "{doc:#}"); - assert!( - std::fs::read_to_string(root.join("package-lock.json")) - .unwrap() - .contains(NPM_HOSTED_URL) - ); + assert!(std::fs::read_to_string(root.join("package-lock.json")) + .unwrap() + .contains(NPM_HOSTED_URL)); assert!(root.join(LEDGER_REL).exists()); } @@ -541,3 +549,60 @@ async fn hosted_scan_returns_with_fifo_candidate() { "the FIFO must not be replaced by a rewrite" ); } + +/// vlt: `vlt-lock.json` is in the rewrite set, so a symlinked lock is +/// refused by the same guard, before the ledger and before any write. +#[tokio::test] +async fn hosted_refuses_symlinked_vlt_lock_before_ledger_write() { + use vlt_hosted_common as vlt; + let server = MockServer::start().await; + vlt::mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path().join("proj"); + std::fs::create_dir_all(&root).unwrap(); + vlt::write_vlt_project(&root, vlt::Era::V1); + let target = symlink_away(&root, &tmp.path().join("shared"), "vlt-lock.json"); + let lock_before = std::fs::read(&target).unwrap(); + + let (code, doc, _stderr) = scan_hosted_json(&root, &server.uri()); + + assert_refused_untouched(code, &doc, &root, "vlt-lock.json", &target, &lock_before); +} + +/// A FIFO planted as `vlt-lock.json` is skipped by the preflight read and +/// the candidate read alike: the scan returns and never probes an artifact. +#[tokio::test] +async fn hosted_scan_returns_with_fifo_vlt_lock() { + use vlt_hosted_common as vlt; + let server = MockServer::start().await; + vlt::mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path().join("proj"); + std::fs::create_dir_all(&root).unwrap(); + vlt::write_vlt_project(&root, vlt::Era::V1); + let fifo = root.join("vlt-lock.json"); + std::fs::remove_file(&fifo).unwrap(); + mkfifo(&fifo); + + let (tx, rx) = std::sync::mpsc::channel(); + let api = server.uri(); + let run_root = root.clone(); + std::thread::spawn(move || { + let _ = tx.send(scan_hosted(&run_root, &api, &["--json"])); + }); + let deadline = std::time::Duration::from_secs(90); + let Ok((code, stdout, stderr)) = rx.recv_timeout(deadline) else { + use std::os::unix::fs::OpenOptionsExt as _; + let _ = std::fs::OpenOptions::new() + .write(true) + .custom_flags(libc::O_NONBLOCK) + .open(&fifo); + panic!("scan --mode hosted wedged on a FIFO vlt-lock.json for {deadline:?}"); + }; + assert_eq!(code, 0, "{stdout}\n{stderr}"); + let doc: Value = serde_json::from_str(&stdout).unwrap(); + assert_eq!(doc["redirect"]["redirected"], 0, "{doc:#}"); + assert_eq!(vlt::artifact_requests(&server).await, 0); + let meta = std::fs::symlink_metadata(&fifo).unwrap(); + assert!(std::os::unix::fs::FileTypeExt::is_fifo(&meta.file_type())); +} diff --git a/crates/socket-patch-cli/tests/in_process_get_hosted_ecosystems.rs b/crates/socket-patch-cli/tests/in_process_get_hosted_ecosystems.rs index 250e6345..1a8c2a21 100644 --- a/crates/socket-patch-cli/tests/in_process_get_hosted_ecosystems.rs +++ b/crates/socket-patch-cli/tests/in_process_get_hosted_ecosystems.rs @@ -26,6 +26,8 @@ use std::path::Path; mod vex_e2e_common; #[path = "vex_pipenv_pip_steps/mod.rs"] mod vex_pipenv_pip_steps; +#[path = "vlt_hosted_common/mod.rs"] +mod vlt_hosted_common; use serial_test::serial; use socket_patch_cli::commands::get::GetArgs; @@ -957,3 +959,63 @@ async fn deno_hosted_grant_lands_nothing() { .unwrap(); } } + +/// vlt: `get --mode hosted` over a lock-only vlt project pins the +/// node (UUID identifiers skip installed narrowing), preflights the +/// artifact, heals a warm store and emits the reinstall advisory. +#[tokio::test] +async fn vlt_hosted_get_pins_the_node_heals_and_emits_the_advisory() { + use vlt_hosted_common as vlt; + let server = MockServer::start().await; + vlt::mock_all(&server).await; + for warm in [false, true] { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + vlt::write_package_json(root); + std::fs::write( + root.join("vlt-lock.json"), + vlt::vlt_lock(vlt::Era::V1, &[vlt::registry_node(vlt::TILDE_ID)]), + ) + .unwrap(); + if warm { + vlt::install_store(root, vlt::TILDE_ID, vlt::PRISTINE); + vlt::write_hidden_lock(root, &[vlt::registry_node(vlt::TILDE_ID)]); + } + let cwd = root.to_str().unwrap().to_string(); + let uri = server.uri(); + + let (code, doc, stderr) = vlt::run_json( + root, + &[ + "get", + vlt::UUID, + "--mode", + "hosted", + "--yes", + "--cwd", + &cwd, + "--api-url", + &uri, + "--org", + vlt::ORG, + "--api-token", + "fake", + ], + &[], + ); + + assert_eq!(code, 0, "{doc:#}\n{stderr}"); + assert_eq!(vlt::redirected(&doc), 1, "{doc:#}"); + assert_eq!( + vlt::read(root, "vlt-lock.json"), + vlt::vlt_lock(vlt::Era::V1, &[vlt::pinned_node(vlt::TILDE_ID, &server)]) + ); + let expected = if warm { + vlt::advisory_invalidated(1) + } else { + vlt::ADVISORY_NOTHING_STALE.to_string() + }; + assert_eq!(vlt::warning_detail(&doc, vlt::ADVISORY), expected); + assert!(!vlt::store_dir(root, vlt::TILDE_ID).exists()); + } +} diff --git a/crates/socket-patch-cli/tests/in_process_redirect.rs b/crates/socket-patch-cli/tests/in_process_redirect.rs index abdb014e..8fd40598 100644 --- a/crates/socket-patch-cli/tests/in_process_redirect.rs +++ b/crates/socket-patch-cli/tests/in_process_redirect.rs @@ -27,6 +27,10 @@ mod vex_e2e_common; #[allow(clippy::duplicate_mod)] #[path = "vex_e2e_common/bun.rs"] mod bun_vex; +#[path = "in_process_redirect/vlt.rs"] +mod vlt; +#[path = "vlt_hosted_common/mod.rs"] +mod vlt_hosted_common; const ORG: &str = "test-org"; const NAME: &str = "in-proc-redirect"; diff --git a/crates/socket-patch-cli/tests/in_process_redirect/vlt.rs b/crates/socket-patch-cli/tests/in_process_redirect/vlt.rs new file mode 100644 index 00000000..b66c17b0 --- /dev/null +++ b/crates/socket-patch-cli/tests/in_process_redirect/vlt.rs @@ -0,0 +1,1057 @@ +//! Hosted vlt legs (DESIGN §3, §8.2): the rewrite through the CLI, the +//! artifact preflight, the warm-tree heal with its advisory, and the in-run +//! VEX exclusion. Every run is the scrubbed subprocess binary, so the +//! `--json` envelope is read back and no parent env is mutated. + +use std::path::Path; + +use serde_json::Value; +use wiremock::matchers::{method, path}; +use wiremock::{Mock, MockServer, ResponseTemplate}; + +use crate::vlt_hosted_common::*; + +fn ledger(root: &Path) -> Value { + serde_json::from_str(&read(root, ".socket/vendor/redirect-state.json")).unwrap() +} + +fn vlt_edit_keys(root: &Path) -> Vec { + ledger(root)["edits"] + .as_array() + .into_iter() + .flatten() + .filter(|e| e["kind"] == "redirect_vlt_lock_node") + .map(|e| e["key"].as_str().unwrap().to_string()) + .collect() +} + +fn skipped_reasons(doc: &Value) -> Vec { + doc["redirect"]["skipped"] + .as_array() + .into_iter() + .flatten() + .filter_map(|s| s["reason"].as_str().map(str::to_string)) + .collect() +} + +fn lock_with(era: Era, nodes: &[String]) -> String { + vlt_lock(era, nodes) +} + +async fn assert_rewrites(era: Era, expected_warnings: &[&str]) { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), era); + + let (_, doc) = scan_hosted(tmp.path(), &server, &[], &[]); + + assert_eq!(redirected(&doc), 1, "{doc:#}"); + assert_eq!( + read(tmp.path(), "vlt-lock.json"), + lock_with(era, &[pinned_node(era.dep_id(), &server)]) + ); + assert_eq!( + doc["redirect"]["rewrittenFiles"], + serde_json::json!(["vlt-lock.json"]) + ); + assert_eq!(warning_codes(&doc), expected_warnings, "{doc:#}"); + assert_eq!(warning_detail(&doc, ADVISORY), ADVISORY_NOTHING_STALE); + assert_eq!(vlt_edit_keys(tmp.path()), ["left-pad@1.3.0"]); + assert!(ledger(tmp.path())["records"][PURL].is_object()); + assert_eq!(artifact_requests(&server).await, 1); +} + +#[tokio::test] +async fn scan_redirect_rewrites_vlt_lock_v1() { + assert_rewrites(Era::V1, &[ADVISORY]).await; +} + +#[tokio::test] +async fn scan_redirect_rewrites_vlt_lock_v0() { + assert_rewrites(Era::V0, &[ADVISORY]).await; +} + +#[tokio::test] +async fn scan_redirect_rewrites_vlt_lock_a0() { + assert_rewrites( + Era::A0, + &[ + "redirect_vlt_lockfile_version_missing", + "redirect_vlt_old_lockfile_ignored", + ADVISORY, + ], + ) + .await; +} + +#[tokio::test] +async fn scan_redirect_refuses_vlt_lock_v2() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + let v2 = read(tmp.path(), "vlt-lock.json") + .replace("\"lockfileVersion\": 1", "\"lockfileVersion\": 2"); + std::fs::write(tmp.path().join("vlt-lock.json"), &v2).unwrap(); + + let (_, doc) = scan_hosted(tmp.path(), &server, &[], &[]); + + assert_eq!(redirected(&doc), 0); + assert_eq!(warning_codes(&doc), ["redirect_vlt_lock_unsupported"]); + assert_eq!(read(tmp.path(), "vlt-lock.json"), v2); + assert_eq!(artifact_requests(&server).await, 0); + assert!(!ledger_path(tmp.path()).exists()); +} + +#[tokio::test] +async fn scan_redirect_vlt_rerun_noop_keeps_ledger() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + scan_hosted(tmp.path(), &server, &[], &[]); + let lock = read(tmp.path(), "vlt-lock.json"); + let ledger_bytes = read(tmp.path(), ".socket/vendor/redirect-state.json"); + + let (_, doc) = scan_hosted(tmp.path(), &server, &[], &[]); + + assert_eq!( + redirected(&doc), + 1, + "a pinned lock stays confirmed: {doc:#}" + ); + assert_eq!(read(tmp.path(), "vlt-lock.json"), lock); + assert_eq!( + read(tmp.path(), ".socket/vendor/redirect-state.json"), + ledger_bytes + ); + assert_eq!(vlt_edit_keys(tmp.path()), ["left-pad@1.3.0"]); +} + +#[tokio::test] +async fn scan_redirect_vlt_crlf() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + let other = "\"~npm~ms@2.1.3\": [0,\"ms\"]".to_string(); + let crlf = lock_with(Era::V1, &[registry_node(TILDE_ID), other.clone()]).replace('\n', "\r\n"); + std::fs::write(tmp.path().join("vlt-lock.json"), &crlf).unwrap(); + + let (_, doc) = scan_hosted(tmp.path(), &server, &[], &[]); + + assert_eq!(redirected(&doc), 1); + assert_eq!( + read(tmp.path(), "vlt-lock.json"), + lock_with(Era::V1, &[pinned_node(TILDE_ID, &server), other]).replace('\n', "\r\n") + ); + let original = ledger(tmp.path())["edits"][0]["original"] + .as_str() + .unwrap() + .to_string(); + assert!( + !original.contains('\r') && !original.ends_with(','), + "{original:?}" + ); +} + +#[tokio::test] +async fn scan_redirect_vlt_peer_instances() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + let peer = "~npm~left-pad@1.3.0~peer.2"; + std::fs::write( + tmp.path().join("vlt-lock.json"), + lock_with(Era::V1, &[registry_node(TILDE_ID), registry_node(peer)]), + ) + .unwrap(); + + let (_, doc) = scan_hosted(tmp.path(), &server, &[], &[]); + + assert_eq!(redirected(&doc), 1); + assert_eq!( + read(tmp.path(), "vlt-lock.json"), + lock_with( + Era::V1, + &[pinned_node(TILDE_ID, &server), pinned_node(peer, &server)] + ) + ); + assert_eq!( + vlt_edit_keys(tmp.path()), + ["left-pad@1.3.0", "left-pad@1.3.0~peer.2"] + ); + assert_eq!( + artifact_requests(&server).await, + 1, + "one GET per distinct artifact URL" + ); +} + +#[tokio::test] +async fn scan_redirect_vlt_sibling_package_lock_ambiguous() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + std::fs::write(tmp.path().join("package-lock.json"), package_lock()).unwrap(); + + let (_, doc) = scan_hosted(tmp.path(), &server, &["--no-npm-allow-remote-config"], &[]); + + assert_eq!(redirected(&doc), 1); + assert!(warning_codes(&doc).contains(&"redirect_vlt_sibling_lockfiles".to_string())); + assert!(read(tmp.path(), "package-lock.json").contains(&artifact_url(&server))); + assert_eq!( + read(tmp.path(), "vlt-lock.json"), + lock_with(Era::V1, &[pinned_node(TILDE_ID, &server)]) + ); +} + +#[tokio::test] +async fn scan_redirect_vlt_sibling_package_lock_vlt_installed_does_not_confirm_refused() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + std::fs::write(tmp.path().join("package-lock.json"), package_lock()).unwrap(); + let off_grammar = + format!("\"{TILDE_ID}\": [0, \"{NAME}\", \"{UPSTREAM_SHA512}\", \"{REGISTRY_URL}\"]"); + let lock = lock_with( + Era::V1, + &["\"~npm~ms@2.1.3\": [0,\"ms\"]".to_string(), off_grammar], + ); + std::fs::write(tmp.path().join("vlt-lock.json"), &lock).unwrap(); + write_hidden_lock(tmp.path(), &[]); + + let (_, doc) = scan_hosted(tmp.path(), &server, &["--no-npm-allow-remote-config"], &[]); + + assert_eq!( + redirected(&doc), + 0, + "vlt drives and refused the dep: the package-lock rewrite confirms nothing: {doc:#}" + ); + assert!(warning_codes(&doc).contains(&"redirect_vlt_unsupported_lock_key".to_string())); + assert_eq!(read(tmp.path(), "vlt-lock.json"), lock); + assert!(!ledger_path(tmp.path()).exists() || ledger(tmp.path())["records"][PURL].is_null()); +} + +// ── artifact preflight ─────────────────────────────────────────────────── + +async fn assert_preflight_refuses(response: ResponseTemplate, reason: &str) { + let server = MockServer::start().await; + mock_discovery(&server).await; + mock_reference(&server).await; + mock_view(&server).await; + mock_artifact_with(&server, response).await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + let lock = read(tmp.path(), "vlt-lock.json"); + + let (_, doc) = scan_hosted(tmp.path(), &server, &[], &[]); + + assert_eq!(redirected(&doc), 0, "{doc:#}"); + assert_eq!(read(tmp.path(), "vlt-lock.json"), lock); + assert!(!ledger_path(tmp.path()).exists(), "nothing is written"); + assert_eq!( + warning_detail(&doc, UNVERIFIABLE), + format!( + "vlt would fail to verify {}: {reason}; nothing was written for {PURL}", + artifact_url(&server) + ) + ); + assert_eq!(skipped_reasons(&doc), [UNVERIFIABLE]); + assert!(!warning_codes(&doc).contains(&ADVISORY.to_string())); +} + +#[tokio::test] +async fn scan_redirect_vlt_artifact_content_encoding_refused() { + assert_preflight_refuses( + ResponseTemplate::new(200) + .insert_header("content-encoding", "gzip") + .set_body_bytes(gzip(&patched_tarball())), + "content-encoding gzip", + ) + .await; +} + +#[tokio::test] +async fn scan_redirect_vlt_artifact_identity_passes() { + let server = MockServer::start().await; + mock_discovery(&server).await; + mock_reference(&server).await; + mock_view(&server).await; + mock_artifact_with( + &server, + ResponseTemplate::new(200) + .insert_header("content-encoding", "identity") + .set_body_bytes(patched_tarball()), + ) + .await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + + let (_, doc) = scan_hosted(tmp.path(), &server, &[], &[]); + + assert_eq!(redirected(&doc), 1); + assert!(!warning_codes(&doc).contains(&UNVERIFIABLE.to_string())); +} + +#[tokio::test] +async fn scan_redirect_vlt_artifact_sha512_mismatch() { + assert_preflight_refuses( + ResponseTemplate::new(200).set_body_bytes(b"other bytes".to_vec()), + "sha512 mismatch", + ) + .await; +} + +#[tokio::test] +async fn scan_redirect_vlt_artifact_http_404() { + assert_preflight_refuses(ResponseTemplate::new(404), "http 404").await; +} + +#[tokio::test] +async fn scan_redirect_vlt_artifact_http_500() { + assert_preflight_refuses(ResponseTemplate::new(500), "http 500").await; +} + +#[tokio::test] +async fn scan_redirect_vlt_artifact_fetch_error() { + let server = MockServer::start().await; + mock_discovery(&server).await; + let url = format!("http://127.0.0.1:9{}", artifact_path()); + mock_reference_at(&server, &url, &sha512_sri(&patched_tarball())).await; + mock_view(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + let lock = read(tmp.path(), "vlt-lock.json"); + + let (_, doc) = scan_hosted(tmp.path(), &server, &[], &[]); + + assert_eq!(redirected(&doc), 0); + assert_eq!(read(tmp.path(), "vlt-lock.json"), lock); + let detail = warning_detail(&doc, UNVERIFIABLE); + assert!( + detail.starts_with(&format!("vlt would fail to verify {url}: fetch error ")) + && detail.ends_with(&format!("; nothing was written for {PURL}")), + "{detail}" + ); +} + +async fn redirect_chain(hops: usize) -> (Value, tempfile::TempDir) { + let server = MockServer::start().await; + mock_discovery(&server).await; + mock_view(&server).await; + for hop in 0..hops { + Mock::given(method("GET")) + .and(path(format!("/hop/{hop}"))) + .respond_with( + ResponseTemplate::new(302) + .insert_header("location", format!("{}/hop/{}", server.uri(), hop + 1)), + ) + .mount(&server) + .await; + } + Mock::given(method("GET")) + .and(path(format!("/hop/{hops}"))) + .respond_with(ResponseTemplate::new(200).set_body_bytes(patched_tarball())) + .mount(&server) + .await; + mock_reference_at( + &server, + &format!("{}/hop/0", server.uri()), + &sha512_sri(&patched_tarball()), + ) + .await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + let (_, doc) = scan_hosted(tmp.path(), &server, &[], &[]); + (doc, tmp) +} + +#[tokio::test] +async fn scan_redirect_vlt_artifact_redirect_chain_10_passes() { + let (doc, _tmp) = redirect_chain(10).await; + assert_eq!(redirected(&doc), 1, "{doc:#}"); +} + +#[tokio::test] +async fn scan_redirect_vlt_artifact_redirect_chain_11_fails() { + let (doc, _tmp) = redirect_chain(11).await; + assert_eq!(redirected(&doc), 0); + assert!(warning_detail(&doc, UNVERIFIABLE).contains(": fetch error ")); +} + +#[tokio::test] +async fn scan_redirect_vlt_artifact_one_get_per_distinct_url() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + let ids = [ + TILDE_ID, + "~npm~left-pad@1.3.0~peer.2", + "~npm~left-pad@1.3.0~peer.3", + ]; + let nodes: Vec = ids.iter().map(|id| registry_node(id)).collect(); + std::fs::write(tmp.path().join("vlt-lock.json"), lock_with(Era::V1, &nodes)).unwrap(); + + let (_, doc) = scan_hosted(tmp.path(), &server, &["--dry-run"], &[]); + assert_eq!(redirected(&doc), 1); + assert_eq!(artifact_requests(&server).await, 1); +} + +#[tokio::test] +async fn scan_redirect_vlt_artifact_dry_run_still_preflights_writes_nothing() { + let server = MockServer::start().await; + mock_discovery(&server).await; + mock_reference(&server).await; + mock_artifact_with( + &server, + ResponseTemplate::new(200) + .insert_header("content-encoding", "gzip") + .set_body_bytes(gzip(&patched_tarball())), + ) + .await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + let lock = read(tmp.path(), "vlt-lock.json"); + + let (_, doc) = scan_hosted(tmp.path(), &server, &["--dry-run"], &[]); + + assert_eq!(artifact_requests(&server).await, 1); + assert_eq!(redirected(&doc), 0); + assert!(warning_codes(&doc).contains(&UNVERIFIABLE.to_string())); + assert_eq!(read(tmp.path(), "vlt-lock.json"), lock); + assert!(!tmp.path().join(".socket").exists()); +} + +async fn gzip_artifact_server() -> MockServer { + let server = MockServer::start().await; + mock_discovery(&server).await; + mock_reference(&server).await; + mock_view(&server).await; + mock_artifact_with( + &server, + ResponseTemplate::new(200) + .insert_header("content-encoding", "gzip") + .set_body_bytes(gzip(&patched_tarball())), + ) + .await; + server +} + +#[tokio::test] +async fn scan_redirect_vlt_artifact_failed_dep_withheld_from_every_rewriter() { + let server = gzip_artifact_server().await; + let tmp = tempfile::tempdir().unwrap(); + write_installed_vlt_project(tmp.path(), PRISTINE); + std::fs::write(tmp.path().join("package-lock.json"), package_lock()).unwrap(); + + let (_, doc) = scan_hosted(tmp.path(), &server, &[], &[]); + + assert_eq!(redirected(&doc), 0); + assert_eq!(read(tmp.path(), "package-lock.json"), package_lock()); + assert_eq!( + read(tmp.path(), "vlt-lock.json"), + lock_with(Era::V1, &[registry_node(TILDE_ID)]) + ); + assert!(store_dir(tmp.path(), TILDE_ID).exists()); +} + +#[tokio::test] +async fn scan_redirect_vlt_artifact_ambiguous_withholds_vlt_only() { + let server = gzip_artifact_server().await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + std::fs::write(tmp.path().join("package-lock.json"), package_lock()).unwrap(); + + let (_, doc) = scan_hosted(tmp.path(), &server, &["--no-npm-allow-remote-config"], &[]); + + assert_eq!( + redirected(&doc), + 1, + "package-lock.json still confirms: {doc:#}" + ); + assert!(read(tmp.path(), "package-lock.json").contains(&artifact_url(&server))); + assert_eq!( + read(tmp.path(), "vlt-lock.json"), + lock_with(Era::V1, &[registry_node(TILDE_ID)]) + ); + assert!(warning_codes(&doc).contains(&UNVERIFIABLE.to_string())); +} + +#[tokio::test] +async fn scan_redirect_vlt_artifact_get_uuid_driver() { + let server = gzip_artifact_server().await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + let lock = read(tmp.path(), "vlt-lock.json"); + let cwd = tmp.path().to_str().unwrap().to_string(); + let uri = server.uri(); + + let (code, doc, stderr) = run_json( + tmp.path(), + &[ + "get", + UUID, + "--mode", + "hosted", + "--yes", + "--cwd", + &cwd, + "--api-url", + &uri, + "--org", + ORG, + "--api-token", + "fake", + ], + &[], + ); + + assert_eq!(code, 0, "{doc:#}\n{stderr}"); + assert_eq!(artifact_requests(&server).await, 1); + assert_eq!(redirected(&doc), 0); + assert!(warning_codes(&doc).contains(&UNVERIFIABLE.to_string())); + assert_eq!(read(tmp.path(), "vlt-lock.json"), lock); +} + +#[tokio::test] +async fn scan_redirect_vlt_artifact_no_preflight_without_vlt_lock() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_package_json(tmp.path()); + install_importer(tmp.path(), PRISTINE); + std::fs::write(tmp.path().join("package-lock.json"), package_lock()).unwrap(); + + let (_, doc) = scan_hosted(tmp.path(), &server, &["--no-npm-allow-remote-config"], &[]); + + assert_eq!(redirected(&doc), 1); + assert_eq!(artifact_requests(&server).await, 0); + assert!(!warning_codes(&doc) + .iter() + .any(|c| c.starts_with("redirect_vlt_"))); +} + +#[tokio::test] +async fn scan_redirect_vlt_artifact_proxy_honored() { + let server = MockServer::start().await; + mock_discovery(&server).await; + mock_view(&server).await; + mock_artifact(&server).await; + let url = format!("http://vlt-artifact.invalid{}", artifact_path()); + mock_reference_at(&server, &url, &sha512_sri(&patched_tarball())).await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + let proxy = server.uri(); + + let (_, doc) = scan_hosted( + tmp.path(), + &server, + &[], + &[ + ("HTTP_PROXY", proxy.as_str()), + ("http_proxy", proxy.as_str()), + ("NO_PROXY", "127.0.0.1,localhost"), + ("no_proxy", "127.0.0.1,localhost"), + ], + ); + + assert_eq!(redirected(&doc), 1, "{doc:#}"); + assert_eq!(artifact_requests(&server).await, 1); +} + +#[tokio::test] +async fn scan_redirect_vlt_artifact_already_pinned_failure_left_pinned() { + let server = gzip_artifact_server().await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + let pinned = lock_with(Era::V1, &[pinned_node(TILDE_ID, &server)]); + std::fs::write(tmp.path().join("vlt-lock.json"), &pinned).unwrap(); + + let (_, doc) = scan_hosted(tmp.path(), &server, &[], &[]); + + assert_eq!(redirected(&doc), 0, "neither confirmed nor attested"); + assert_eq!( + read(tmp.path(), "vlt-lock.json"), + pinned, + "nothing is reverted" + ); + assert_eq!( + warning_detail(&doc, UNVERIFIABLE), + format!( + "vlt would fail to verify {}: content-encoding gzip; {PURL} was left pinned by an \ + earlier run and `vlt ci` will fail until the artifact verifies", + artifact_url(&server) + ) + ); +} + +#[tokio::test] +async fn scan_redirect_vlt_artifact_no_bearer_on_artifact_request() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + + scan_hosted(tmp.path(), &server, &[], &[]); + + let requests = server.received_requests().await.unwrap(); + let (artifact, api): (Vec<_>, Vec<_>) = requests + .iter() + .partition(|r| r.url.path() == artifact_path()); + assert_eq!(artifact.len(), 1); + assert!(artifact[0].headers.get("authorization").is_none()); + assert!(api.iter().any(|r| r.headers.get("authorization").is_some())); +} + +// ── warm-tree heal ─────────────────────────────────────────────────────── + +#[tokio::test] +async fn scan_redirect_vlt_warm_tree_invalidates_stale_store() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_installed_vlt_project(tmp.path(), PATCHED); + + let (_, doc) = scan_hosted(tmp.path(), &server, &[], &[]); + + assert_eq!(redirected(&doc), 1); + assert!(!store_dir(tmp.path(), TILDE_ID).exists()); + assert!(!tmp.path().join("node_modules/.vlt").join(TILDE_ID).exists()); + assert!(!tmp.path().join("node_modules/.vlt-lock.json").exists()); + assert!(tmp.path().join("node_modules/.vlt").is_dir()); + assert_eq!(warning_detail(&doc, ADVISORY), advisory_invalidated(1)); +} + +#[tokio::test] +async fn scan_redirect_vlt_heal_rule_b_hidden_lock_without_node() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_installed_vlt_project(tmp.path(), PATCHED); + write_hidden_lock(tmp.path(), &["\"~npm~ms@2.1.3\": [0,\"ms\"]".to_string()]); + + let (_, doc) = scan_hosted(tmp.path(), &server, &[], &[]); + + assert!(!store_dir(tmp.path(), TILDE_ID).exists()); + assert_eq!(warning_detail(&doc, ADVISORY), advisory_invalidated(1)); +} + +#[tokio::test] +async fn scan_redirect_vlt_heal_rule_c_no_hidden_lock_with_record() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_installed_vlt_project(tmp.path(), PRISTINE); + std::fs::remove_file(tmp.path().join("node_modules/.vlt-lock.json")).unwrap(); + + let (_, doc) = scan_hosted(tmp.path(), &server, &[], &[]); + + assert!(!store_dir(tmp.path(), TILDE_ID).exists()); + assert_eq!(warning_detail(&doc, ADVISORY), advisory_invalidated(1)); +} + +#[tokio::test] +async fn scan_redirect_vlt_heal_rule_c_no_record_uses_artifact_bytes() { + let server = MockServer::start().await; + mock_discovery(&server).await; + mock_reference(&server).await; + mock_artifact(&server).await; + Mock::given(method("GET")) + .and(path(format!("/v0/orgs/{ORG}/patches/view/{UUID}"))) + .respond_with(ResponseTemplate::new(404)) + .mount(&server) + .await; + let tmp = tempfile::tempdir().unwrap(); + write_installed_vlt_project(tmp.path(), PRISTINE); + std::fs::remove_file(tmp.path().join("node_modules/.vlt-lock.json")).unwrap(); + let healthy = tempfile::tempdir().unwrap(); + write_installed_vlt_project(healthy.path(), PATCHED); + std::fs::remove_file(healthy.path().join("node_modules/.vlt-lock.json")).unwrap(); + + let (_, doc) = scan_hosted(tmp.path(), &server, &[], &[]); + let (_, healthy_doc) = scan_hosted(healthy.path(), &server, &[], &[]); + + assert!(warning_codes(&doc).contains(&"record_fetch_failed".to_string())); + assert!(!store_dir(tmp.path(), TILDE_ID).exists()); + assert_eq!(warning_detail(&doc, ADVISORY), advisory_invalidated(1)); + assert!(store_dir(healthy.path(), TILDE_ID).exists()); + assert_eq!( + warning_detail(&healthy_doc, ADVISORY), + ADVISORY_NOTHING_STALE + ); +} + +#[cfg(unix)] +fn link_node_modules_outside(root: &Path, outside: &Path) { + let real = outside.join("node_modules"); + std::fs::rename(root.join("node_modules"), &real).unwrap(); + std::os::unix::fs::symlink(&real, root.join("node_modules")).unwrap(); +} + +#[cfg(unix)] +#[tokio::test] +async fn scan_redirect_vlt_heal_undeterminable_node_modules_symlink_not_deleted() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + let outside = tempfile::tempdir().unwrap(); + write_installed_vlt_project(tmp.path(), PRISTINE); + link_node_modules_outside(tmp.path(), outside.path()); + + let (_, doc) = scan_hosted(tmp.path(), &server, &[], &[]); + + assert_eq!(warning_detail(&doc, ADVISORY), advisory_undeterminable(1)); + assert!(store_dir(outside.path(), TILDE_ID) + .join("index.js") + .exists()); + assert!(outside.path().join("node_modules/.vlt-lock.json").exists()); +} + +#[tokio::test] +async fn scan_redirect_vlt_heal_advisory_texts_exact() { + let server = MockServer::start().await; + mock_all(&server).await; + let fresh = tempfile::tempdir().unwrap(); + write_vlt_project(fresh.path(), Era::V1); + let warm = tempfile::tempdir().unwrap(); + write_installed_vlt_project(warm.path(), PRISTINE); + let skipped = tempfile::tempdir().unwrap(); + write_installed_vlt_project(skipped.path(), PRISTINE); + + let (_, fresh_doc) = scan_hosted(fresh.path(), &server, &[], &[]); + let (_, warm_doc) = scan_hosted(warm.path(), &server, &[], &[]); + let (_, skipped_doc) = scan_hosted(skipped.path(), &server, &["--no-vlt-install-cleanup"], &[]); + + assert_eq!(warning_detail(&fresh_doc, ADVISORY), ADVISORY_NOTHING_STALE); + assert_eq!(warning_detail(&warm_doc, ADVISORY), advisory_invalidated(1)); + assert_eq!( + warning_detail(&skipped_doc, ADVISORY), + advisory_cleanup_skipped(1) + ); + for doc in [&fresh_doc, &warm_doc, &skipped_doc] { + assert_eq!( + warning_codes(doc).iter().filter(|c| *c == ADVISORY).count(), + 1, + "{doc:#}" + ); + } +} + +#[tokio::test] +async fn scan_redirect_vlt_heal_dry_run_deletes_nothing_emits_iii() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_installed_vlt_project(tmp.path(), PRISTINE); + let lock = read(tmp.path(), "vlt-lock.json"); + + let (_, doc) = scan_hosted(tmp.path(), &server, &["--dry-run"], &[]); + + assert_eq!(warning_detail(&doc, ADVISORY), advisory_cleanup_skipped(1)); + assert!(store_dir(tmp.path(), TILDE_ID).join("index.js").exists()); + assert!(tmp.path().join("node_modules/.vlt-lock.json").exists()); + assert_eq!(read(tmp.path(), "vlt-lock.json"), lock); +} + +#[cfg(unix)] +fn mkfifo(path: &Path) { + let status = std::process::Command::new("mkfifo") + .arg(path) + .status() + .unwrap(); + assert!(status.success()); +} + +#[cfg(unix)] +#[tokio::test] +async fn scan_redirect_vlt_heal_hidden_lock_fifo_or_symlink() { + let server = MockServer::start().await; + mock_all(&server).await; + + let fifo = tempfile::tempdir().unwrap(); + write_installed_vlt_project(fifo.path(), PRISTINE); + let hidden = fifo.path().join("node_modules/.vlt-lock.json"); + std::fs::remove_file(&hidden).unwrap(); + mkfifo(&hidden); + let (_, doc) = scan_hosted(fifo.path(), &server, &[], &[]); + assert_eq!(warning_detail(&doc, ADVISORY), advisory_invalidated(1)); + assert!(std::fs::symlink_metadata(&hidden).is_err()); + assert!(!store_dir(fifo.path(), TILDE_ID).exists()); + + let linked = tempfile::tempdir().unwrap(); + write_installed_vlt_project(linked.path(), PRISTINE); + let hidden = linked.path().join("node_modules/.vlt-lock.json"); + let target = linked.path().join("hidden-target.json"); + std::fs::rename(&hidden, &target).unwrap(); + std::os::unix::fs::symlink(&target, &hidden).unwrap(); + let (_, doc) = scan_hosted(linked.path(), &server, &[], &[]); + assert_eq!(warning_detail(&doc, ADVISORY), advisory_invalidated(1)); + assert!(std::fs::symlink_metadata(&hidden).is_err()); + assert!(target.exists(), "only the link is removed"); +} + +#[cfg(unix)] +#[tokio::test] +async fn scan_redirect_vlt_heal_invalidation_failure_warns() { + use std::os::unix::fs::PermissionsExt as _; + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_installed_vlt_project(tmp.path(), PRISTINE); + let store = tmp.path().join("node_modules/.vlt"); + std::fs::set_permissions(&store, std::fs::Permissions::from_mode(0o555)).unwrap(); + + let (code, doc) = scan_hosted(tmp.path(), &server, &[], &[]); + std::fs::set_permissions(&store, std::fs::Permissions::from_mode(0o755)).unwrap(); + + assert_eq!(code, 0); + assert_eq!(redirected(&doc), 1); + assert_eq!(warning_detail(&doc, ADVISORY), advisory_cleanup_skipped(1)); + assert!(store.join(TILDE_ID).exists()); +} + +#[tokio::test] +async fn scan_redirect_vlt_no_install_cleanup_flag_keeps_tree() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_installed_vlt_project(tmp.path(), PRISTINE); + + let (_, doc) = scan_hosted(tmp.path(), &server, &["--no-vlt-install-cleanup"], &[]); + + assert_eq!(redirected(&doc), 1); + assert_eq!( + read(tmp.path(), "vlt-lock.json"), + lock_with(Era::V1, &[pinned_node(TILDE_ID, &server)]) + ); + assert!(store_dir(tmp.path(), TILDE_ID).join("index.js").exists()); + assert!(tmp.path().join("node_modules/.vlt-lock.json").exists()); + assert_eq!(warning_detail(&doc, ADVISORY), advisory_cleanup_skipped(1)); +} + +#[tokio::test] +async fn scan_redirect_vlt_env_socket_no_vlt_install_cleanup() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_installed_vlt_project(tmp.path(), PRISTINE); + + let (_, doc) = scan_hosted( + tmp.path(), + &server, + &[], + &[("SOCKET_NO_VLT_INSTALL_CLEANUP", "1")], + ); + + assert!(store_dir(tmp.path(), TILDE_ID).join("index.js").exists()); + assert_eq!(warning_detail(&doc, ADVISORY), advisory_cleanup_skipped(1)); +} + +#[tokio::test] +async fn scan_redirect_vlt_rerun_does_not_invalidate_healthy_tree() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + std::fs::write( + tmp.path().join("vlt-lock.json"), + lock_with(Era::V1, &[pinned_node(TILDE_ID, &server)]), + ) + .unwrap(); + install_store(tmp.path(), TILDE_ID, PATCHED); + write_hidden_lock(tmp.path(), &[pinned_node(TILDE_ID, &server)]); + + let (_, doc) = scan_hosted(tmp.path(), &server, &[], &[]); + + assert_eq!(redirected(&doc), 1); + assert!(store_dir(tmp.path(), TILDE_ID).join("index.js").exists()); + assert!(tmp.path().join("node_modules/.vlt-lock.json").exists()); + assert_eq!(warning_detail(&doc, ADVISORY), ADVISORY_NOTHING_STALE); +} + +#[tokio::test] +async fn scan_redirect_vlt_heal_custom_patch_server_origin_heals() { + let server = MockServer::start().await; + let artifacts = MockServer::start().await; + mock_discovery(&server).await; + mock_view(&server).await; + mock_artifact(&artifacts).await; + mock_reference_at( + &server, + &artifact_url(&artifacts), + &sha512_sri(&patched_tarball()), + ) + .await; + let configured = tempfile::tempdir().unwrap(); + write_installed_vlt_project(configured.path(), PRISTINE); + let unconfigured = tempfile::tempdir().unwrap(); + write_installed_vlt_project(unconfigured.path(), PRISTINE); + let origin = artifacts.uri(); + + let (_, doc) = scan_hosted( + configured.path(), + &server, + &["--patch-server-url", &origin], + &[], + ); + let (_, plain) = scan_hosted(unconfigured.path(), &server, &[], &[]); + + assert_eq!(warning_detail(&doc, ADVISORY), advisory_invalidated(1)); + assert!(!store_dir(configured.path(), TILDE_ID).exists()); + assert!( + !warning_codes(&plain).contains(&ADVISORY.to_string()), + "a URL on an unconfigured host is not Socket-owned: {plain:#}" + ); + assert!(store_dir(unconfigured.path(), TILDE_ID).exists()); +} + +// ── in-run VEX exclusion ───────────────────────────────────────────────── + +fn vex_args(out: &Path) -> Vec { + vec![ + "--vex".into(), + out.to_str().unwrap().into(), + "--vex-product".into(), + "pkg:npm/consumer@0.0.0".into(), + ] +} + +fn scan_with_vex(root: &Path, server: &MockServer, extra: &[&str]) -> (i32, Value, bool) { + let out = root.join("out.vex.json"); + let vex = vex_args(&out); + let mut args: Vec<&str> = vex.iter().map(String::as_str).collect(); + args.extend_from_slice(extra); + let (code, doc) = scan_hosted(root, server, &args, &[]); + (code, doc, vex_attests(&out)) +} + +#[tokio::test] +async fn scan_redirect_vlt_no_cleanup_vex_not_attested() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_installed_vlt_project(tmp.path(), PRISTINE); + let healed = tempfile::tempdir().unwrap(); + write_installed_vlt_project(healed.path(), PRISTINE); + + let (_, doc, attested) = scan_with_vex(tmp.path(), &server, &["--no-vlt-install-cleanup"]); + let (code, _, healed_attested) = scan_with_vex(healed.path(), &server, &[]); + + assert_eq!(redirected(&doc), 1); + assert!( + !attested, + "stale installed copies are not attested: {doc:#}" + ); + assert_eq!(code, 0); + assert!( + healed_attested, + "the invalidated tree attests from the ledger" + ); +} + +#[tokio::test] +async fn scan_redirect_vlt_foreign_instance_vex_not_attested() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + std::fs::write( + tmp.path().join("vlt-lock.json"), + lock_with( + Era::V1, + &[ + registry_node("~custom~left-pad@1.3.0"), + registry_node(TILDE_ID), + ], + ), + ) + .unwrap(); + + let (_, doc, attested) = scan_with_vex(tmp.path(), &server, &[]); + + assert_eq!(redirected(&doc), 1); + assert!(warning_codes(&doc).contains(&"redirect_vlt_custom_registry_skipped".to_string())); + assert!(!attested, "{doc:#}"); +} + +#[tokio::test] +async fn scan_redirect_vlt_old_lockfile_vex_not_attested() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V0); + std::fs::write( + tmp.path().join("vlt-lock.json"), + lock_with(Era::V0, &[registry_node(EMPTY_SEGMENT_ID)]), + ) + .unwrap(); + + let (_, doc, attested) = scan_with_vex(tmp.path(), &server, &[]); + + assert_eq!(redirected(&doc), 1); + assert!(warning_codes(&doc).contains(&"redirect_vlt_old_lockfile_ignored".to_string())); + assert!(!attested, "{doc:#}"); +} + +#[tokio::test] +async fn scan_redirect_vlt_a0_vex_not_attested() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::A0); + let control = tempfile::tempdir().unwrap(); + write_vlt_project(control.path(), Era::V1); + + let (_, doc, attested) = scan_with_vex(tmp.path(), &server, &[]); + let (_, _, control_attested) = scan_with_vex(control.path(), &server, &[]); + + assert!(warning_codes(&doc).contains(&"redirect_vlt_lockfile_version_missing".to_string())); + assert!(!attested, "{doc:#}"); + assert!(control_attested); +} + +#[cfg(unix)] +#[tokio::test] +async fn scan_redirect_vlt_undeterminable_vex_not_attested() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + let outside = tempfile::tempdir().unwrap(); + write_installed_vlt_project(tmp.path(), PRISTINE); + link_node_modules_outside(tmp.path(), outside.path()); + + let (_, doc, attested) = scan_with_vex(tmp.path(), &server, &[]); + + assert_eq!(warning_detail(&doc, ADVISORY), advisory_undeterminable(1)); + assert!(!attested, "{doc:#}"); +} + +#[tokio::test] +async fn scan_redirect_vlt_workspace_member_cwd_sees_no_root_lock() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + let root_lock = read(tmp.path(), "vlt-lock.json"); + let member = tmp.path().join("packages/a"); + std::fs::create_dir_all(&member).unwrap(); + write_package_json(&member); + install_importer(&member, PRISTINE); + + let (_, doc) = scan_hosted(&member, &server, &[], &[]); + + assert_eq!(redirected(&doc), 0, "{doc:#}"); + assert!(!member.join("package-lock.json").exists()); + assert!(!member.join("vlt-lock.json").exists()); + assert_eq!(read(tmp.path(), "vlt-lock.json"), root_lock); + assert!(!warning_codes(&doc) + .iter() + .any(|c| c.starts_with("redirect_vlt_"))); + assert_eq!(artifact_requests(&server).await, 0); +} diff --git a/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs b/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs index 6c4b80ea..8339c0e9 100644 --- a/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs +++ b/crates/socket-patch-cli/tests/in_process_rollback_hosted.rs @@ -26,6 +26,10 @@ use std::path::Path; mod vex_e2e_common; #[path = "vex_pipenv_pip_steps/mod.rs"] mod vex_pipenv_pip_steps; +#[path = "in_process_rollback_hosted/vlt.rs"] +mod vlt; +#[path = "vlt_hosted_common/mod.rs"] +mod vlt_hosted_common; use serde_json::Value; use serial_test::serial; diff --git a/crates/socket-patch-cli/tests/in_process_rollback_hosted/vlt.rs b/crates/socket-patch-cli/tests/in_process_rollback_hosted/vlt.rs new file mode 100644 index 00000000..42e20634 --- /dev/null +++ b/crates/socket-patch-cli/tests/in_process_rollback_hosted/vlt.rs @@ -0,0 +1,191 @@ +//! Hosted vlt round trips: a real `scan --mode hosted` over a vlt project, +//! then `rollback` / `remove` restoring the registry pins slot by slot +//! (after vlt re-laid the line) and invalidating the patched installed +//! copies so the next install extracts the registry bytes. + +use std::path::Path; + +use serde_json::Value; +use wiremock::MockServer; + +use crate::vlt_hosted_common::*; + +const RESTORED: &str = "restored registry pins for 1 packages; removed the patched installed \ + copies, so node_modules is incomplete until you run `vlt install` (or `vlt ci`)"; + +async fn hosted_vlt_project(root: &Path) -> MockServer { + let server = MockServer::start().await; + mock_all(&server).await; + write_vlt_project(root, Era::V1); + let (_, doc) = scan_hosted(root, &server, &[], &[]); + assert_eq!(redirected(&doc), 1, "{doc:#}"); + assert_eq!( + read(root, "vlt-lock.json"), + vlt_lock(Era::V1, &[pinned_node(TILDE_ID, &server)]) + ); + server +} + +/// What `vlt install` leaves after the redirect: the patched store entry +/// and a hidden lock recording the pinned node. +fn vlt_install_patched(root: &Path, server: &MockServer) { + install_store(root, TILDE_ID, PATCHED); + write_hidden_lock(root, &[pinned_node(TILDE_ID, server)]); +} + +fn run_verb(root: &Path, verb: &str, extra: &[&str]) -> (i32, Value) { + let cwd = root.to_str().unwrap().to_string(); + let mut args = vec![verb]; + args.extend_from_slice(extra); + args.extend_from_slice(&["--yes", "--offline", "--cwd", &cwd]); + let (code, doc, stderr) = run_json(root, &args, &[]); + assert_eq!(code, 0, "{verb} must succeed: {doc:#}\n{stderr}"); + (code, doc) +} + +fn advisory_details(doc: &Value) -> Vec { + doc["warnings"] + .as_array() + .into_iter() + .flatten() + .filter(|w| w["code"] == ADVISORY) + .filter_map(|w| w["detail"].as_str().map(str::to_string)) + .collect() +} + +#[tokio::test] +async fn vlt_hosted_round_trip() { + for scoped in [true, false] { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let pristine = vlt_lock(Era::V1, &[registry_node(TILDE_ID)]); + let server = hosted_vlt_project(root).await; + vlt_install_patched(root, &server); + + let targets: &[&str] = if scoped { &[PURL] } else { &[] }; + let (_, doc) = run_verb(root, "rollback", targets); + + assert_eq!(read(root, "vlt-lock.json"), pristine, "scoped={scoped}"); + assert_eq!(advisory_details(&doc), [RESTORED], "{doc:#}"); + assert!(!store_dir(root, TILDE_ID).exists()); + assert!(!root.join("node_modules/.vlt-lock.json").exists()); + assert!( + !ledger_path(root).exists() + || !read(root, ".socket/vendor/redirect-state.json") + .contains("redirect_vlt_lock_node") + ); + } +} + +#[tokio::test] +async fn vlt_hosted_remove_restores_and_emits_the_advisory() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let server = hosted_vlt_project(root).await; + vlt_install_patched(root, &server); + + let (_, doc) = run_verb(root, "remove", &[PURL]); + + assert_eq!( + read(root, "vlt-lock.json"), + vlt_lock(Era::V1, &[registry_node(TILDE_ID)]) + ); + assert!(doc.to_string().contains(ADVISORY), "{doc:#}"); + assert!(!store_dir(root, TILDE_ID).exists()); +} + +#[tokio::test] +async fn vlt_rollback_after_vlt_update_invalidates_patched_store() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let _server = hosted_vlt_project(root).await; + let registry = vlt_lock(Era::V1, &[registry_node(TILDE_ID)]); + std::fs::write(root.join("vlt-lock.json"), ®istry).unwrap(); + install_store(root, TILDE_ID, PATCHED); + write_hidden_lock(root, &[registry_node(TILDE_ID)]); + + let (_, doc) = run_verb(root, "rollback", &[]); + + assert_eq!(read(root, "vlt-lock.json"), registry); + assert_eq!(advisory_details(&doc), [RESTORED], "{doc:#}"); + assert!(!store_dir(root, TILDE_ID).exists()); +} + +#[tokio::test] +async fn vlt_rollback_after_comma_move() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let server = hosted_vlt_project(root).await; + let sibling = "\"~npm~zz@1.0.0\": [0,\"zz\"]".to_string(); + std::fs::write( + root.join("vlt-lock.json"), + vlt_lock(Era::V1, &[pinned_node(TILDE_ID, &server), sibling.clone()]), + ) + .unwrap(); + + let (_, doc) = run_verb(root, "rollback", &[PURL]); + + assert_eq!( + read(root, "vlt-lock.json"), + vlt_lock(Era::V1, &[registry_node(TILDE_ID), sibling]) + ); + assert!( + advisory_details(&doc).is_empty(), + "nothing installed: {doc:#}" + ); +} + +#[tokio::test] +async fn vlt_rollback_after_e0_flag_change() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let server = hosted_vlt_project(root).await; + let relaid = pinned_node(TILDE_ID, &server).replacen("[0,", "[1,", 1); + std::fs::write(root.join("vlt-lock.json"), vlt_lock(Era::V1, &[relaid])).unwrap(); + + run_verb(root, "rollback", &[]); + + assert_eq!( + read(root, "vlt-lock.json"), + vlt_lock( + Era::V1, + &[registry_node(TILDE_ID).replacen("[0,", "[1,", 1)] + ) + ); +} + +#[tokio::test] +async fn vlt_rollback_honors_no_vlt_install_cleanup() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let server = hosted_vlt_project(root).await; + vlt_install_patched(root, &server); + + let (_, doc) = run_verb(root, "rollback", &["--no-vlt-install-cleanup"]); + + assert_eq!( + advisory_details(&doc), + [ + "restored registry pins for 1 packages, but node_modules still holds 1 patched copies \ + and `vlt install` will not refresh them; run `vlt ci` (or re-run without \ + --no-vlt-install-cleanup)" + ], + "{doc:#}" + ); + assert!(store_dir(root, TILDE_ID).join("index.js").exists()); +} + +#[tokio::test] +async fn vlt_rollback_of_a_pristine_tree_keeps_it() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let _server = hosted_vlt_project(root).await; + install_store(root, TILDE_ID, PRISTINE); + write_hidden_lock(root, &[registry_node(TILDE_ID)]); + + let (_, doc) = run_verb(root, "rollback", &[]); + + assert!(advisory_details(&doc).is_empty(), "{doc:#}"); + assert!(store_dir(root, TILDE_ID).join("index.js").exists()); + assert!(root.join("node_modules/.vlt-lock.json").exists()); +} diff --git a/crates/socket-patch-cli/tests/remove_rollback_api_overrides.rs b/crates/socket-patch-cli/tests/remove_rollback_api_overrides.rs index 5db30e08..bee81cc8 100644 --- a/crates/socket-patch-cli/tests/remove_rollback_api_overrides.rs +++ b/crates/socket-patch-cli/tests/remove_rollback_api_overrides.rs @@ -59,6 +59,7 @@ const SOCKET_ENV_VARS: &[&str] = &[ "SOCKET_SKIP_ROLLBACK", "SOCKET_NO_TRUST_LOCKFILE_CONFIG", "SOCKET_NO_NPM_ALLOW_REMOTE_CONFIG", + "SOCKET_NO_VLT_INSTALL_CLEANUP", ]; /// Drift guard: the scrub must cover every env var `GlobalArgs` binds — the diff --git a/crates/socket-patch-cli/tests/vlt_hosted_common/mod.rs b/crates/socket-patch-cli/tests/vlt_hosted_common/mod.rs new file mode 100644 index 00000000..2620a041 --- /dev/null +++ b/crates/socket-patch-cli/tests/vlt_hosted_common/mod.rs @@ -0,0 +1,509 @@ +//! Hermetic vlt hosted-mode fixtures shared by the redirect, rollback, +//! symlink, get and covgap suites: a vlt project (`vlt-lock.json`, an +//! installed importer copy, optionally vlt's store and hidden lock), the +//! wiremock API (discovery, the grant, the patch view) plus the artifact +//! route the vlt preflight fetches, and a scrubbed subprocess runner that +//! reads the `--json` envelope back. +//! +//! Include with `#[path = "vlt_hosted_common/mod.rs"] mod vlt_hosted_common;`. + +#![allow(dead_code)] + +use std::io::Write as _; +use std::path::{Path, PathBuf}; + +use base64::Engine as _; +use serde_json::{json, Value}; +use sha2::{Digest, Sha256, Sha512}; +use wiremock::matchers::{method, path, path_regex}; +use wiremock::{Mock, MockServer, ResponseTemplate}; + +pub const ORG: &str = "test-org"; +pub const NAME: &str = "left-pad"; +pub const VERSION: &str = "1.3.0"; +pub const PURL: &str = "pkg:npm/left-pad@1.3.0"; +pub const UUID: &str = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"; +pub const TOKEN: &str = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"; +pub const GHSA: &str = "GHSA-vlth-aaaa-bbbb"; +pub const UPSTREAM_SHA512: &str = "sha512-UPSTREAMupstreamUPSTREAMupstream=="; +pub const REGISTRY_URL: &str = "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"; +pub const PRISTINE: &[u8] = b"module.exports = 'pristine'\n"; +pub const PATCHED: &[u8] = b"module.exports = 'patched'\n"; +pub const PACKAGE_JSON: &[u8] = b"{\"name\":\"left-pad\",\"version\":\"1.3.0\"}\n"; +pub const TILDE_ID: &str = "~npm~left-pad@1.3.0"; +pub const LEGACY_ID: &str = "·npm·left-pad@1.3.0"; +pub const EMPTY_SEGMENT_ID: &str = "··left-pad@1.3.0"; + +pub const ADVISORY: &str = "redirect_vlt_reinstall_required"; +pub const UNVERIFIABLE: &str = "redirect_vlt_artifact_unverifiable"; + +pub const ADVISORY_NOTHING_STALE: &str = "vlt-lock.json pins Socket-patched packages; fresh \ + checkouts install them with `vlt ci` or `vlt install --frozen-lockfile`. Note: `vlt update` \ + re-resolves from the registry and drops these redirects."; + +pub fn advisory_invalidated(n: usize) -> String { + format!( + "vlt-lock.json pins Socket-patched packages; socket-patch removed {n} stale installed \ + copies (node_modules/.vlt-lock.json and node_modules/.vlt entries), so node_modules is \ + incomplete until you run `vlt install` (or `vlt ci`), which installs the patched \ + packages. Note: `vlt update` re-resolves from the registry and drops these redirects." + ) +} + +pub fn advisory_cleanup_skipped(n: usize) -> String { + format!( + "vlt-lock.json pins Socket-patched packages, but node_modules still holds {n} unpatched \ + copies and `vlt install` will not refresh them; run `vlt ci` (or re-run without \ + --no-vlt-install-cleanup)." + ) +} + +pub fn advisory_undeterminable(n: usize) -> String { + format!( + "vlt-lock.json pins Socket-patched packages, but socket-patch could not check {n} \ + installed copies (node_modules is a link, or no patch record or artifact was \ + available); run `vlt ci` to be sure the patched packages are installed." + ) +} + +pub fn git_sha256(bytes: &[u8]) -> String { + let mut hasher = Sha256::new(); + hasher.update(format!("blob {}\0", bytes.len()).as_bytes()); + hasher.update(bytes); + hex::encode(hasher.finalize()) +} + +pub fn sha512_sri(bytes: &[u8]) -> String { + format!( + "sha512-{}", + base64::engine::general_purpose::STANDARD.encode(Sha512::digest(bytes)) + ) +} + +/// The patched npm tarball the hosted artifact route serves. +pub fn patched_tarball() -> Vec { + let gz = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default()); + let mut builder = tar::Builder::new(gz); + for (name, data) in [ + ("package/package.json", PACKAGE_JSON), + ("package/index.js", PATCHED), + ] { + let mut header = tar::Header::new_gnu(); + header.set_size(data.len() as u64); + header.set_mode(0o644); + header.set_cksum(); + builder.append_data(&mut header, name, data).unwrap(); + } + builder.into_inner().unwrap().finish().unwrap() +} + +pub fn gzip(bytes: &[u8]) -> Vec { + let mut enc = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default()); + enc.write_all(bytes).unwrap(); + enc.finish().unwrap() +} + +pub fn artifact_path() -> String { + format!("/patch/npm/{NAME}/{VERSION}/{TOKEN}/{UUID}/{NAME}-{VERSION}.tgz") +} + +pub fn artifact_url(server: &MockServer) -> String { + format!("{}{}", server.uri(), artifact_path()) +} + +pub async fn mock_discovery(server: &MockServer) { + Mock::given(method("POST")) + .and(path(format!("/v0/orgs/{ORG}/patches/batch"))) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "packages": [{ + "purl": PURL, + "patches": [{ + "uuid": UUID, "purl": PURL, "tier": "free", + "cveIds": [], "ghsaIds": [], "severity": "high", + "title": "vlt hosted fixture" + }] + }], + "canAccessPaidPatches": false, + }))) + .mount(server) + .await; + Mock::given(method("GET")) + .and(path_regex(format!( + "^/v0/orgs/{ORG}/patches/by-package/.+$" + ))) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "patches": [{ + "uuid": UUID, "purl": PURL, + "publishedAt": "2024-01-01T00:00:00Z", + "description": "x", "license": "MIT", "tier": "free", + "vulnerabilities": {} + }], + "canAccessPaidPatches": false, + }))) + .mount(server) + .await; +} + +/// The grant for [`UUID`], pointing at `url` with `sha512`. +pub async fn mock_reference_at(server: &MockServer, url: &str, sha512: &str) { + Mock::given(method("POST")) + .and(path(format!("/v0/orgs/{ORG}/patches/package"))) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "results": { + UUID: { + "status": "granted", + "url": url, + "purl": PURL, + "artifacts": [{ + "kind": "tarball", + "url": url, + "integrity": { "sha512": sha512 } + }], + "registryOverride": null + } + } + }))) + .mount(server) + .await; +} + +/// The grant for the served [`patched_tarball`]. +pub async fn mock_reference(server: &MockServer) { + mock_reference_at( + server, + &artifact_url(server), + &sha512_sri(&patched_tarball()), + ) + .await; +} + +pub fn view_body() -> Value { + json!({ + "uuid": UUID, + "purl": PURL, + "publishedAt": "2024-01-01T00:00:00Z", + "files": { + "package/index.js": { + "beforeHash": git_sha256(PRISTINE), + "afterHash": git_sha256(PATCHED), + } + }, + "vulnerabilities": { + GHSA: { + "cves": ["CVE-2026-4242"], + "summary": "vlt hosted fixture", + "severity": "high", + "description": "d" + } + }, + "description": "x", "license": "MIT", "tier": "free" + }) +} + +pub async fn mock_view(server: &MockServer) { + Mock::given(method("GET")) + .and(path(format!("/v0/orgs/{ORG}/patches/view/{UUID}"))) + .respond_with(ResponseTemplate::new(200).set_body_json(view_body())) + .mount(server) + .await; +} + +/// The artifact route with an arbitrary response. +pub async fn mock_artifact_with(server: &MockServer, response: ResponseTemplate) { + Mock::given(method("GET")) + .and(path(artifact_path())) + .respond_with(response) + .mount(server) + .await; +} + +pub async fn mock_artifact(server: &MockServer) { + mock_artifact_with( + server, + ResponseTemplate::new(200).set_body_bytes(patched_tarball()), + ) + .await; +} + +/// Discovery, grant, view and a passing artifact. +pub async fn mock_all(server: &MockServer) { + mock_discovery(server).await; + mock_reference(server).await; + mock_view(server).await; + mock_artifact(server).await; +} + +/// How many requests reached the artifact route. +pub async fn artifact_requests(server: &MockServer) -> usize { + server + .received_requests() + .await + .unwrap_or_default() + .iter() + .filter(|r| r.url.path() == artifact_path()) + .count() +} + +/// Which `vlt-lock.json` era to write. +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Era { + /// `lockfileVersion: 1`, tilde ids. + V1, + /// `lockfileVersion: 0`, `·npm·` ids. + V0, + /// No `lockfileVersion`, `··` ids. + A0, +} + +impl Era { + pub fn dep_id(self) -> &'static str { + match self { + Era::V1 => TILDE_ID, + Era::V0 => LEGACY_ID, + Era::A0 => EMPTY_SEGMENT_ID, + } + } +} + +/// A canonical vlt lock with the given node entries (`"": `). +pub fn vlt_lock(era: Era, nodes: &[String]) -> String { + let version = match era { + Era::V1 => " \"lockfileVersion\": 1,\n", + Era::V0 => " \"lockfileVersion\": 0,\n", + Era::A0 => "", + }; + let body = nodes + .iter() + .map(|n| format!(" {n}")) + .collect::>() + .join(",\n"); + format!( + "{{\n{version} \"options\": {{}},\n \"nodes\": {{\n{body}\n }},\n \"edges\": {{}}\n}}\n" + ) +} + +/// `"": [0,"left-pad","",""]`. +pub fn node(id: &str, sha512: &str, url: &str) -> String { + format!("\"{id}\": [0,\"{NAME}\",\"{sha512}\",\"{url}\"]") +} + +pub fn registry_node(id: &str) -> String { + node(id, UPSTREAM_SHA512, REGISTRY_URL) +} + +/// The same line after a hosted splice. +pub fn pinned_node(id: &str, server: &MockServer) -> String { + node(id, &sha512_sri(&patched_tarball()), &artifact_url(server)) +} + +pub fn write_package_json(root: &Path) { + std::fs::write( + root.join("package.json"), + format!( + r#"{{ "name": "consumer", "version": "0.0.0", "dependencies": {{ "{NAME}": "{VERSION}" }} }}"# + ), + ) + .unwrap(); +} + +/// The importer copy the crawler discovers, `node_modules/left-pad`. +pub fn install_importer(root: &Path, index: &[u8]) { + let dir = root.join("node_modules").join(NAME); + std::fs::create_dir_all(&dir).unwrap(); + std::fs::write(dir.join("package.json"), PACKAGE_JSON).unwrap(); + std::fs::write(dir.join("index.js"), index).unwrap(); +} + +/// vlt's store entry `node_modules/.vlt//node_modules/left-pad`. +pub fn store_dir(root: &Path, id: &str) -> PathBuf { + root.join("node_modules/.vlt") + .join(id) + .join("node_modules") + .join(NAME) +} + +pub fn install_store(root: &Path, id: &str, index: &[u8]) { + let dir = store_dir(root, id); + std::fs::create_dir_all(&dir).unwrap(); + std::fs::write(dir.join("package.json"), PACKAGE_JSON).unwrap(); + std::fs::write(dir.join("index.js"), index).unwrap(); +} + +/// `node_modules/.vlt-lock.json` holding `nodes` verbatim. +pub fn write_hidden_lock(root: &Path, nodes: &[String]) { + std::fs::create_dir_all(root.join("node_modules")).unwrap(); + std::fs::write( + root.join("node_modules/.vlt-lock.json"), + vlt_lock(Era::V1, nodes), + ) + .unwrap(); +} + +/// A vlt project: package.json, `vlt-lock.json` with one registry node, +/// and the importer copy (no store). +pub fn write_vlt_project(root: &Path, era: Era) { + write_package_json(root); + std::fs::write( + root.join("vlt-lock.json"), + vlt_lock(era, &[registry_node(era.dep_id())]), + ) + .unwrap(); + install_importer(root, PRISTINE); +} + +/// [`write_vlt_project`] (v1) plus a warm install: the store entry with +/// `index` and a hidden lock recording the registry integrity. +pub fn write_installed_vlt_project(root: &Path, index: &[u8]) { + write_vlt_project(root, Era::V1); + install_store(root, TILDE_ID, index); + write_hidden_lock(root, &[registry_node(TILDE_ID)]); +} + +pub fn package_lock() -> String { + format!( + r#"{{ + "name": "consumer", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": {{ + "": {{ "name": "consumer", "version": "0.0.0", "dependencies": {{ "{NAME}": "{VERSION}" }} }}, + "node_modules/{NAME}": {{ + "version": "{VERSION}", + "resolved": "{REGISTRY_URL}", + "integrity": "{UPSTREAM_SHA512}" + }} + }} +}} +"# + ) +} + +pub fn read(root: &Path, rel: &str) -> String { + std::fs::read_to_string(root.join(rel)).unwrap_or_else(|e| panic!("read {rel}: {e}")) +} + +pub fn ledger_path(root: &Path) -> PathBuf { + root.join(".socket/vendor/redirect-state.json") +} + +/// The `socket-patch` binary with the ambient `SOCKET_*` and proxy +/// environment scrubbed (telemetry opt-outs kept). +pub fn scrubbed_cli() -> std::process::Command { + let mut cmd = std::process::Command::new(env!("CARGO_BIN_EXE_socket-patch")); + cmd.env("SOCKET_DRY_RUN", "true") + .env("SOCKET_OFFLINE", "true") + .env("SOCKET_NO_VLT_INSTALL_CLEANUP", "true") + .env_remove("SOCKET_DRY_RUN") + .env_remove("SOCKET_OFFLINE") + .env_remove("SOCKET_NO_VLT_INSTALL_CLEANUP"); + for (key, _) in std::env::vars_os() { + let name = key.to_string_lossy(); + let proxy = name.eq_ignore_ascii_case("http_proxy") + || name.eq_ignore_ascii_case("https_proxy") + || name.eq_ignore_ascii_case("all_proxy") + || name.eq_ignore_ascii_case("no_proxy"); + if proxy + || (name.starts_with("SOCKET_") + && !name.contains("TELEMETRY") + && name != "SOCKET_NO_CONFIG") + { + cmd.env_remove(&key); + } + } + cmd.env("SOCKET_NO_CONFIG", "1") + .env("SOCKET_NO_UPDATE_CHECK", "1"); + cmd +} + +/// Run ` --json` in `cwd` with `env`; `(exit code, envelope, stderr)`. +pub fn run_json(cwd: &Path, args: &[&str], env: &[(&str, &str)]) -> (i32, Value, String) { + let mut cmd = scrubbed_cli(); + cmd.args(args).arg("--json").current_dir(cwd); + for (k, v) in env { + cmd.env(k, v); + } + let out = cmd.output().expect("run socket-patch"); + let stdout = String::from_utf8_lossy(&out.stdout).to_string(); + let stderr = String::from_utf8_lossy(&out.stderr).to_string(); + let doc = serde_json::from_str(&stdout).unwrap_or_else(|e| { + panic!("stdout must be the JSON envelope ({e});\nstdout=\n{stdout}\nstderr=\n{stderr}") + }); + (out.status.code().unwrap_or(-1), doc, stderr) +} + +/// `scan --mode hosted --json --yes` against `server`, plus `extra`. +pub fn scan_hosted( + cwd: &Path, + server: &MockServer, + extra: &[&str], + env: &[(&str, &str)], +) -> (i32, Value) { + let cwd_s = cwd.to_str().unwrap().to_string(); + let uri = server.uri(); + let mut args = vec![ + "scan", + "--mode", + "hosted", + "--yes", + "--cwd", + &cwd_s, + "--api-url", + &uri, + "--org", + ORG, + "--api-token", + "fake", + ]; + args.extend_from_slice(extra); + let (code, doc, stderr) = run_json(cwd, &args, env); + assert!( + code == 0 || extra.contains(&"--vex"), + "scan --mode hosted must exit 0: {doc:#}\nstderr=\n{stderr}" + ); + (code, doc) +} + +pub fn warning_codes(doc: &Value) -> Vec { + doc["redirect"]["warnings"] + .as_array() + .map(|arr| { + arr.iter() + .filter_map(|w| w["code"].as_str().map(str::to_string)) + .collect() + }) + .unwrap_or_default() +} + +pub fn warning_detail(doc: &Value, code: &str) -> String { + doc["redirect"]["warnings"] + .as_array() + .into_iter() + .flatten() + .find(|w| w["code"] == code) + .and_then(|w| w["detail"].as_str()) + .unwrap_or_else(|| panic!("expected a `{code}` warning: {doc:#}")) + .to_string() +} + +pub fn redirected(doc: &Value) -> u64 { + doc["redirect"]["redirected"] + .as_u64() + .unwrap_or_else(|| panic!("{doc:#}")) +} + +/// Whether the VEX document at `path` attests [`PURL`]. +pub fn vex_attests(path: &Path) -> bool { + let Ok(text) = std::fs::read_to_string(path) else { + return false; + }; + let doc: Value = serde_json::from_str(&text).unwrap(); + doc["statements"].as_array().into_iter().flatten().any(|s| { + s["products"] + .as_array() + .into_iter() + .flatten() + .flat_map(|p| p["subcomponents"].as_array().into_iter().flatten()) + .any(|c| c["@id"] == PURL) + }) +} diff --git a/crates/socket-patch-core/src/api/client.rs b/crates/socket-patch-core/src/api/client.rs index ba3ff596..a2851c84 100644 --- a/crates/socket-patch-core/src/api/client.rs +++ b/crates/socket-patch-core/src/api/client.rs @@ -281,6 +281,12 @@ impl ApiClient { self } + /// The User-Agent-only client for grant-tokenized artifact URLs, where + /// the Socket bearer must never be sent. + pub(crate) fn plain_http(&self) -> &reqwest::Client { + &self.plain + } + /// Returns the API token, if set. pub fn api_token(&self) -> Option<&String> { self.api_token.as_ref() @@ -1266,7 +1272,7 @@ impl ApiClient { /// Cap on a single prebuilt-archive download (defensive bound against a /// runaway / hostile serve response). Generous enough for any real package. -const MAX_VENDOR_PACKAGE_BYTES: u64 = 256 * 1024 * 1024; +pub(crate) const MAX_VENDOR_PACKAGE_BYTES: u64 = 256 * 1024 * 1024; /// A prebuilt vendored archive downloaded from the patch.socket.dev service, /// together with the service-reported integrity. The bytes are **unverified** diff --git a/crates/socket-patch-core/src/patch/redirect/mod.rs b/crates/socket-patch-core/src/patch/redirect/mod.rs index ed40feca..ea8cfc74 100644 --- a/crates/socket-patch-core/src/patch/redirect/mod.rs +++ b/crates/socket-patch-core/src/patch/redirect/mod.rs @@ -43,6 +43,8 @@ mod staged; mod state; mod takeover; pub mod vlt; +pub mod vlt_heal; +pub mod vlt_preflight; pub use replay::{revert_remaining_redirect_edits, GroupRefusal, ReplayOutcome}; pub use state::{ drop_superseded_purl, load_redirect_state, persist_redirect_state, save_redirect_state, @@ -228,6 +230,9 @@ pub struct RewriteResult { /// the node grammar, a failed residual gate). Never confirmed, whichever /// lock drives. pub refused_vlt_uuids: std::collections::BTreeSet, + /// Patch uuids with a same-`name@version` vlt node under a named alias, + /// a scoped registry or jsr, which hosted mode leaves unpatched. + pub vlt_foreign_uuids: std::collections::BTreeSet, /// [`vlt::vlt_drives`] over the rewriter's input files and the /// caller's `bun_lockb_present`. pub vlt_drives: bool, @@ -335,6 +340,28 @@ pub fn rewrite_registry_redirect_with_pipenv_version( python_metadata: &BTreeMap, pipenv_major: Option, bun_lockb_present: bool, +) -> RewriteResult { + rewrite_registry_redirect_withholding_vlt( + files, + overrides, + python_metadata, + pipenv_major, + bun_lockb_present, + &std::collections::BTreeSet::new(), + ) +} + +/// [`rewrite_registry_redirect_with_pipenv_version`] with the patch uuids +/// in `vlt_withheld` kept out of the vlt rewrite only: their artifact +/// failed vlt's preflight while another npm-family lock may be the one the +/// project installs from. +pub fn rewrite_registry_redirect_withholding_vlt( + files: &BTreeMap, + overrides: &[DepOverride], + python_metadata: &BTreeMap, + pipenv_major: Option, + bun_lockb_present: bool, + vlt_withheld: &std::collections::BTreeSet, ) -> RewriteResult { let mut result = RewriteResult::default(); // pdm runs FIRST, but only when `pdm.lock` is the project's PyPI install @@ -356,7 +383,12 @@ pub fn rewrite_registry_redirect_with_pipenv_version( rewrite_yarn_classic(files, overrides, &mut result); rewrite_yarn_berry(files, overrides, &mut result); rewrite_bun_lock(files, overrides, &mut result); - vlt::rewrite_vlt_lock(files, overrides, bun_lockb_present, &mut result); + vlt::rewrite_vlt_lock( + files, + &withhold(overrides, vlt_withheld), + bun_lockb_present, + &mut result, + ); result.vlt_drives = vlt::vlt_drives(files, bun_lockb_present); requirements::rewrite(files, overrides, &mut result); rewrite_hatch(files, overrides, &mut result); diff --git a/crates/socket-patch-core/src/patch/redirect/vlt.rs b/crates/socket-patch-core/src/patch/redirect/vlt.rs index 310f1f6f..cc79bd08 100644 --- a/crates/socket-patch-core/src/patch/redirect/vlt.rs +++ b/crates/socket-patch-core/src/patch/redirect/vlt.rs @@ -69,12 +69,12 @@ fn lock_unsupported(detail: &str) -> RewriteWarning { } /// A lock that passed the lock-level parse, with its nodes section located. -struct HostedLock { +pub(super) struct HostedLock { parsed: ParsedLock, nodes: Option, } -fn parse_hosted_lock(text: &str) -> Result { +pub(super) fn parse_hosted_lock(text: &str) -> Result { let parsed = match sniff_lock(text) { LockSniff::Readable(parsed) => parsed, LockSniff::Bom => { @@ -125,6 +125,35 @@ fn registry_instance( .then(|| is_default_registry(&dep_id.first, options)) } +/// The default-registry and the foreign registry node ids of +/// `name@version`, in the lock's key order. +fn partition_instances<'a>( + nodes: &'a Map, + name: &str, + version: &str, + options: Option<&Map>, +) -> (Vec<&'a str>, Vec<&'a str>) { + let mut defaults = Vec::new(); + let mut foreign = Vec::new(); + for id in nodes.keys() { + match registry_instance(id, name, version, options) { + Some(true) => defaults.push(id.as_str()), + Some(false) => foreign.push(id.as_str()), + None => {} + } + } + (defaults, foreign) +} + +/// The default-registry node ids of `dep` in a lock that passed the +/// lock-level parse. +pub(super) fn default_instances<'a>(lock: &'a HostedLock, dep: &DepOverride) -> Vec<&'a str> { + let Some(nodes) = lock.parsed.nodes() else { + return Vec::new(); + }; + partition_instances(nodes, &full_name(dep), &dep.version, lock.parsed.options()).0 +} + fn is_old_lockfile_ignored(lock: &HostedLock, files: &BTreeMap) -> bool { if lock.parsed.version == Some(1) { return false; @@ -230,6 +259,21 @@ fn ledger_key(name: &str, version: &str, extra: Option<&str>) -> String { } } +/// The DepID a [`KIND`] ledger edit records, from its `original` entry +/// text. +pub fn edit_dep_id(edit: &FileEdit) -> Option { + let text = edit.original.as_ref()?.as_str()?; + parse_node_entry_text(text).map(|entry| entry.key.to_string()) +} + +/// The node ids of a readable `vlt-lock.json`. +pub fn lock_node_ids(text: &str) -> Option> { + match sniff_lock(text) { + LockSniff::Readable(lock) => Some(lock.nodes()?.keys().cloned().collect()), + _ => None, + } +} + /// Does a ledger edit of [`KIND`] belong to `name@version`? Claims are by /// key, with a `~` boundary before a variant's extra segment. pub(crate) fn claims_key(key: &str, name: &str, version: &str) -> bool { @@ -261,7 +305,12 @@ fn instance_line( } /// The residual gate: re-parsed, every instance carries the patched slots. -fn every_instance_pinned(text: &str, ids: &[&str], sha512: &str, url: &str) -> Option<()> { +pub(super) fn every_instance_pinned( + text: &str, + ids: &[&str], + sha512: &str, + url: &str, +) -> Option<()> { let json: Value = serde_json::from_str(text).ok()?; let nodes = json.get("nodes")?.as_object()?; ids.iter() @@ -306,16 +355,9 @@ fn rewrite_dep( let empty = Map::new(); let nodes = lock.parsed.nodes().unwrap_or(&empty); - let mut defaults: Vec<&str> = Vec::new(); - let mut foreign: Vec<&str> = Vec::new(); - for id in nodes.keys() { - match registry_instance(id, &name, version, options) { - Some(true) => defaults.push(id), - Some(false) => foreign.push(id), - None => {} - } - } + let (defaults, foreign) = partition_instances(nodes, &name, version, options); if !foreign.is_empty() { + result.vlt_foreign_uuids.insert(dep.patch_uuid.clone()); result.warnings.push(RewriteWarning { code: "redirect_vlt_custom_registry_skipped".into(), detail: format!( diff --git a/crates/socket-patch-core/src/patch/redirect/vlt_heal.rs b/crates/socket-patch-core/src/patch/redirect/vlt_heal.rs new file mode 100644 index 00000000..98f563b1 --- /dev/null +++ b/crates/socket-patch-core/src/patch/redirect/vlt_heal.rs @@ -0,0 +1,903 @@ +//! Warm-tree heal for hosted vlt redirects (DESIGN D7). +//! +//! vlt never refreshes a store entry it already holds: after the lock is +//! repointed, `vlt install` keeps `node_modules/.vlt/` and the +//! hidden lock as they are. A store entry whose bytes are not the ones the +//! lock now pins is invalidated (the entry plus `node_modules/.vlt-lock.json`) +//! so the next install extracts the pinned artifact. An entry whose state +//! cannot be determined is never removed, and nothing outside the project +//! root is ever touched. + +use std::collections::{BTreeMap, BTreeSet}; +use std::path::{Path, PathBuf}; + +use serde_json::{Map, Value}; + +use super::{hosted_patch_uuid, vlt, RedirectState}; +use crate::constants::npm_family::{VLT_HIDDEN_LOCK_REL, VLT_STORE_DIR}; +use crate::manifest::schema::PatchRecord; +use crate::patch::apply::{is_safe_relative_subpath, normalize_file_path}; +use crate::patch::file_hash::compute_file_git_sha256; +use crate::patch::package::read_archive_bytes_to_map_strict; +use crate::utils::purl::{canonical_purl, purl_parts}; +use crate::vendor::vlt_lock_text::{ + is_default_registry, is_registry_package_name, parse_node_entry_text, sniff_lock, split_dep_id, + DepIdKind, LockSniff, +}; + +/// The installed state a target should be in: patched after a redirect, +/// pristine after a rollback or remove. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Expected { + Patched, + Pristine, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TargetState { + Healthy, + Stale, + Undeterminable, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ByteCheck { + Match, + Mismatch, + Unknown, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +enum StoreState { + /// No `node_modules` or no `node_modules/.vlt`: nothing is installed. + Absent, + /// Both are real directories inside the canonical project root. + Real, + /// A link, a non-directory, or a store outside the root. + Unsafe, +} + +#[derive(Debug, Clone)] +enum HiddenLock { + Absent, + Unreadable, + Parsed(Map), +} + +/// The project's vlt install state, read once per heal. +#[derive(Debug, Clone)] +pub struct InstallState { + store: StoreState, + hidden: HiddenLock, + hidden_present: bool, +} + +/// One default-registry node of the lock that Socket hosts. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OwnedInstance { + pub dep_id: String, + pub name: String, + pub version: String, + pub patch_uuid: String, + pub url: String, + pub sha512: Option, +} + +/// A store entry to classify. +#[derive(Debug, Clone, Copy)] +pub struct Target<'a> { + pub dep_id: &'a str, + pub name: &'a str, + /// Slot [2] of the node in the final `vlt-lock.json` (`None` when the + /// node is gone or the slot is null). + pub lock_sha512: Option<&'a str>, + pub record: Option<&'a PatchRecord>, + /// The artifact bytes the preflight downloaded. + pub artifact: Option<&'a [u8]>, +} + +/// A ledger vlt node of one purl, for the rollback heal. +#[derive(Debug, Clone, PartialEq)] +pub struct LedgerTarget { + pub purl: String, + pub dep_id: String, + pub name: String, + pub record: Option, +} + +/// What invalidation removed and what it could not. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct Invalidation { + pub removed: Vec, + pub failed: Vec<(String, String)>, +} + +fn url_leaf(url: &str) -> &str { + let path = url.split(['?', '#']).next().unwrap_or(url); + path.rsplit('/').next().unwrap_or(path) +} + +/// Every default-registry node whose slot [3] is a Socket-hosted URL (on +/// patch.socket.dev or one of `origins`) whose leaf `-.tgz` +/// agrees with the DepID. An unreadable lock has none. +pub fn socket_owned_instances(lock_text: &str, origins: &[String]) -> Vec { + let LockSniff::Readable(lock) = sniff_lock(lock_text) else { + return Vec::new(); + }; + let options = lock.options(); + let Some(nodes) = lock.nodes() else { + return Vec::new(); + }; + let mut out = Vec::new(); + for (id, tuple) in nodes { + let Some(dep_id) = split_dep_id(id) else { + continue; + }; + if dep_id.kind != DepIdKind::Registry || !is_default_registry(&dep_id.first, options) { + continue; + } + let Some((name, version)) = dep_id.registry_identity() else { + continue; + }; + let Some(url) = tuple.get(3).and_then(Value::as_str) else { + continue; + }; + let Some(patch_uuid) = hosted_patch_uuid(url, origins) else { + continue; + }; + let bare = name.rsplit('/').next().unwrap_or(name); + if url_leaf(url) != format!("{bare}-{version}.tgz") { + continue; + } + out.push(OwnedInstance { + dep_id: id.clone(), + name: name.to_string(), + version: version.to_string(), + patch_uuid, + url: url.to_string(), + sha512: tuple.get(2).and_then(Value::as_str).map(str::to_string), + }); + } + out +} + +/// Slot [2] of `dep_id`'s node in `lock_text`. +pub fn lock_sha512(lock_text: &str, dep_id: &str) -> Option { + let LockSniff::Readable(lock) = sniff_lock(lock_text) else { + return None; + }; + lock.nodes()? + .get(dep_id)? + .get(2)? + .as_str() + .map(str::to_string) +} + +/// vlt's `isDepID` path-safety rule: the id is used as one path segment. +pub fn is_safe_dep_id(id: &str) -> bool { + let bytes = id.as_bytes(); + let drive = bytes.len() >= 2 && bytes[0].is_ascii_alphabetic() && bytes[1] == b':'; + !id.is_empty() + && !drive + && !id.contains(['/', '\\']) + && !id.chars().any(char::is_control) + && id + .split(['~', '·']) + .all(|field| field != "." && field != "..") +} + +async fn store_state(root: &Path) -> StoreState { + let node_modules = root.join("node_modules"); + let store = root.join(VLT_STORE_DIR); + for dir in [&node_modules, &store] { + match tokio::fs::symlink_metadata(dir).await { + Ok(meta) if meta.file_type().is_dir() => {} + Ok(_) => return StoreState::Unsafe, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => return StoreState::Absent, + Err(_) => return StoreState::Unsafe, + } + } + match ( + tokio::fs::canonicalize(root).await, + tokio::fs::canonicalize(&store).await, + ) { + (Ok(root), Ok(store)) if store.starts_with(&root) => StoreState::Real, + _ => StoreState::Unsafe, + } +} + +async fn hidden_lock(root: &Path) -> (HiddenLock, bool) { + let path = root.join(VLT_HIDDEN_LOCK_REL); + let meta = match tokio::fs::symlink_metadata(&path).await { + Ok(meta) => meta, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => return (HiddenLock::Absent, false), + Err(_) => return (HiddenLock::Unreadable, true), + }; + if !meta.file_type().is_file() { + return (HiddenLock::Unreadable, true); + } + let parsed = crate::utils::fs::read_regular_to_string(&path) + .await + .ok() + .and_then(|text| serde_json::from_str::(&text).ok()) + .and_then(|json| json.get("nodes").and_then(Value::as_object).cloned()); + match parsed { + Some(nodes) => (HiddenLock::Parsed(nodes), true), + None => (HiddenLock::Unreadable, true), + } +} + +/// Read the store precondition and the hidden lock. +pub async fn read_install_state(root: &Path) -> InstallState { + let store = store_state(root).await; + let (hidden, hidden_present) = hidden_lock(root).await; + InstallState { + store, + hidden, + hidden_present, + } +} + +fn package_dir(root: &Path, dep_id: &str, name: &str) -> PathBuf { + let mut dir = root.join(VLT_STORE_DIR).join(dep_id).join("node_modules"); + for part in name.split('/') { + dir.push(part); + } + dir +} + +async fn file_hash(path: &Path) -> Result, ()> { + match compute_file_git_sha256(path).await { + Ok(hash) => Ok(Some(hash)), + Err(e) if crate::patch::apply::is_missing_path(&e) => Ok(None), + Err(_) => Err(()), + } +} + +async fn record_check(dir: &Path, record: &PatchRecord, expected: Expected) -> ByteCheck { + let mut unknown = false; + for (file, info) in &record.files { + let rel = normalize_file_path(file); + if !is_safe_relative_subpath(rel) { + return ByteCheck::Unknown; + } + let Ok(current) = file_hash(&dir.join(rel)).await else { + unknown = true; + continue; + }; + let want = match expected { + Expected::Patched => &info.after_hash, + Expected::Pristine => &info.before_hash, + }; + let matches = match current.as_deref() { + None => want.is_empty(), + Some(hash) => hash == want.as_str(), + }; + if !matches { + return ByteCheck::Mismatch; + } + } + if unknown { + ByteCheck::Unknown + } else { + ByteCheck::Match + } +} + +/// The regular files under `dir` (its own `node_modules/` excluded) by +/// `/`-joined relative path; `Err(true)` when a link or special file is +/// present (never an extracted artifact), `Err(false)` when unreadable. +fn installed_files(dir: &Path) -> Result>, bool> { + let mut out = BTreeMap::new(); + let walk = walkdir::WalkDir::new(dir) + .follow_links(false) + .into_iter() + .filter_entry(|e| !(e.depth() == 1 && e.file_name() == "node_modules")); + for entry in walk { + let entry = entry.map_err(|_| false)?; + if entry.depth() == 0 || entry.file_type().is_dir() { + continue; + } + if !entry.file_type().is_file() { + return Err(true); + } + let rel = entry.path().strip_prefix(dir).map_err(|_| false)?; + let key = rel + .components() + .map(|c| c.as_os_str().to_str()) + .collect::>>() + .ok_or(false)? + .join("/"); + out.insert(key, std::fs::read(entry.path()).map_err(|_| false)?); + } + Ok(out) +} + +fn artifact_check(dir: &Path, artifact: &[u8]) -> ByteCheck { + let Ok(expected) = read_archive_bytes_to_map_strict(artifact) else { + return ByteCheck::Unknown; + }; + let installed = match installed_files(dir) { + Ok(files) => files, + Err(true) => return ByteCheck::Mismatch, + Err(false) => return ByteCheck::Unknown, + }; + let expected: BTreeMap> = expected.into_iter().collect(); + if expected == installed { + ByteCheck::Match + } else { + ByteCheck::Mismatch + } +} + +async fn bytes_check(dir: &Path, target: &Target<'_>, expected: Expected) -> ByteCheck { + if let Some(record) = target.record.filter(|r| !r.files.is_empty()) { + return record_check(dir, record, expected).await; + } + match (expected, target.artifact) { + (Expected::Patched, Some(artifact)) => artifact_check(dir, artifact), + _ => ByteCheck::Unknown, + } +} + +/// Is `target`'s store entry stale against `expected`, healthy, or +/// impossible to judge? See DESIGN §3.9 "Heal" for the rules. +pub async fn classify_target( + state: &InstallState, + root: &Path, + target: &Target<'_>, + expected: Expected, +) -> TargetState { + match state.store { + StoreState::Absent => return TargetState::Healthy, + StoreState::Unsafe => return TargetState::Undeterminable, + StoreState::Real => {} + } + if !is_safe_dep_id(target.dep_id) || !is_registry_package_name(target.name) { + return TargetState::Undeterminable; + } + let dir = package_dir(root, target.dep_id, target.name); + if !tokio::fs::metadata(&dir).await.is_ok_and(|m| m.is_dir()) { + return TargetState::Healthy; + } + let bytes = bytes_check(&dir, target, expected).await; + if let HiddenLock::Parsed(nodes) = &state.hidden { + match nodes.get(target.dep_id) { + None => return TargetState::Stale, + Some(node) => { + if node.get(2).and_then(Value::as_str) != target.lock_sha512 { + return TargetState::Stale; + } + } + } + } + if bytes == ByteCheck::Mismatch { + return TargetState::Stale; + } + if !matches!(state.hidden, HiddenLock::Parsed(_)) && bytes == ByteCheck::Unknown { + return TargetState::Undeterminable; + } + TargetState::Healthy +} + +async fn remove_entry(path: &Path) -> std::io::Result<()> { + let meta = tokio::fs::symlink_metadata(path).await?; + if meta.file_type().is_symlink() { + crate::utils::fs::remove_link(path).await + } else if meta.file_type().is_dir() { + tokio::fs::remove_dir_all(path).await + } else { + tokio::fs::remove_file(path).await + } +} + +/// Remove the hidden lock and each stale store entry. Callers pass only +/// ids [`classify_target`] judged stale, which requires a real store dir. +pub async fn invalidate(root: &Path, state: &InstallState, stale: &[String]) -> Invalidation { + let mut out = Invalidation::default(); + if stale.is_empty() || state.store != StoreState::Real { + return out; + } + if state.hidden_present { + if let Err(e) = remove_entry(&root.join(VLT_HIDDEN_LOCK_REL)).await { + if e.kind() != std::io::ErrorKind::NotFound { + out.failed + .push((VLT_HIDDEN_LOCK_REL.to_string(), e.to_string())); + } + } + } + let unique: BTreeSet<&String> = stale.iter().collect(); + for id in unique { + if !is_safe_dep_id(id) { + continue; + } + match remove_entry(&root.join(VLT_STORE_DIR).join(id)).await { + Ok(()) => out.removed.push(id.clone()), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => out.removed.push(id.clone()), + Err(e) => out.failed.push((id.clone(), e.to_string())), + } + } + out +} + +/// The vlt nodes the ledger's `redirect_vlt_lock_node` edits name for each +/// of `purls`, with the purl's patch record. +pub fn ledger_targets(state: &RedirectState, purls: &[String]) -> Vec { + let mut out: Vec = Vec::new(); + for purl in purls { + let Some((ecosystem, name, version)) = purl_parts(purl) else { + continue; + }; + if ecosystem != "npm" { + continue; + } + let canon = canonical_purl(purl); + let record = state + .records + .iter() + .find(|(key, _)| canonical_purl(key) == canon) + .map(|(_, record)| record.clone()); + for edit in &state.edits { + if edit.kind != vlt::KIND + || !edit + .key + .as_deref() + .is_some_and(|key| vlt::claims_key(key, &name, &version)) + { + continue; + } + let Some(entry) = edit + .original + .as_ref() + .and_then(Value::as_str) + .and_then(parse_node_entry_text) + else { + continue; + }; + if out.iter().any(|t| t.dep_id == entry.key && t.purl == *purl) { + continue; + } + out.push(LedgerTarget { + purl: purl.clone(), + dep_id: entry.key.to_string(), + name: name.clone(), + record: record.clone(), + }); + } + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::hash::git_sha256::compute_git_sha256_from_bytes; + use crate::manifest::schema::PatchFileInfo; + use crate::patch::redirect::FileEdit; + use std::collections::HashMap; + + const ID: &str = "~npm~left-pad@1.3.0"; + const PRISTINE: &[u8] = b"module.exports = 'pristine'\n"; + const PATCHED: &[u8] = b"module.exports = 'patched'\n"; + const LOCK_SHA: &str = "sha512-PATCHED"; + + fn record() -> PatchRecord { + PatchRecord { + uuid: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa".into(), + exported_at: String::new(), + files: HashMap::from([( + "package/index.js".to_string(), + PatchFileInfo { + before_hash: compute_git_sha256_from_bytes(PRISTINE), + after_hash: compute_git_sha256_from_bytes(PATCHED), + }, + )]), + vulnerabilities: HashMap::new(), + description: String::new(), + license: String::new(), + tier: String::new(), + } + } + + fn store(root: &Path, id: &str, index: &[u8]) -> PathBuf { + let dir = package_dir(root, id, "left-pad"); + std::fs::create_dir_all(&dir).unwrap(); + std::fs::write(dir.join("index.js"), index).unwrap(); + std::fs::write(dir.join("package.json"), b"{\"name\":\"left-pad\"}").unwrap(); + dir + } + + fn hidden(root: &Path, slot2: Option<&str>) { + let nodes = match slot2 { + Some(sha) => serde_json::json!({ ID: [0, "left-pad", sha, null] }), + None => serde_json::json!({}), + }; + std::fs::write( + root.join(VLT_HIDDEN_LOCK_REL), + serde_json::to_vec(&serde_json::json!({ "nodes": nodes })).unwrap(), + ) + .unwrap(); + } + + fn target<'a>(record: Option<&'a PatchRecord>, artifact: Option<&'a [u8]>) -> Target<'a> { + Target { + dep_id: ID, + name: "left-pad", + lock_sha512: Some(LOCK_SHA), + record, + artifact, + } + } + + async fn classify(root: &Path, target: &Target<'_>, expected: Expected) -> TargetState { + let state = read_install_state(root).await; + classify_target(&state, root, target, expected).await + } + + fn tarball(entries: &[(&str, &[u8])]) -> Vec { + let gz = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default()); + let mut builder = tar::Builder::new(gz); + for (name, data) in entries { + let mut header = tar::Header::new_gnu(); + header.set_size(data.len() as u64); + header.set_mode(0o644); + header.set_cksum(); + builder.append_data(&mut header, name, *data).unwrap(); + } + builder.into_inner().unwrap().finish().unwrap() + } + + #[tokio::test] + async fn rule_a_hidden_lock_integrity_differs() { + let tmp = tempfile::tempdir().unwrap(); + store(tmp.path(), ID, PATCHED); + hidden(tmp.path(), Some("sha512-UPSTREAM")); + let rec = record(); + assert_eq!( + classify(tmp.path(), &target(Some(&rec), None), Expected::Patched).await, + TargetState::Stale + ); + } + + #[tokio::test] + async fn rule_b_hidden_lock_without_the_node() { + let tmp = tempfile::tempdir().unwrap(); + store(tmp.path(), ID, PATCHED); + hidden(tmp.path(), None); + let rec = record(); + assert_eq!( + classify(tmp.path(), &target(Some(&rec), None), Expected::Patched).await, + TargetState::Stale + ); + } + + #[tokio::test] + async fn rule_c_bytes_mismatch_whatever_the_hidden_lock_says() { + let tmp = tempfile::tempdir().unwrap(); + store(tmp.path(), ID, PRISTINE); + hidden(tmp.path(), Some(LOCK_SHA)); + let rec = record(); + assert_eq!( + classify(tmp.path(), &target(Some(&rec), None), Expected::Patched).await, + TargetState::Stale + ); + std::fs::remove_file(tmp.path().join(VLT_HIDDEN_LOCK_REL)).unwrap(); + assert_eq!( + classify(tmp.path(), &target(Some(&rec), None), Expected::Patched).await, + TargetState::Stale + ); + } + + #[tokio::test] + async fn healthy_patched_and_pristine_trees() { + let tmp = tempfile::tempdir().unwrap(); + store(tmp.path(), ID, PATCHED); + hidden(tmp.path(), Some(LOCK_SHA)); + let rec = record(); + assert_eq!( + classify(tmp.path(), &target(Some(&rec), None), Expected::Patched).await, + TargetState::Healthy + ); + std::fs::remove_file(tmp.path().join(VLT_HIDDEN_LOCK_REL)).unwrap(); + assert_eq!( + classify(tmp.path(), &target(Some(&rec), None), Expected::Patched).await, + TargetState::Healthy, + "a tree without a hidden lock is judged by its bytes" + ); + assert_eq!( + classify(tmp.path(), &target(Some(&rec), None), Expected::Pristine).await, + TargetState::Stale, + "patched bytes are stale once the pin is restored" + ); + store(tmp.path(), ID, PRISTINE); + assert_eq!( + classify(tmp.path(), &target(Some(&rec), None), Expected::Pristine).await, + TargetState::Healthy + ); + } + + #[tokio::test] + async fn undeterminable_without_hidden_lock_record_or_artifact() { + let tmp = tempfile::tempdir().unwrap(); + store(tmp.path(), ID, PATCHED); + assert_eq!( + classify(tmp.path(), &target(None, None), Expected::Patched).await, + TargetState::Undeterminable + ); + hidden(tmp.path(), Some(LOCK_SHA)); + assert_eq!( + classify(tmp.path(), &target(None, None), Expected::Patched).await, + TargetState::Healthy, + "a parsed hidden lock that agrees decides alone" + ); + std::fs::write(tmp.path().join(VLT_HIDDEN_LOCK_REL), b"{not json").unwrap(); + assert_eq!( + classify(tmp.path(), &target(None, None), Expected::Pristine).await, + TargetState::Undeterminable + ); + } + + #[tokio::test] + async fn no_record_compares_against_the_artifact_bytes() { + let tmp = tempfile::tempdir().unwrap(); + store(tmp.path(), ID, PATCHED); + let dir = package_dir(tmp.path(), ID, "left-pad"); + std::fs::create_dir_all(dir.join("node_modules/dep")).unwrap(); + std::fs::write(dir.join("node_modules/dep/x.js"), b"ignored").unwrap(); + let good = tarball(&[ + ("package/index.js", PATCHED), + ("package/package.json", b"{\"name\":\"left-pad\"}"), + ]); + let bad = tarball(&[ + ("package/index.js", PRISTINE), + ("package/package.json", b"{\"name\":\"left-pad\"}"), + ]); + assert_eq!( + classify(tmp.path(), &target(None, Some(&good)), Expected::Patched).await, + TargetState::Healthy + ); + assert_eq!( + classify(tmp.path(), &target(None, Some(&bad)), Expected::Patched).await, + TargetState::Stale + ); + std::fs::write(dir.join("extra.js"), b"x").unwrap(); + assert_eq!( + classify(tmp.path(), &target(None, Some(&good)), Expected::Patched).await, + TargetState::Stale, + "an extra installed file is not the artifact" + ); + assert_eq!( + classify( + tmp.path(), + &target(None, Some(b"not a tarball")), + Expected::Patched + ) + .await, + TargetState::Undeterminable + ); + } + + #[tokio::test] + async fn an_uninstalled_target_is_healthy() { + let tmp = tempfile::tempdir().unwrap(); + let rec = record(); + assert_eq!( + classify(tmp.path(), &target(Some(&rec), None), Expected::Patched).await, + TargetState::Healthy + ); + std::fs::create_dir_all(tmp.path().join(VLT_STORE_DIR)).unwrap(); + assert_eq!( + classify(tmp.path(), &target(Some(&rec), None), Expected::Patched).await, + TargetState::Healthy + ); + } + + #[cfg(unix)] + #[tokio::test] + async fn a_linked_node_modules_or_store_outside_the_root_is_undeterminable_and_kept() { + for linked in ["node_modules", VLT_STORE_DIR] { + let tmp = tempfile::tempdir().unwrap(); + let project = tmp.path().join("project"); + let outside = tmp.path().join("outside"); + std::fs::create_dir_all(&project).unwrap(); + let real_root = outside.join("root"); + store(&real_root, ID, PRISTINE); + if linked == "node_modules" { + std::os::unix::fs::symlink(real_root.join("node_modules"), project.join(linked)) + .unwrap(); + } else { + std::fs::create_dir_all(project.join("node_modules")).unwrap(); + std::os::unix::fs::symlink(real_root.join(VLT_STORE_DIR), project.join(linked)) + .unwrap(); + } + hidden(&real_root, Some("sha512-UPSTREAM")); + let rec = record(); + let state = read_install_state(&project).await; + assert_eq!( + classify_target( + &state, + &project, + &target(Some(&rec), None), + Expected::Patched + ) + .await, + TargetState::Undeterminable, + "{linked}" + ); + let out = invalidate(&project, &state, &[ID.to_string()]).await; + assert!(out.removed.is_empty() && out.failed.is_empty(), "{out:?}"); + assert!(package_dir(&real_root, ID, "left-pad") + .join("index.js") + .exists()); + } + } + + #[test] + fn dep_ids_are_single_safe_segments() { + for ok in [ + ID, + "··left-pad@1.3.0", + "~npm~@a+b@1.0.0~peer.2", + "·npm·x@1.0.0", + ] { + assert!(is_safe_dep_id(ok), "{ok}"); + } + for bad in [ + "", + "..", + ".", + "~npm~../x@1", + "~..~x@1.0.0", + "a/b", + "a\\b", + "C:x", + "~npm~x@1.0.0\n", + ] { + assert!(!is_safe_dep_id(bad), "{bad:?}"); + } + } + + #[tokio::test] + async fn invalidation_removes_the_hidden_lock_and_stale_entries_only() { + let tmp = tempfile::tempdir().unwrap(); + store(tmp.path(), ID, PRISTINE); + let other = "~npm~ms@2.1.3"; + let other_dir = package_dir(tmp.path(), other, "left-pad"); + std::fs::create_dir_all(&other_dir).unwrap(); + hidden(tmp.path(), Some("sha512-UPSTREAM")); + let state = read_install_state(tmp.path()).await; + let out = invalidate(tmp.path(), &state, &[ID.to_string(), "../escape".into()]).await; + assert_eq!(out.removed, [ID.to_string()]); + assert!(out.failed.is_empty()); + assert!(!tmp.path().join(VLT_HIDDEN_LOCK_REL).exists()); + assert!(!tmp.path().join(VLT_STORE_DIR).join(ID).exists()); + assert!(other_dir.exists()); + } + + #[cfg(unix)] + #[tokio::test] + async fn a_linked_store_entry_is_unlinked_not_traversed() { + let tmp = tempfile::tempdir().unwrap(); + let target_root = tmp.path().join("elsewhere"); + store(&target_root, ID, PRISTINE); + let root = tmp.path().join("project"); + std::fs::create_dir_all(root.join(VLT_STORE_DIR)).unwrap(); + std::os::unix::fs::symlink( + target_root.join(VLT_STORE_DIR).join(ID), + root.join(VLT_STORE_DIR).join(ID), + ) + .unwrap(); + let dep_link = root.join(VLT_STORE_DIR).join("~npm~ms@2.1.3"); + std::fs::create_dir_all(dep_link.join("node_modules")).unwrap(); + std::os::unix::fs::symlink( + target_root.join(VLT_STORE_DIR), + dep_link.join("node_modules/linked"), + ) + .unwrap(); + let state = read_install_state(&root).await; + let out = invalidate(&root, &state, &[ID.to_string(), "~npm~ms@2.1.3".into()]).await; + assert!(out.failed.is_empty(), "{out:?}"); + assert!(!root.join(VLT_STORE_DIR).join(ID).exists()); + assert!( + package_dir(&target_root, ID, "left-pad") + .join("index.js") + .exists(), + "the link target survives" + ); + assert!(!dep_link.exists()); + assert!(target_root.join(VLT_STORE_DIR).exists()); + } + + #[cfg(windows)] + #[tokio::test] + async fn store_entries_with_junction_and_dir_symlink_children_are_removed_alone() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let sibling = "~npm~ms@2.1.3"; + let sibling_dir = package_dir(root, sibling, "ms"); + std::fs::create_dir_all(&sibling_dir).unwrap(); + std::fs::write(sibling_dir.join("index.js"), b"ms").unwrap(); + let entry = root.join(VLT_STORE_DIR).join(ID).join("node_modules"); + std::fs::create_dir_all(entry.join("left-pad")).unwrap(); + let status = std::process::Command::new("cmd") + .args(["/C", "mklink", "/J"]) + .arg(entry.join("ms")) + .arg( + root.join(VLT_STORE_DIR) + .join(sibling) + .join("node_modules/ms"), + ) + .status() + .unwrap(); + assert!(status.success()); + if std::os::windows::fs::symlink_dir( + root.join(VLT_STORE_DIR).join(sibling), + entry.join("dir-link"), + ) + .is_err() + { + eprintln!("dir symlinks need Developer Mode; junction leg only"); + } + let state = read_install_state(root).await; + let out = invalidate(root, &state, &[ID.to_string()]).await; + assert!(out.failed.is_empty(), "{out:?}"); + assert!(!root.join(VLT_STORE_DIR).join(ID).exists()); + assert!(sibling_dir.join("index.js").exists()); + } + + #[test] + fn owned_instances_need_a_socket_url_whose_leaf_matches_the_dep_id() { + let lock = r#"{ + "lockfileVersion": 1, + "options": {}, + "nodes": { + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-P","https://patch.socket.dev/patch/npm/t/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/left-pad-1.3.0.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-M","https://patch.socket.dev/patch/npm/t/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/left-pad-1.3.0.tgz"], + "~npm~@a+b@1.0.0": [0,"@a/b","sha512-S","http://localhost:4026/patch/npm/t/cccccccc-cccc-4ccc-8ccc-cccccccccccc/b-1.0.0.tgz"], + "~custom~c@1.0.0": [0,"c","sha512-C","https://patch.socket.dev/patch/npm/t/dddddddd-dddd-4ddd-8ddd-dddddddddddd/c-1.0.0.tgz"], + "~npm~d@1.0.0": [0,"d","sha512-D","https://registry.npmjs.org/d/-/d-1.0.0.tgz"] + }, + "edges": {} +} +"#; + let ids: Vec = socket_owned_instances(lock, &[]) + .into_iter() + .map(|i| i.dep_id) + .collect(); + assert_eq!(ids, ["~npm~left-pad@1.3.0"]); + let with_origin = socket_owned_instances(lock, &["http://localhost:4026".into()]); + let scoped = with_origin.iter().find(|i| i.name == "@a/b").unwrap(); + assert_eq!(scoped.patch_uuid, "cccccccc-cccc-4ccc-8ccc-cccccccccccc"); + assert_eq!(scoped.sha512.as_deref(), Some("sha512-S")); + assert_eq!( + lock_sha512(lock, "~npm~d@1.0.0").as_deref(), + Some("sha512-D") + ); + assert!(socket_owned_instances("\u{feff}{}", &[]).is_empty()); + } + + #[test] + fn ledger_targets_follow_the_claimed_edits() { + let mut state = RedirectState::new(); + let edit = |key: &str, id: &str| FileEdit { + path: "vlt-lock.json".into(), + kind: vlt::KIND.into(), + action: "rewritten".into(), + key: Some(key.into()), + original: Some(Value::String(format!("\"{id}\": [0,\"x\"]"))), + new: Some(Value::String(format!("\"{id}\": [0,\"x\",\"s\",\"u\"]"))), + }; + state.edits = vec![ + edit("left-pad@1.3.0", "~npm~left-pad@1.3.0"), + edit("left-pad@1.3.0~peer.2", "~npm~left-pad@1.3.0~peer.2"), + edit("left-pad@1.3.1", "~npm~left-pad@1.3.1"), + ]; + state + .records + .insert("pkg:npm/left-pad@1.3.0".into(), record()); + let targets = ledger_targets(&state, &["pkg:npm/left-pad@1.3.0".into()]); + let ids: Vec<&str> = targets.iter().map(|t| t.dep_id.as_str()).collect(); + assert_eq!(ids, ["~npm~left-pad@1.3.0", "~npm~left-pad@1.3.0~peer.2"]); + assert!(targets.iter().all(|t| t.record.is_some())); + } +} diff --git a/crates/socket-patch-core/src/patch/redirect/vlt_preflight.rs b/crates/socket-patch-core/src/patch/redirect/vlt_preflight.rs new file mode 100644 index 00000000..1875b7d2 --- /dev/null +++ b/crates/socket-patch-core/src/patch/redirect/vlt_preflight.rs @@ -0,0 +1,503 @@ +//! The hosted vlt artifact preflight (`redirect_vlt_artifact_unverifiable`). +//! +//! vlt always sends `accept-encoding: gzip`, and it hashes the bytes it +//! received, so a server that re-gzips the archive makes every install fail +//! `EINTEGRITY`. Before a vlt lock is pinned to a hosted artifact, the +//! artifact is fetched the way vlt fetches it and hashed raw: the workspace +//! reqwest is built without gzip/brotli, so bodies are never decoded. + +use std::collections::{BTreeMap, BTreeSet}; +use std::time::Duration; + +use base64::Engine as _; +use sha2::{Digest, Sha512}; + +use super::{vlt, DepOverride}; +use crate::api::client::{ApiClient, MAX_VENDOR_PACKAGE_BYTES}; +use crate::constants::npm_family::VLT_LOCK; +use crate::utils::http::{read_capped_typed, ReadCappedError}; + +/// The `accept-encoding` vlt sends for registry tarballs. +pub const VLT_ACCEPT_ENCODING: &str = "gzip;q=1.0, identity;q=0.5"; + +/// The failure reason for a run that may not touch the network. +pub const OFFLINE_REASON: &str = "offline"; + +const MAX_CONCURRENT_PROBES: usize = 4; +const HEADERS_TIMEOUT: Duration = Duration::from_secs(60); +const BODY_TIMEOUT: Duration = Duration::from_secs(300); + +/// What fetching one artifact URL as vlt does returned. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct ArtifactProbe { + pub status: Option, + pub content_encoding: Option, + /// SRI form (`sha512-`) of the raw body. + pub sha512: Option, + /// The raw body, kept for the warm-tree heal's no-record comparison. + pub body: Option>, + pub error: Option, +} + +impl ArtifactProbe { + /// Why vlt would fail to verify this artifact against `sha512`, or + /// `None` when it passes. + pub fn failure(&self, sha512: &str) -> Option { + if let Some(error) = &self.error { + return Some(format!("fetch error {error}")); + } + match self.status { + Some(200) => {} + Some(status) => return Some(format!("http {status}")), + None => return Some("fetch error no response".to_string()), + } + if let Some(encoding) = self + .content_encoding + .as_deref() + .map(str::trim) + .filter(|e| !e.is_empty() && !e.eq_ignore_ascii_case("identity")) + { + return Some(format!("content-encoding {encoding}")); + } + if self.sha512.as_deref() != Some(sha512) { + return Some("sha512 mismatch".to_string()); + } + None + } +} + +/// The SRI form of `bytes`' sha512. +pub fn sha512_sri(bytes: &[u8]) -> String { + format!( + "sha512-{}", + base64::engine::general_purpose::STANDARD.encode(Sha512::digest(bytes)) + ) +} + +fn fetch_error(error: impl Into) -> ArtifactProbe { + ArtifactProbe { + error: Some(error.into()), + ..ArtifactProbe::default() + } +} + +/// `GET url` with vlt's `accept-encoding` (and reqwest's default +/// `Accept: */*`), following up to ten redirects, the body capped like +/// every other artifact download. +pub async fn fetch_artifact_probe(client: &reqwest::Client, url: &str) -> ArtifactProbe { + fetch_capped(client, url, MAX_VENDOR_PACKAGE_BYTES).await +} + +async fn fetch_capped(client: &reqwest::Client, url: &str, max: u64) -> ArtifactProbe { + if !(url.starts_with("https://") || url.starts_with("http://")) { + return fetch_error("refusing a non-http(s) artifact URL"); + } + let sent = tokio::time::timeout( + HEADERS_TIMEOUT, + client + .get(url) + .header(reqwest::header::ACCEPT_ENCODING, VLT_ACCEPT_ENCODING) + .send(), + ) + .await; + let resp = match sent { + Ok(Ok(resp)) => resp, + Ok(Err(e)) => return fetch_error(e.without_url().to_string()), + Err(_) => return fetch_error(format!("no response within {HEADERS_TIMEOUT:?}")), + }; + let status = resp.status().as_u16(); + let content_encoding = resp + .headers() + .get(reqwest::header::CONTENT_ENCODING) + .map(|v| String::from_utf8_lossy(v.as_bytes()).into_owned()); + if status != 200 { + return ArtifactProbe { + status: Some(status), + content_encoding, + ..ArtifactProbe::default() + }; + } + let body = tokio::time::timeout( + BODY_TIMEOUT, + read_capped_typed(resp, max, "hosted artifact"), + ) + .await + .unwrap_or_else(|_| { + Err(ReadCappedError::Truncated(format!( + "hosted artifact body not received within {BODY_TIMEOUT:?}" + ))) + }); + match body { + Ok(bytes) => ArtifactProbe { + status: Some(status), + content_encoding, + sha512: Some(sha512_sri(&bytes)), + body: Some(bytes), + error: None, + }, + Err(e) => ArtifactProbe { + status: Some(status), + content_encoding, + error: Some(e.to_string()), + ..ArtifactProbe::default() + }, + } +} + +/// Probe every distinct URL in `urls` through `api`'s plain client (no +/// `Authorization`: the grant token is in the URL), at most four at a time. +pub async fn probe_artifacts( + api: &ApiClient, + urls: &BTreeSet, +) -> BTreeMap { + let semaphore = std::sync::Arc::new(tokio::sync::Semaphore::new(MAX_CONCURRENT_PROBES)); + let mut tasks = tokio::task::JoinSet::new(); + for url in urls { + let client = api.plain_http().clone(); + let semaphore = semaphore.clone(); + let url = url.clone(); + tasks.spawn(async move { + let _permit = semaphore.acquire_owned().await; + let probe = fetch_artifact_probe(&client, &url).await; + (url, probe) + }); + } + let mut out = BTreeMap::new(); + while let Some(joined) = tasks.join_next().await { + if let Ok((url, probe)) = joined { + out.insert(url, probe); + } + } + for url in urls { + out.entry(url.clone()) + .or_insert_with(|| fetch_error("probe task failed")); + } + out +} + +/// One override the preflight must probe. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PreflightDep { + pub patch_uuid: String, + pub artifact_url: String, + pub sha512: String, + /// Every default-registry instance already carries this URL and sha512 + /// (an earlier run pinned it). + pub already_pinned: bool, +} + +/// The npm overrides the preflight probes: `vlt-lock.json` passes the +/// lock-level parse, the override has a sha512, and it has at least one +/// default-registry instance in the lock. Nothing is probed without a +/// `vlt-lock.json`. +pub fn preflight_scope( + files: &BTreeMap, + overrides: &[DepOverride], +) -> Vec { + let Some(text) = files.get(VLT_LOCK) else { + return Vec::new(); + }; + let Ok(lock) = vlt::parse_hosted_lock(text) else { + return Vec::new(); + }; + overrides + .iter() + .filter(|dep| dep.ecosystem == "npm") + .filter_map(|dep| { + let sha512 = dep.integrity.sha512.as_deref().filter(|s| !s.is_empty())?; + let ids = vlt::default_instances(&lock, dep); + if ids.is_empty() { + return None; + } + let already_pinned = + vlt::every_instance_pinned(text, &ids, sha512, &dep.artifact_url).is_some(); + Some(PreflightDep { + patch_uuid: dep.patch_uuid.clone(), + artifact_url: dep.artifact_url.clone(), + sha512: sha512.to_string(), + already_pinned, + }) + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::api::client::ApiClientOptions; + use crate::patch::redirect::Integrity; + use std::io::Write as _; + use wiremock::matchers::{method, path}; + use wiremock::{Mock, MockServer, Request, ResponseTemplate}; + + const BODY: &[u8] = b"patched tarball bytes"; + + fn api(token: Option<&str>) -> ApiClient { + ApiClient::new(ApiClientOptions { + api_url: "http://127.0.0.1:9".to_string(), + api_token: token.map(str::to_string), + use_public_proxy: false, + org_slug: Some("org".to_string()), + }) + } + + async fn probe_of(server: &MockServer, route: &str) -> ArtifactProbe { + fetch_artifact_probe(api(None).plain_http(), &format!("{}{route}", server.uri())).await + } + + fn gzip(bytes: &[u8]) -> Vec { + let mut enc = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default()); + enc.write_all(bytes).unwrap(); + enc.finish().unwrap() + } + + #[tokio::test] + async fn identity_body_with_the_granted_sha512_passes() { + let server = MockServer::start().await; + Mock::given(method("GET")) + .and(path("/a.tgz")) + .respond_with(ResponseTemplate::new(200).set_body_bytes(BODY)) + .mount(&server) + .await; + let probe = probe_of(&server, "/a.tgz").await; + assert_eq!(probe.failure(&sha512_sri(BODY)), None); + assert_eq!(probe.body.as_deref(), Some(BODY)); + } + + #[tokio::test] + async fn an_explicit_identity_encoding_passes() { + let server = MockServer::start().await; + Mock::given(method("GET")) + .respond_with( + ResponseTemplate::new(200) + .insert_header("content-encoding", "identity") + .set_body_bytes(BODY), + ) + .mount(&server) + .await; + assert_eq!( + probe_of(&server, "/a.tgz").await.failure(&sha512_sri(BODY)), + None + ); + } + + #[tokio::test] + async fn every_failure_reason() { + let server = MockServer::start().await; + let gz = gzip(BODY); + Mock::given(method("GET")) + .and(path("/gz.tgz")) + .respond_with( + ResponseTemplate::new(200) + .insert_header("content-encoding", "gzip") + .set_body_bytes(gz.clone()), + ) + .mount(&server) + .await; + Mock::given(method("GET")) + .and(path("/other.tgz")) + .respond_with(ResponseTemplate::new(200).set_body_bytes(b"other".to_vec())) + .mount(&server) + .await; + Mock::given(method("GET")) + .and(path("/missing.tgz")) + .respond_with(ResponseTemplate::new(404)) + .mount(&server) + .await; + Mock::given(method("GET")) + .and(path("/broken.tgz")) + .respond_with(ResponseTemplate::new(500)) + .mount(&server) + .await; + let expected = sha512_sri(BODY); + + let gz_probe = probe_of(&server, "/gz.tgz").await; + assert_eq!( + gz_probe.failure(&expected).as_deref(), + Some("content-encoding gzip") + ); + assert_eq!( + gz_probe.sha512, + Some(sha512_sri(&gz)), + "the body is hashed as received, never decoded" + ); + assert_eq!( + probe_of(&server, "/other.tgz") + .await + .failure(&expected) + .as_deref(), + Some("sha512 mismatch") + ); + assert_eq!( + probe_of(&server, "/missing.tgz") + .await + .failure(&expected) + .as_deref(), + Some("http 404") + ); + assert_eq!( + probe_of(&server, "/broken.tgz") + .await + .failure(&expected) + .as_deref(), + Some("http 500") + ); + let refused = + fetch_artifact_probe(api(None).plain_http(), "http://127.0.0.1:9/a.tgz").await; + assert!( + refused + .failure(&expected) + .is_some_and(|r| r.starts_with("fetch error ")), + "{refused:?}" + ); + let scheme = fetch_artifact_probe(api(None).plain_http(), "file:///etc/passwd").await; + assert!(scheme + .failure(&expected) + .is_some_and(|r| r.starts_with("fetch error "))); + } + + #[tokio::test] + async fn sends_vlt_accept_encoding_and_no_authorization() { + let server = MockServer::start().await; + Mock::given(method("GET")) + .respond_with(ResponseTemplate::new(200).set_body_bytes(BODY)) + .expect(1) + .mount(&server) + .await; + let urls: BTreeSet = [format!("{}/a.tgz", server.uri())].into(); + let probes = probe_artifacts(&api(Some("sktsec_secret_token")), &urls).await; + assert_eq!(probes.len(), 1); + let requests: Vec = server.received_requests().await.unwrap(); + assert_eq!(requests.len(), 1); + assert_eq!( + requests[0] + .headers + .get("accept-encoding") + .map(|v| v.as_bytes()), + Some(VLT_ACCEPT_ENCODING.as_bytes()) + ); + assert!(requests[0].headers.get("authorization").is_none()); + assert!(requests[0] + .headers + .get("accept") + .is_none_or(|v| v.as_bytes() == b"*/*")); + } + + #[tokio::test] + async fn redirect_chains_of_ten_pass_and_eleven_fail() { + let server = MockServer::start().await; + for hop in 0..11 { + Mock::given(method("GET")) + .and(path(format!("/r{hop}"))) + .respond_with( + ResponseTemplate::new(302) + .insert_header("location", format!("{}/r{}", server.uri(), hop + 1)), + ) + .mount(&server) + .await; + } + Mock::given(method("GET")) + .and(path("/r11")) + .respond_with(ResponseTemplate::new(200).set_body_bytes(BODY)) + .mount(&server) + .await; + let expected = sha512_sri(BODY); + assert_eq!(probe_of(&server, "/r1").await.failure(&expected), None); + assert!(probe_of(&server, "/r0") + .await + .failure(&expected) + .is_some_and(|r| r.starts_with("fetch error "))); + } + + #[tokio::test] + async fn a_body_over_the_cap_is_a_fetch_error() { + let server = MockServer::start().await; + Mock::given(method("GET")) + .respond_with(ResponseTemplate::new(200).set_body_bytes(BODY)) + .mount(&server) + .await; + let url = format!("{}/big.tgz", server.uri()); + let probe = fetch_capped(api(None).plain_http(), &url, 4).await; + assert!( + probe + .failure(&sha512_sri(BODY)) + .is_some_and(|r| r.starts_with("fetch error ") && r.contains("too large")), + "{probe:?}" + ); + assert_eq!(probe.body, None); + } + + #[tokio::test] + async fn one_request_per_distinct_url() { + let server = MockServer::start().await; + Mock::given(method("GET")) + .respond_with(ResponseTemplate::new(200).set_body_bytes(BODY)) + .mount(&server) + .await; + let urls: BTreeSet = (0..6) + .map(|i| format!("{}/u{}.tgz", server.uri(), i % 3)) + .collect(); + let probes = probe_artifacts(&api(None), &urls).await; + assert_eq!(probes.len(), 3); + assert_eq!(server.received_requests().await.unwrap().len(), 3); + } + + fn dep(name: &str, sha512: Option<&str>) -> DepOverride { + DepOverride { + ecosystem: "npm".into(), + name: name.into(), + namespace: None, + version: "1.0.0".into(), + token: String::new(), + patch_uuid: format!("uuid-{name}"), + artifact_url: format!("https://patch.socket.dev/patch/npm/t/u/{name}-1.0.0.tgz"), + berry_zip_url: None, + registry_override: None, + integrity: Integrity { + sha512: sha512.map(str::to_string), + ..Integrity::default() + }, + } + } + + fn lock(nodes: &[&str]) -> BTreeMap { + let body = nodes + .iter() + .map(|n| format!(" {n}")) + .collect::>() + .join(",\n"); + [( + VLT_LOCK.to_string(), + format!( + "{{\n \"lockfileVersion\": 1,\n \"options\": {{}},\n \"nodes\": {{\n{body}\n }},\n \"edges\": {{}}\n}}\n" + ), + )] + .into() + } + + #[test] + fn scope_needs_a_parsed_lock_a_sha512_and_a_default_instance() { + let files = lock(&[ + r#""~npm~a@1.0.0": [0,"a","sha512-old","https://registry.npmjs.org/a/-/a-1.0.0.tgz"]"#, + r#""~npm~b@1.0.0": [0,"b","sha512-B","https://patch.socket.dev/patch/npm/t/u/b-1.0.0.tgz"]"#, + r#""~custom~c@1.0.0": [0,"c"]"#, + ]); + let deps = [ + dep("a", Some("sha512-A")), + dep("b", Some("sha512-B")), + dep("c", Some("sha512-C")), + dep("d", Some("sha512-D")), + dep("a", None), + ]; + let scope = preflight_scope(&files, &deps); + let got: Vec<(&str, bool)> = scope + .iter() + .map(|d| (d.patch_uuid.as_str(), d.already_pinned)) + .collect(); + assert_eq!(got, [("uuid-a", false), ("uuid-b", true)]); + assert!(preflight_scope(&BTreeMap::new(), &deps).is_empty()); + let mut bom = files.clone(); + bom.insert(VLT_LOCK.into(), format!("\u{feff}{}", files[VLT_LOCK])); + assert!(preflight_scope(&bom, &deps).is_empty()); + } +} From f38c1487c3eb328c9b0fef5367ad743eff099041 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Fri, 25 Sep 2026 23:43:54 -0400 Subject: [PATCH 14/46] Verify vlt takeovers before reverting them A vendored vlt package taken over by `scan --mode hosted` is now fetched and verified before its vendored state is reverted, dry runs included. An artifact vlt would reject keeps the package vendored instead of leaving a lock pinned to bytes nobody checked. A same-run --vex attests a vlt package only when the heal checked its installed copy; a pin on a host socket-patch does not own is left to a later `socket-patch vex`. When vlt-lock.json is withheld beside another lockfile, its old pin no longer confirms the package, and the warning says only vlt-lock.json was left unchanged. The heal keeps every store entry when node_modules/.vlt-lock.json cannot be removed, so vlt never trusts a hidden lock with dangling links, and packages that bundle dependencies are no longer re-invalidated on every run. Assisted-by: Claude Code:claude-opus-5-5 --- crates/socket-patch-cli/CLI_CONTRACT.md | 4 +- .../src/commands/scan/hosted.rs | 16 +- .../src/commands/scan/hosted/vlt.rs | 164 ++++++++-- .../tests/in_process_redirect/vlt.rs | 301 +++++++++++++++++- .../tests/in_process_rollback_hosted/vlt.rs | 85 +++++ .../src/patch/redirect/vlt.rs | 8 + .../src/patch/redirect/vlt_heal.rs | 62 +++- .../src/patch/redirect/vlt_preflight.rs | 49 ++- 8 files changed, 632 insertions(+), 57 deletions(-) diff --git a/crates/socket-patch-cli/CLI_CONTRACT.md b/crates/socket-patch-cli/CLI_CONTRACT.md index 706583ed..1947b076 100644 --- a/crates/socket-patch-cli/CLI_CONTRACT.md +++ b/crates/socket-patch-cli/CLI_CONTRACT.md @@ -1294,8 +1294,8 @@ Every `--json` invocation emits a single JSON object that follows the **unified | `redirect_vlt_lockfile_version_missing` / `redirect_vlt_old_lockfile_ignored` / `redirect_vlt_scalar_registry_ignored` | `redirect.warnings[]` (warning) | scan/get `--mode hosted` (vlt): the lock has no `lockfileVersion` (vlt ≥ 1.0.0-rc.15 re-resolves it) / a legacy default-registry id without `"modifiers"` in `vlt.json` (vlt 0.0.0-16 … 0.0.0-24 ignore the lock) / a scalar `registry` option that vlt 1.0.0-rc.7 … rc.29 honor over the lock. The deps stay redirected, but the run's `--vex` does not attest them. | | `redirect_vlt_sibling_lockfiles` | `redirect.warnings[]` (warning) | scan/get `--mode hosted` (vlt): `vlt-lock.json` and another npm-family lock are both present and vlt's install state (`node_modules/.vlt-lock.json` or `node_modules/.vlt/`) is not, so both locks were rewritten and the other lock's rules confirm. | | `redirect_vlt_no_lockfile` | `redirect.warnings[]` (warning) | scan/get `--mode hosted` (vlt): `vlt.json` or vlt's install state is present without `vlt-lock.json`; replaces `redirect_npm_no_lockfile` for vlt projects. | -| `redirect_vlt_artifact_unverifiable` | `redirect.warnings[]` (warning), `redirect.skipped[].reason` | scan/get `--mode hosted` (vlt): before any takeover or rewrite (dry runs included), each granted artifact with a default-registry instance in `vlt-lock.json` is fetched once as vlt fetches it (`accept-encoding: gzip;q=1.0, identity;q=0.5`, no `Authorization`, up to 10 redirects) and must return 200 with no content encoding (or `identity`) and the granted sha512. On failure (`content-encoding `, `sha512 mismatch`, `http `, `fetch error `, `offline`) the dep is withheld from every rewriter when vlt drives (from the vlt rewrite only otherwise). A lock already pinned by an earlier run is left pinned, and neither confirmed nor attested. Projects without `vlt-lock.json` make no such request. Exit 0. | -| `redirect_vlt_reinstall_required` | `redirect.warnings[]` (advisory); rollback/remove `warnings[]` (+ human stderr) | vlt: `vlt-lock.json` pins (or, after rollback/remove, no longer pins) Socket-patched packages, and vlt never refreshes an installed copy. The heal removes `node_modules/.vlt-lock.json` and each stale `node_modules/.vlt/` of a Socket-owned node (never a link's target, never outside the project, never a copy it cannot judge) unless `--no-vlt-install-cleanup` or `--dry-run`; the detail says whether copies were removed, left stale, could not be checked, or none were stale. Stale or unchecked copies are not attested by the run's `--vex`. Invalidation failures only warn. | +| `redirect_vlt_artifact_unverifiable` | `redirect.warnings[]` (warning), `redirect.skipped[].reason` | scan/get `--mode hosted` (vlt): before any takeover or rewrite (dry runs included), each granted artifact with a default-registry instance in `vlt-lock.json` (or, for a purl a `flavor: "vlt"` vendored entry claims, its vendored node, probed before the takeover reverts it) is fetched once as vlt fetches it (`accept-encoding: gzip;q=1.0, identity;q=0.5`, no `Authorization`, up to 10 redirects) and must return 200 with no content encoding (or `identity`) and the granted sha512. On failure (`content-encoding `, `sha512 mismatch`, `http `, `fetch error `, `offline`) the dep is withheld from every rewriter when vlt drives or it is vlt-vendored (which also keeps it vendored), and from the vlt rewrite only otherwise (detail "…; vlt-lock.json was not changed for {purl}"; only the sibling lock this run rewrote can confirm it). A lock already pinned by an earlier run is left pinned, and neither confirmed nor attested. Projects without `vlt-lock.json` make no such request. Exit 0. | +| `redirect_vlt_reinstall_required` | `redirect.warnings[]` (advisory); rollback/remove `warnings[]` (+ human stderr) | vlt: `vlt-lock.json` pins (or, after rollback/remove, no longer pins) Socket-patched packages, and vlt never refreshes an installed copy. The heal removes `node_modules/.vlt-lock.json` and each stale `node_modules/.vlt/` of a Socket-owned node (never a link's target, never outside the project, never a copy it cannot judge) unless `--no-vlt-install-cleanup` or `--dry-run`; the detail says whether copies were removed, left stale, could not be checked, or none were stale. Stale or unchecked copies are not attested by the run's `--vex`, nor is a confirmed vlt pin the heal did not check (a URL on a host other than patch.socket.dev and the configured `--patch-server-url`/`--api-url`). A hidden lock that cannot be removed keeps every store entry. Invalidation failures only warn. | | `vendor_prebuilt_stub_invalid` | `failed` / `skipped` (warning) | vendor (gem, `--vendor-source`): the served stub gemspec fails the rubygems `summary`/`authors` bar, so bundler would refuse the vendored path source at install time. `service`: refusal naming the missing attributes; `auto`: loud warning + local-build fallback — or, when the gem is also not installed locally (no stub to derive), a refusal naming the served defect and the install-the-gem remedy. | | `gem_spec_invalid` | `failed` | vendor (gem): the LOCAL `specifications/` stub gemspec fails the same rubygems `summary`/`authors` bar (a corrupted or hand-edited gem home); the refusal names the file — reinstall the gem (`gem pristine ` / fresh `bundle install`). | | `vendor_*` / `pypi_*` / `gemfile_*` / `lock_*` / `locked_version_mismatch` / `user_authored_*` / `native_extensions_unsupported` / `platform_gem_unsupported` | `failed`/`skipped` | vendor: per-ecosystem refusal + drift vocabulary; see the Vendor command contract section. New tags are additive (MINOR). | diff --git a/crates/socket-patch-cli/src/commands/scan/hosted.rs b/crates/socket-patch-cli/src/commands/scan/hosted.rs index 412e4f20..191ca64d 100644 --- a/crates/socket-patch-cli/src/commands/scan/hosted.rs +++ b/crates/socket-patch-cli/src/commands/scan/hosted.rs @@ -2415,16 +2415,20 @@ pub(crate) async fn run_redirect_selected( // here is always safe; `pdm.lock` only ever carries pypi URLs. let pdm_inactive = files.contains_key("pdm.lock") && !socket_patch_core::patch::redirect::pdm_drives(&files); - let final_texts: Vec<&String> = files + // Likewise a `vlt-lock.json` the vlt rewrite was withheld from (its + // artifact failed the preflight beside another npm-family lock) may + // still hold an earlier run's pin: only the sibling lock this run + // rewrote can confirm that dep. + let final_texts: Vec<(&str, &String)> = files .iter() .filter(|(name, _)| !(pdm_inactive && name.as_str() == "pdm.lock")) - .map(|(name, content)| rewrite.files.get(name).unwrap_or(content)) + .map(|(name, content)| (name.as_str(), rewrite.files.get(name).unwrap_or(content))) .chain( rewrite .files .iter() .filter(|(name, _)| !files.contains_key(*name)) - .map(|(_, content)| content), + .map(|(name, content)| (name.as_str(), content)), ) .collect(); let confirmed: Vec<(String, String)> = candidates @@ -2504,7 +2508,11 @@ pub(crate) async fn run_redirect_selected( let suffixed_version = registry.and_then(|o| o.identifiers.maven_suffixed_version.as_deref()); let encoded = socket_patch_core::utils::uri::encode_uri_component(artifact_url); - final_texts.iter().any(|text| { + let vlt_withheld = vlt_preflight.withheld_from_vlt.contains(uuid); + final_texts.iter().any(|(name, text)| { + if vlt_withheld && *name == socket_patch_core::constants::npm_family::VLT_LOCK { + return false; + } // The rewriters' own predicate — raw, or the `\/`-escaped // slashes an old composer.lock spells them with — so a // writer's spelling can never be one this probe misses. It diff --git a/crates/socket-patch-cli/src/commands/scan/hosted/vlt.rs b/crates/socket-patch-cli/src/commands/scan/hosted/vlt.rs index 62fa3981..2e1d1878 100644 --- a/crates/socket-patch-cli/src/commands/scan/hosted/vlt.rs +++ b/crates/socket-patch-cli/src/commands/scan/hosted/vlt.rs @@ -21,7 +21,9 @@ use super::StaleInstallOutcome; pub(super) const REINSTALL_REQUIRED: &str = "redirect_vlt_reinstall_required"; const ARTIFACT_UNVERIFIABLE: &str = "redirect_vlt_artifact_unverifiable"; -/// The lock-level warnings that say vlt may discard the redirect. +/// The lock-level warnings that say vlt may discard the redirect (§3.9 (c); +/// `redirect_vlt_sibling_lockfiles` says nothing about vlt's reading of the +/// lock). const DISCARDING_LOCK_WARNINGS: [&str; 3] = [ "redirect_vlt_lockfile_version_missing", "redirect_vlt_old_lockfile_ignored", @@ -47,7 +49,8 @@ pub(super) fn install_state_present(cwd: &Path) -> bool { /// What the artifact preflight decided for this run's npm candidates. #[derive(Default)] pub(super) struct Preflight { - /// Failed while vlt drives: withheld from every rewriter. + /// Failed while vlt drives, or for a vlt-vendored takeover: withheld + /// from every rewriter. pub(super) withheld_everywhere: BTreeMap, /// Failed while another npm-family lock may drive: kept out of the vlt /// rewrite only. @@ -79,20 +82,50 @@ async fn vlt_inputs(cwd: &Path) -> BTreeMap { files } -fn unverifiable_detail(url: &str, reason: &str, purl: &str, already_pinned: bool) -> String { +fn unverifiable_detail( + url: &str, + reason: &str, + purl: &str, + already_pinned: bool, + everywhere: bool, +) -> String { if already_pinned { format!( "vlt would fail to verify {url}: {reason}; {purl} was left pinned by an earlier run \ and `vlt ci` will fail until the artifact verifies" ) - } else { + } else if everywhere { format!("vlt would fail to verify {url}: {reason}; nothing was written for {purl}") + } else { + format!( + "vlt would fail to verify {url}: {reason}; vlt-lock.json was not changed for {purl}" + ) } } +/// The uuids of `deps` whose purl a vlt vendored ledger entry claims: a +/// hosted takeover reverts them to a registry node before the rewrite. +async fn vlt_vendored_uuids(cwd: &Path, deps: &[(&str, &DepOverride)]) -> BTreeSet { + let Ok(state) = socket_patch_core::vendor::load_state(cwd).await else { + return BTreeSet::new(); + }; + deps.iter() + .filter(|(purl, _)| { + socket_patch_core::vendor::lookup_entry( + &state.entries, + socket_patch_core::utils::purl::strip_purl_qualifiers(purl), + ) + .is_some_and(|e| e.ecosystem == "npm" && e.flavor.as_deref() == Some("vlt")) + }) + .map(|(_, dep)| dep.patch_uuid.clone()) + .collect() +} + /// Fetch each in-scope artifact the way vlt does (once per distinct URL, /// `offline` making no request) and decide which deps may be pinned in -/// `vlt-lock.json`. Projects without `vlt-lock.json` make no request. +/// `vlt-lock.json`. Projects without `vlt-lock.json` make no request. A +/// vlt-vendored dep is probed through its vendored node, before the +/// takeover reverts it, and a failure keeps it vendored. pub(super) async fn artifact_preflight( common: &crate::args::GlobalArgs, api_client: &socket_patch_core::api::client::ApiClient, @@ -104,7 +137,8 @@ pub(super) async fn artifact_preflight( return out; } let overrides: Vec = deps.iter().map(|(_, dep)| (*dep).clone()).collect(); - let scope = vlt_preflight::preflight_scope(&files, &overrides); + let vendored = vlt_vendored_uuids(&common.cwd, deps).await; + let scope = vlt_preflight::preflight_scope(&files, &overrides, &vendored); if scope.is_empty() { return out; } @@ -131,11 +165,18 @@ pub(super) async fn artifact_preflight( .iter() .find(|(_, d)| d.patch_uuid == dep.patch_uuid) .map_or("", |(purl, _)| *purl); + let everywhere = drives || dep.vendored; out.warnings.push(serde_json::json!({ "code": ARTIFACT_UNVERIFIABLE, - "detail": unverifiable_detail(&dep.artifact_url, &reason, purl, dep.already_pinned), + "detail": unverifiable_detail( + &dep.artifact_url, + &reason, + purl, + dep.already_pinned, + everywhere, + ), })); - if drives { + if everywhere { out.withheld_everywhere .insert(dep.patch_uuid.clone(), purl.to_string()); } else { @@ -274,7 +315,10 @@ pub(super) struct HealInputs<'a> { /// The heal after a hosted rewrite, the advisory, and the purls whose /// installed or next-installed bytes are not known to be patched (removed -/// from the same run's in-run VEX attestation). +/// from the same run's in-run VEX attestation). A confirmed vlt uuid with +/// no heal target (a non-Socket host, a leaf that disagrees with the +/// DepID, an artifact no preflight verified) was never checked, so it is +/// never attested here. pub(super) async fn heal_after_rewrite( common: &crate::args::GlobalArgs, inputs: &HealInputs<'_>, @@ -287,6 +331,7 @@ pub(super) async fn heal_after_rewrite( .into_iter() .filter(|i| inputs.preflight.passed.contains(&i.patch_uuid)) .collect(); + let targeted: BTreeSet<&str> = owned.iter().map(|i| i.patch_uuid.as_str()).collect(); let mut tally = HealTally::default(); if !owned.is_empty() { let targets: Vec<(Target<'_>, &str)> = owned @@ -320,6 +365,7 @@ pub(super) async fn heal_after_rewrite( continue; } if lock_discards + || !targeted.contains(uuid.as_str()) || inputs.foreign.contains(uuid) || tally.stale_uuids.contains(uuid) || tally.undeterminable_uuids.contains(uuid) @@ -418,32 +464,35 @@ mod tests { assert!(reinstall_detail(&tally).contains("still holds 3 unpatched copies")); } - #[tokio::test] - async fn offline_withholds_every_probed_dep_without_a_request() { - let server = wiremock::MockServer::start().await; - let tmp = tempfile::tempdir().unwrap(); - let url = format!("{}/patch/npm/t/u/left-pad-1.3.0.tgz", server.uri()); - std::fs::write( - tmp.path().join(VLT_LOCK), - "{\n \"lockfileVersion\": 1,\n \"options\": {},\n \"nodes\": {\n \ - \"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-old\",\"https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz\"]\n },\n \"edges\": {}\n}\n", - ) - .unwrap(); - let dep = DepOverride { + fn left_pad_dep(url: &str) -> DepOverride { + DepOverride { ecosystem: "npm".into(), name: "left-pad".into(), namespace: None, version: "1.3.0".into(), token: String::new(), patch_uuid: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa".into(), - artifact_url: url.clone(), + artifact_url: url.to_string(), berry_zip_url: None, registry_override: None, integrity: socket_patch_core::patch::redirect::Integrity { sha512: Some("sha512-new".into()), ..Default::default() }, - }; + } + } + + const LEFT_PAD_LOCK: &str = "{\n \"lockfileVersion\": 1,\n \"options\": {},\n \"nodes\": \ + {\n \"~npm~left-pad@1.3.0\": [0,\"left-pad\",\"sha512-old\",\ + \"https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz\"]\n },\n \"edges\": {}\n}\n"; + + #[tokio::test] + async fn offline_withholds_every_probed_dep_without_a_request() { + let server = wiremock::MockServer::start().await; + let tmp = tempfile::tempdir().unwrap(); + let url = format!("{}/patch/npm/t/u/left-pad-1.3.0.tgz", server.uri()); + std::fs::write(tmp.path().join(VLT_LOCK), LEFT_PAD_LOCK).unwrap(); + let dep = left_pad_dep(&url); let common = crate::args::GlobalArgs { cwd: tmp.path().to_path_buf(), offline: true, @@ -470,14 +519,79 @@ mod tests { #[tokio::test] async fn no_vlt_lock_makes_no_request() { + let server = wiremock::MockServer::start().await; + let url = format!("{}/patch/npm/t/u/left-pad-1.3.0.tgz", server.uri()); + let dep = left_pad_dep(&url); + let api = test_api(); + for with_lock in [false, true] { + let tmp = tempfile::tempdir().unwrap(); + std::fs::write(tmp.path().join("package-lock.json"), "{}").unwrap(); + if with_lock { + std::fs::write(tmp.path().join(VLT_LOCK), LEFT_PAD_LOCK).unwrap(); + } + let common = crate::args::GlobalArgs { + cwd: tmp.path().to_path_buf(), + ..crate::args::GlobalArgs::default() + }; + let pre = artifact_preflight(&common, &api, &[("pkg:npm/left-pad@1.3.0", &dep)]).await; + let requests = server.received_requests().await.unwrap().len(); + if with_lock { + assert_eq!(requests, 1, "the control probes the vlt lock's dep"); + assert!(pre.withheld_from_vlt.contains(&dep.patch_uuid)); + } else { + assert_eq!(requests, 0); + assert!(pre.passed.is_empty() && pre.warnings.is_empty()); + assert!(pre.withheld_everywhere.is_empty() && pre.withheld_from_vlt.is_empty()); + } + } + } + + #[tokio::test] + async fn a_vlt_vendored_dep_is_probed_and_withheld_everywhere() { + let server = wiremock::MockServer::start().await; + let url = format!("{}/patch/npm/t/u/left-pad-1.3.0.tgz", server.uri()); + let dep = left_pad_dep(&url); let tmp = tempfile::tempdir().unwrap(); std::fs::write(tmp.path().join("package-lock.json"), "{}").unwrap(); + std::fs::write( + tmp.path().join(VLT_LOCK), + "{\n \"lockfileVersion\": 1,\n \"options\": {},\n \"nodes\": {\n \ + \"file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+left-pad-1.3.0+node__modules+left-pad\": \ + [0,\"left-pad\",null,\".socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad\"]\n \ + },\n \"edges\": {}\n}\n", + ) + .unwrap(); let common = crate::args::GlobalArgs { cwd: tmp.path().to_path_buf(), ..crate::args::GlobalArgs::default() }; let api = test_api(); - let pre = artifact_preflight(&common, &api, &[]).await; - assert!(pre.passed.is_empty() && pre.warnings.is_empty()); + let deps = [("pkg:npm/left-pad@1.3.0", &dep)]; + let unclaimed = artifact_preflight(&common, &api, &deps).await; + assert!(server.received_requests().await.unwrap().is_empty()); + assert!(unclaimed.warnings.is_empty()); + std::fs::create_dir_all(tmp.path().join(".socket/vendor")).unwrap(); + std::fs::write( + tmp.path().join(".socket/vendor/state.json"), + serde_json::to_vec(&serde_json::json!({ + "version": 1, + "entries": { "pkg:npm/left-pad@1.3.0": { + "ecosystem": "npm", "basePurl": "pkg:npm/left-pad@1.3.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "artifact": { "path": ".socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0" }, + "wiring": [], "flavor": "vlt" + }} + })) + .unwrap(), + ) + .unwrap(); + let claimed = artifact_preflight(&common, &api, &deps).await; + assert_eq!(server.received_requests().await.unwrap().len(), 1); + assert!(claimed.withheld_from_vlt.is_empty()); + assert!(claimed.withheld_everywhere.contains_key(&dep.patch_uuid)); + assert_eq!( + claimed.warnings[0]["detail"], + format!("vlt would fail to verify {url}: http 404; nothing was written for pkg:npm/left-pad@1.3.0") + ); } } diff --git a/crates/socket-patch-cli/tests/in_process_redirect/vlt.rs b/crates/socket-patch-cli/tests/in_process_redirect/vlt.rs index b66c17b0..71fa8e0b 100644 --- a/crates/socket-patch-cli/tests/in_process_redirect/vlt.rs +++ b/crates/socket-patch-cli/tests/in_process_redirect/vlt.rs @@ -480,7 +480,216 @@ async fn scan_redirect_vlt_artifact_ambiguous_withholds_vlt_only() { read(tmp.path(), "vlt-lock.json"), lock_with(Era::V1, &[registry_node(TILDE_ID)]) ); - assert!(warning_codes(&doc).contains(&UNVERIFIABLE.to_string())); + assert_eq!( + warning_detail(&doc, UNVERIFIABLE), + format!( + "vlt would fail to verify {}: content-encoding gzip; vlt-lock.json was not changed \ + for {PURL}", + artifact_url(&server) + ) + ); + assert!(skipped_reasons(&doc).is_empty(), "{doc:#}"); +} + +/// A package-lock.json that lists only the root: the npm rewriter has +/// nothing to pin. +fn package_lock_without_dep() -> String { + r#"{ + "name": "consumer", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { "name": "consumer", "version": "0.0.0" } + } +} +"# + .to_string() +} + +#[tokio::test] +async fn scan_redirect_vlt_artifact_ambiguous_earlier_pin_is_not_confirmed() { + let server = gzip_artifact_server().await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + let pinned = lock_with(Era::V1, &[pinned_node(TILDE_ID, &server)]); + std::fs::write(tmp.path().join("vlt-lock.json"), &pinned).unwrap(); + std::fs::write( + tmp.path().join("package-lock.json"), + package_lock_without_dep(), + ) + .unwrap(); + + let (_, doc) = scan_hosted(tmp.path(), &server, &["--no-npm-allow-remote-config"], &[]); + + assert_eq!( + redirected(&doc), + 0, + "the earlier run's vlt pin alone confirms nothing: {doc:#}" + ); + assert_eq!(read(tmp.path(), "vlt-lock.json"), pinned); + assert_eq!( + warning_detail(&doc, UNVERIFIABLE), + format!( + "vlt would fail to verify {}: content-encoding gzip; {PURL} was left pinned by an \ + earlier run and `vlt ci` will fail until the artifact verifies", + artifact_url(&server) + ) + ); +} + +/// A real `node_modules/.vlt` store with no hidden lock (0.0.0-1 and +/// 0.0.0-32 write none) is vlt's install state too: vlt drives beside a +/// package-lock.json, so there is no sibling warning and a failed preflight +/// withholds the dep from every rewriter. +#[tokio::test] +async fn scan_redirect_vlt_store_dir_without_hidden_lock_drives() { + let server = gzip_artifact_server().await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_project(tmp.path(), Era::V1); + install_store(tmp.path(), TILDE_ID, PRISTINE); + std::fs::write(tmp.path().join("package-lock.json"), package_lock()).unwrap(); + + let (_, doc) = scan_hosted(tmp.path(), &server, &["--no-npm-allow-remote-config"], &[]); + + assert_eq!(redirected(&doc), 0, "{doc:#}"); + assert!( + !warning_codes(&doc).contains(&"redirect_vlt_sibling_lockfiles".to_string()), + "{doc:#}" + ); + assert_eq!(skipped_reasons(&doc), [UNVERIFIABLE]); + assert_eq!(read(tmp.path(), "package-lock.json"), package_lock()); + assert_eq!( + warning_detail(&doc, UNVERIFIABLE), + format!( + "vlt would fail to verify {}: content-encoding gzip; nothing was written for {PURL}", + artifact_url(&server) + ) + ); +} + +/// When vlt drives, only the vlt rewriter confirms an npm purl: a +/// package-lock.json rewrite that carries the hosted URL does not, when +/// vlt-lock.json has no default-registry node for the dep. +#[tokio::test] +async fn scan_redirect_vlt_drives_entry_not_found_sibling_rewrite_does_not_confirm() { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_package_json(tmp.path()); + install_importer(tmp.path(), PRISTINE); + std::fs::write( + tmp.path().join("vlt-lock.json"), + lock_with(Era::V1, &["\"~npm~ms@2.1.3\": [0,\"ms\"]".to_string()]), + ) + .unwrap(); + write_hidden_lock(tmp.path(), &[]); + std::fs::write(tmp.path().join("package-lock.json"), package_lock()).unwrap(); + + let (_, doc, attested) = scan_with_vex(tmp.path(), &server, &["--no-npm-allow-remote-config"]); + + assert!( + warning_codes(&doc).contains(&"redirect_vlt_entry_not_found".to_string()), + "{doc:#}" + ); + assert!(read(tmp.path(), "package-lock.json").contains(&artifact_url(&server))); + assert_eq!(redirected(&doc), 0, "{doc:#}"); + assert!(!attested, "{doc:#}"); + assert_eq!(artifact_requests(&server).await, 0); +} + +const VENDORED_UUID: &str = "11111111-2222-4333-8444-555555555555"; + +/// A vlt project whose left-pad is vendored (§3.4 D19 dir node) and +/// claimed by a `flavor: "vlt"` vendored ledger entry. +fn write_vlt_vendored_project(root: &Path) -> (String, Vec) { + write_package_json(root); + install_importer(root, PATCHED); + let dir = format!(".socket/vendor/npm/{VENDORED_UUID}/{NAME}-{VERSION}/node_modules/{NAME}"); + let lock = lock_with( + Era::V1, + &[format!( + "\"file~.socket+vendor+npm+{VENDORED_UUID}+{NAME}-{VERSION}+node__modules+{NAME}\": \ + [0,\"{NAME}\",null,\"{dir}\"]" + )], + ); + std::fs::write(root.join("vlt-lock.json"), &lock).unwrap(); + let state = serde_json::json!({ + "version": 1, + "entries": { PURL: { + "ecosystem": "npm", + "basePurl": PURL, + "uuid": VENDORED_UUID, + "artifact": { "path": format!(".socket/vendor/npm/{VENDORED_UUID}/{NAME}-{VERSION}") }, + "wiring": [], + "flavor": "vlt" + }} + }); + std::fs::create_dir_all(root.join(".socket/vendor")).unwrap(); + let state = serde_json::to_vec_pretty(&state).unwrap(); + std::fs::write(root.join(".socket/vendor/state.json"), &state).unwrap(); + (lock, state) +} + +/// Vendored → hosted (§4.10): the takeover restores the registry node this +/// run pins, so the artifact is probed through the vendored node BEFORE the +/// revert; a failure keeps the package vendored (never reverted), with or +/// without another npm-family lock, wet or dry. +#[tokio::test] +async fn scan_redirect_vlt_vendored_takeover_preflights_before_the_revert() { + for (sibling, dry_run) in [(false, false), (false, true), (true, false)] { + let server = gzip_artifact_server().await; + let tmp = tempfile::tempdir().unwrap(); + let (lock, state) = write_vlt_vendored_project(tmp.path()); + if sibling { + std::fs::write(tmp.path().join("package-lock.json"), package_lock()).unwrap(); + } + let mut extra = vec!["--no-npm-allow-remote-config"]; + if dry_run { + extra.push("--dry-run"); + } + + let (_, doc) = scan_hosted(tmp.path(), &server, &extra, &[]); + + let leg = format!("sibling={sibling} dry_run={dry_run}: {doc:#}"); + assert_eq!(artifact_requests(&server).await, 1, "{leg}"); + assert_eq!(redirected(&doc), 0, "{leg}"); + assert_eq!(skipped_reasons(&doc), [UNVERIFIABLE], "{leg}"); + assert!( + !warning_codes(&doc) + .iter() + .any(|c| c.contains("revert") || c.contains("takeover")), + "no takeover is attempted: {leg}" + ); + assert_eq!(read(tmp.path(), "vlt-lock.json"), lock, "{leg}"); + assert_eq!( + std::fs::read(tmp.path().join(".socket/vendor/state.json")).unwrap(), + state, + "{leg}" + ); + if sibling { + assert_eq!( + read(tmp.path(), "package-lock.json"), + package_lock(), + "{leg}" + ); + } + } + + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_vlt_vendored_project(tmp.path()); + let (_, doc) = scan_hosted(tmp.path(), &server, &["--dry-run"], &[]); + assert_eq!( + artifact_requests(&server).await, + 1, + "a verifying artifact is probed too: {doc:#}" + ); + assert!( + !warning_codes(&doc).contains(&UNVERIFIABLE.to_string()), + "{doc:#}" + ); } #[tokio::test] @@ -519,22 +728,50 @@ async fn scan_redirect_vlt_artifact_get_uuid_driver() { assert_eq!(read(tmp.path(), "vlt-lock.json"), lock); } -#[tokio::test] -async fn scan_redirect_vlt_artifact_no_preflight_without_vlt_lock() { - let server = MockServer::start().await; - mock_all(&server).await; - let tmp = tempfile::tempdir().unwrap(); - write_package_json(tmp.path()); - install_importer(tmp.path(), PRISTINE); - std::fs::write(tmp.path().join("package-lock.json"), package_lock()).unwrap(); +fn pnpm_lock() -> String { + format!( + "lockfileVersion: '9.0'\n\nimporters:\n .:\n dependencies:\n {NAME}:\n \ + specifier: {VERSION}\n version: {VERSION}\n\npackages:\n {NAME}@{VERSION}:\n \ + resolution: {{integrity: {UPSTREAM_SHA512}}}\n\nsnapshots:\n {NAME}@{VERSION}: {{}}\n" + ) +} - let (_, doc) = scan_hosted(tmp.path(), &server, &["--no-npm-allow-remote-config"], &[]); +fn bun_lock() -> String { + format!( + "{{\n \"lockfileVersion\": 1,\n \"packages\": {{\n \"{NAME}\": [\"{NAME}@{VERSION}\", \ + \"\", {{}}, \"{UPSTREAM_SHA512}\"],\n }}\n}}\n" + ) +} - assert_eq!(redirected(&doc), 1); - assert_eq!(artifact_requests(&server).await, 0); - assert!(!warning_codes(&doc) - .iter() - .any(|c| c.starts_with("redirect_vlt_"))); +#[tokio::test] +async fn scan_redirect_vlt_artifact_no_preflight_without_vlt_lock() { + for (lock, text) in [ + ("package-lock.json", package_lock()), + ("pnpm-lock.yaml", pnpm_lock()), + ("bun.lock", bun_lock()), + ] { + let server = MockServer::start().await; + mock_all(&server).await; + let tmp = tempfile::tempdir().unwrap(); + write_package_json(tmp.path()); + install_importer(tmp.path(), PRISTINE); + std::fs::write(tmp.path().join(lock), text).unwrap(); + + let (_, doc) = scan_hosted(tmp.path(), &server, &["--no-npm-allow-remote-config"], &[]); + + assert_eq!(redirected(&doc), 1, "{lock}: {doc:#}"); + assert!( + read(tmp.path(), lock).contains(&artifact_url(&server)), + "{lock}" + ); + assert_eq!(artifact_requests(&server).await, 0, "{lock}"); + assert!( + !warning_codes(&doc) + .iter() + .any(|c| c.starts_with("redirect_vlt_")), + "{lock}: {doc:#}" + ); + } } #[tokio::test] @@ -913,6 +1150,40 @@ async fn scan_redirect_vlt_heal_custom_patch_server_origin_heals() { // ── in-run VEX exclusion ───────────────────────────────────────────────── +/// A confirmed vlt pin on a host that is neither patch.socket.dev nor a +/// configured origin is never healed, so the same run must not attest the +/// installed (pristine) copy; with the origin configured the heal proves it. +#[tokio::test] +async fn scan_redirect_vlt_unconfigured_origin_vex_not_attested() { + let server = MockServer::start().await; + let artifacts = MockServer::start().await; + mock_discovery(&server).await; + mock_view(&server).await; + mock_artifact(&artifacts).await; + mock_reference_at( + &server, + &artifact_url(&artifacts), + &sha512_sri(&patched_tarball()), + ) + .await; + let unconfigured = tempfile::tempdir().unwrap(); + write_installed_vlt_project(unconfigured.path(), PRISTINE); + let configured = tempfile::tempdir().unwrap(); + write_installed_vlt_project(configured.path(), PRISTINE); + let origin = artifacts.uri(); + + let (_, doc, attested) = scan_with_vex(unconfigured.path(), &server, &[]); + let (_, healed, healed_attested) = + scan_with_vex(configured.path(), &server, &["--patch-server-url", &origin]); + + assert_eq!(redirected(&doc), 1, "{doc:#}"); + assert!(!warning_codes(&doc).contains(&ADVISORY.to_string())); + assert!(store_dir(unconfigured.path(), TILDE_ID).exists()); + assert!(!attested, "an unhealed pin is not attested: {doc:#}"); + assert_eq!(warning_detail(&healed, ADVISORY), advisory_invalidated(1)); + assert!(healed_attested, "{healed:#}"); +} + fn vex_args(out: &Path) -> Vec { vec![ "--vex".into(), diff --git a/crates/socket-patch-cli/tests/in_process_rollback_hosted/vlt.rs b/crates/socket-patch-cli/tests/in_process_rollback_hosted/vlt.rs index 42e20634..7f45302b 100644 --- a/crates/socket-patch-cli/tests/in_process_rollback_hosted/vlt.rs +++ b/crates/socket-patch-cli/tests/in_process_rollback_hosted/vlt.rs @@ -189,3 +189,88 @@ async fn vlt_rollback_of_a_pristine_tree_keeps_it() { assert!(store_dir(root, TILDE_ID).join("index.js").exists()); assert!(root.join("node_modules/.vlt-lock.json").exists()); } + +const OTHER: &str = "right-pad"; +const OTHER_UUID: &str = "cccccccc-cccc-4ccc-8ccc-cccccccccccc"; + +fn as_other(text: &str) -> String { + text.replace(NAME, OTHER).replace(UUID, OTHER_UUID) +} + +/// Add a second hosted vlt package to the ledger and the lock by renaming +/// left-pad's recorded edit, record and pinned node. +fn add_second_hosted_package(root: &Path, server: &MockServer) -> String { + let path = ledger_path(root); + let mut ledger: Value = serde_json::from_str(&read(root, ".socket/vendor/redirect-state.json")) + .expect("the hosted run wrote a ledger"); + let edits: Vec = ledger["edits"] + .as_array() + .unwrap() + .iter() + .filter(|e| e["kind"] == "redirect_vlt_lock_node") + .map(|e| serde_json::from_str(&as_other(&e.to_string())).unwrap()) + .collect(); + assert_eq!(edits.len(), 1); + ledger["edits"].as_array_mut().unwrap().extend(edits); + let record: Value = + serde_json::from_str(&as_other(&ledger["records"][PURL].to_string())).unwrap(); + ledger["records"][as_other(PURL)] = record; + std::fs::write(&path, serde_json::to_vec_pretty(&ledger).unwrap()).unwrap(); + let other_pinned = as_other(&pinned_node(TILDE_ID, server)); + std::fs::write( + root.join("vlt-lock.json"), + vlt_lock( + Era::V1, + &[pinned_node(TILDE_ID, server), other_pinned.clone()], + ), + ) + .unwrap(); + other_pinned +} + +fn other_store_dir(root: &Path) -> std::path::PathBuf { + root.join("node_modules/.vlt") + .join(as_other(TILDE_ID)) + .join("node_modules") + .join(OTHER) +} + +/// A scoped rollback of one of two hosted vlt packages takes the per-purl +/// path (not a whole-ledger replay): only that package's pin is restored +/// and only its patched store entry (plus the hidden lock) is removed. +#[tokio::test] +async fn vlt_scoped_rollback_of_one_of_two_heals_only_that_package() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let server = hosted_vlt_project(root).await; + let other_pinned = add_second_hosted_package(root, &server); + install_store(root, TILDE_ID, PATCHED); + let other = other_store_dir(root); + std::fs::create_dir_all(&other).unwrap(); + std::fs::write( + other.join("package.json"), + as_other(&String::from_utf8_lossy(PACKAGE_JSON)), + ) + .unwrap(); + std::fs::write(other.join("index.js"), PATCHED).unwrap(); + write_hidden_lock( + root, + &[pinned_node(TILDE_ID, &server), other_pinned.clone()], + ); + + let (_, doc) = run_verb(root, "rollback", &[PURL]); + + assert_eq!( + read(root, "vlt-lock.json"), + vlt_lock(Era::V1, &[registry_node(TILDE_ID), other_pinned]) + ); + assert_eq!(advisory_details(&doc), [RESTORED], "{doc:#}"); + assert!(!store_dir(root, TILDE_ID).exists()); + assert!(!root.join("node_modules/.vlt-lock.json").exists()); + assert!( + other.join("index.js").exists(), + "the other package's copy stays" + ); + let ledger = read(root, ".socket/vendor/redirect-state.json"); + assert!(ledger.contains(OTHER_UUID), "{ledger}"); +} diff --git a/crates/socket-patch-core/src/patch/redirect/vlt.rs b/crates/socket-patch-core/src/patch/redirect/vlt.rs index cc79bd08..2fadf504 100644 --- a/crates/socket-patch-core/src/patch/redirect/vlt.rs +++ b/crates/socket-patch-core/src/patch/redirect/vlt.rs @@ -154,6 +154,14 @@ pub(super) fn default_instances<'a>(lock: &'a HostedLock, dep: &DepOverride) -> partition_instances(nodes, &full_name(dep), &dep.version, lock.parsed.options()).0 } +/// Does `dep` have a node of socket-patch's vendored vlt shape (§3.4) in a +/// lock that passed the lock-level parse? +pub(super) fn has_vendored_instance(lock: &HostedLock, dep: &DepOverride) -> bool { + lock.parsed + .nodes() + .is_some_and(|nodes| has_vendored_node(nodes, &full_name(dep), &dep.version)) +} + fn is_old_lockfile_ignored(lock: &HostedLock, files: &BTreeMap) -> bool { if lock.parsed.version == Some(1) { return false; diff --git a/crates/socket-patch-core/src/patch/redirect/vlt_heal.rs b/crates/socket-patch-core/src/patch/redirect/vlt_heal.rs index 98f563b1..337227d4 100644 --- a/crates/socket-patch-core/src/patch/redirect/vlt_heal.rs +++ b/crates/socket-patch-core/src/patch/redirect/vlt_heal.rs @@ -322,7 +322,10 @@ fn artifact_check(dir: &Path, artifact: &[u8]) -> ByteCheck { Err(true) => return ByteCheck::Mismatch, Err(false) => return ByteCheck::Unknown, }; - let expected: BTreeMap> = expected.into_iter().collect(); + let expected: BTreeMap> = expected + .into_iter() + .filter(|(path, _)| path != "node_modules" && !path.starts_with("node_modules/")) + .collect(); if expected == installed { ByteCheck::Match } else { @@ -393,20 +396,29 @@ async fn remove_entry(path: &Path) -> std::io::Result<()> { /// Remove the hidden lock and each stale store entry. Callers pass only /// ids [`classify_target`] judged stale, which requires a real store dir. +/// A hidden lock that cannot be removed keeps every store entry: vlt would +/// trust it and leave the importer links dangling. pub async fn invalidate(root: &Path, state: &InstallState, stale: &[String]) -> Invalidation { let mut out = Invalidation::default(); if stale.is_empty() || state.store != StoreState::Real { return out; } + let unique: BTreeSet<&String> = stale.iter().collect(); if state.hidden_present { if let Err(e) = remove_entry(&root.join(VLT_HIDDEN_LOCK_REL)).await { if e.kind() != std::io::ErrorKind::NotFound { + let why = e.to_string(); out.failed - .push((VLT_HIDDEN_LOCK_REL.to_string(), e.to_string())); + .push((VLT_HIDDEN_LOCK_REL.to_string(), why.clone())); + out.failed.extend( + unique + .into_iter() + .map(|id| (id.clone(), format!("kept: {VLT_HIDDEN_LOCK_REL}: {why}"))), + ); + return out; } } } - let unique: BTreeSet<&String> = stale.iter().collect(); for id in unique { if !is_safe_dep_id(id) { continue; @@ -677,6 +689,29 @@ mod tests { ); } + #[tokio::test] + async fn bundled_node_modules_in_the_artifact_are_not_compared() { + let tmp = tempfile::tempdir().unwrap(); + store(tmp.path(), ID, PATCHED); + let dir = package_dir(tmp.path(), ID, "left-pad"); + std::fs::create_dir_all(dir.join("node_modules/x")).unwrap(); + std::fs::write(dir.join("node_modules/x/index.js"), b"bundled").unwrap(); + let bundling = tarball(&[ + ("package/index.js", PATCHED), + ("package/package.json", b"{\"name\":\"left-pad\"}"), + ("package/node_modules/x/index.js", b"bundled"), + ]); + assert_eq!( + classify( + tmp.path(), + &target(None, Some(&bundling)), + Expected::Patched + ) + .await, + TargetState::Healthy + ); + } + #[tokio::test] async fn an_uninstalled_target_is_healthy() { let tmp = tempfile::tempdir().unwrap(); @@ -774,6 +809,27 @@ mod tests { assert!(other_dir.exists()); } + #[cfg(unix)] + #[tokio::test] + async fn an_unremovable_hidden_lock_keeps_every_store_entry() { + use std::os::unix::fs::PermissionsExt as _; + let tmp = tempfile::tempdir().unwrap(); + store(tmp.path(), ID, PRISTINE); + hidden(tmp.path(), Some("sha512-UPSTREAM")); + let state = read_install_state(tmp.path()).await; + let node_modules = tmp.path().join("node_modules"); + std::fs::set_permissions(&node_modules, std::fs::Permissions::from_mode(0o555)).unwrap(); + let out = invalidate(tmp.path(), &state, &[ID.to_string()]).await; + std::fs::set_permissions(&node_modules, std::fs::Permissions::from_mode(0o755)).unwrap(); + assert!(out.removed.is_empty(), "{out:?}"); + let failed: Vec<&str> = out.failed.iter().map(|(id, _)| id.as_str()).collect(); + assert_eq!(failed, [VLT_HIDDEN_LOCK_REL, ID]); + assert!(tmp.path().join(VLT_HIDDEN_LOCK_REL).exists()); + assert!(package_dir(tmp.path(), ID, "left-pad") + .join("index.js") + .exists()); + } + #[cfg(unix)] #[tokio::test] async fn a_linked_store_entry_is_unlinked_not_traversed() { diff --git a/crates/socket-patch-core/src/patch/redirect/vlt_preflight.rs b/crates/socket-patch-core/src/patch/redirect/vlt_preflight.rs index 1875b7d2..f848197c 100644 --- a/crates/socket-patch-core/src/patch/redirect/vlt_preflight.rs +++ b/crates/socket-patch-core/src/patch/redirect/vlt_preflight.rs @@ -184,15 +184,21 @@ pub struct PreflightDep { /// Every default-registry instance already carries this URL and sha512 /// (an earlier run pinned it). pub already_pinned: bool, + /// In scope only through its vendored node: a vendored-to-hosted + /// takeover restores the registry node this run would pin. + pub vendored: bool, } /// The npm overrides the preflight probes: `vlt-lock.json` passes the /// lock-level parse, the override has a sha512, and it has at least one -/// default-registry instance in the lock. Nothing is probed without a -/// `vlt-lock.json`. +/// default-registry instance in the lock, or (for the uuids in +/// `vendored_vlt`, whose purl a vlt vendored entry claims) a vendored node +/// the hosted takeover will turn back into one. Nothing is probed without +/// a `vlt-lock.json`. pub fn preflight_scope( files: &BTreeMap, overrides: &[DepOverride], + vendored_vlt: &BTreeSet, ) -> Vec { let Some(text) = files.get(VLT_LOCK) else { return Vec::new(); @@ -206,16 +212,20 @@ pub fn preflight_scope( .filter_map(|dep| { let sha512 = dep.integrity.sha512.as_deref().filter(|s| !s.is_empty())?; let ids = vlt::default_instances(&lock, dep); - if ids.is_empty() { + let vendored = ids.is_empty() + && vendored_vlt.contains(&dep.patch_uuid) + && vlt::has_vendored_instance(&lock, dep); + if ids.is_empty() && !vendored { return None; } - let already_pinned = - vlt::every_instance_pinned(text, &ids, sha512, &dep.artifact_url).is_some(); + let already_pinned = !vendored + && vlt::every_instance_pinned(text, &ids, sha512, &dep.artifact_url).is_some(); Some(PreflightDep { patch_uuid: dep.patch_uuid.clone(), artifact_url: dep.artifact_url.clone(), sha512: sha512.to_string(), already_pinned, + vendored, }) }) .collect() @@ -489,15 +499,38 @@ mod tests { dep("d", Some("sha512-D")), dep("a", None), ]; - let scope = preflight_scope(&files, &deps); + let none = BTreeSet::new(); + let scope = preflight_scope(&files, &deps, &none); let got: Vec<(&str, bool)> = scope .iter() .map(|d| (d.patch_uuid.as_str(), d.already_pinned)) .collect(); assert_eq!(got, [("uuid-a", false), ("uuid-b", true)]); - assert!(preflight_scope(&BTreeMap::new(), &deps).is_empty()); + assert!(preflight_scope(&BTreeMap::new(), &deps, &none).is_empty()); let mut bom = files.clone(); bom.insert(VLT_LOCK.into(), format!("\u{feff}{}", files[VLT_LOCK])); - assert!(preflight_scope(&bom, &deps).is_empty()); + assert!(preflight_scope(&bom, &deps, &none).is_empty()); + } + + #[test] + fn a_vlt_vendored_takeover_is_scoped_through_its_vendored_node() { + let files = lock(&[ + r#""file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+e-1.0.0+node__modules+e": [0,"e",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/e-1.0.0/node_modules/e"]"#, + r#""file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+f-1.0.0+node__modules+f": [0,"f",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/f-1.0.0/node_modules/f"]"#, + ]); + let deps = [dep("e", Some("sha512-E")), dep("f", Some("sha512-F"))]; + assert!(preflight_scope(&files, &deps, &BTreeSet::new()).is_empty()); + let vendored = BTreeSet::from(["uuid-e".to_string()]); + let scope = preflight_scope(&files, &deps, &vendored); + assert_eq!( + scope, + [PreflightDep { + patch_uuid: "uuid-e".into(), + artifact_url: "https://patch.socket.dev/patch/npm/t/u/e-1.0.0.tgz".into(), + sha512: "sha512-E".into(), + already_pinned: false, + vendored: true, + }] + ); } } From 5a779c5e5896f31a04c3c8f9e4106ec4db567bea Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Sat, 26 Sep 2026 00:40:54 -0400 Subject: [PATCH 15/46] Vendor vlt projects as patched package dirs `socket-patch vendor` now wires projects that install with vlt. A vlt-lock.json (lockfileVersion 0 or 1) routes npm vendoring to a new vlt backend ahead of every other lockfile, and a package already vendored through another lockfile refuses with vendor_flavor_changed. A direct dependency of the root or a workspace member is committed as the patched package directory under .socket/vendor/npm//-/node_modules//, so packages that require their own name keep resolving. Its devDependencies are dropped from the vendored package.json, and the uuid dir's .gitignore and .gitattributes keep the payload committable and byte-exact on every checkout. The lock node, its importer edges and the importers' package.json specs move to the file: path, placed where vlt itself writes them, so `vlt ci` leaves the lock unchanged; this is checked against locks real vlt 1.2.0, 1.0.10 and 1.0.0-rc.14 wrote. Transitive targets, peer or modifier variants, foreign registries, peer edges, multi-field declarations, stale specs, unreadable locks and git-ignored payloads refuse before any write. `vendor --revert` restores the registry node, edges and specs while keeping whatever vlt re-laid since, or leaves everything on drift. Health checks, reuse and repair judge the directory with its package.json exemption, and lock inventory reads vlt-lock.json. Assisted-by: Claude Code:claude-opus-5-5 --- .gitattributes | 3 + CHANGELOG.md | 24 + .../tests/repair_vendor_flavors_e2e.rs | 4 +- crates/socket-patch-core/src/constants.rs | 13 + .../src/vendor/lock_inventory/mod.rs | 3 + .../src/vendor/lock_inventory/npm_family.rs | 22 +- .../src/vendor/lock_inventory/recover.rs | 16 + .../vendor/lock_inventory/recover_tests.rs | 45 + .../src/vendor/lock_inventory/tests.rs | 134 + .../src/vendor/lock_inventory/vlt.rs | 138 + .../src/vendor/lock_inventory/wired.rs | 4 +- crates/socket-patch-core/src/vendor/mod.rs | 17 +- .../src/vendor/npm_common.rs | 12 +- .../socket-patch-core/src/vendor/npm_dir.rs | 1028 +++++++ .../src/vendor/npm_flavor.rs | 316 ++- .../socket-patch-core/src/vendor/npm_lock.rs | 4 +- crates/socket-patch-core/src/vendor/path.rs | 66 +- .../src/vendor/registry_fetch.rs | 135 +- crates/socket-patch-core/src/vendor/reuse.rs | 52 + crates/socket-patch-core/src/vendor/state.rs | 6 +- crates/socket-patch-core/src/vendor/verify.rs | 246 +- .../socket-patch-core/src/vendor/vlt_lock.rs | 2399 +++++++++++++++++ .../src/vendor/vlt_lock_text.rs | 37 +- .../socket-patch-core/src/vex/discover/mod.rs | 1 + .../cases/alias-selfref-peer/case.json | 12 + .../alias-selfref-peer/expected/package.json | 9 + .../alias-selfref-peer/expected/vlt-lock.json | 19 + .../npm/vlt/1.0.0-rc.14/cases/alias/case.json | 7 + .../cases/alias/expected/package.json | 9 + .../cases/alias/expected/vlt-lock.json | 19 + .../vlt/1.0.0-rc.14/cases/dev-edge/case.json | 7 + .../cases/dev-edge/expected/package.json | 19 + .../dev-edge/expected/packages/a/package.json | 8 + .../cases/dev-edge/expected/vlt-lock.json | 42 + .../vlt/1.0.0-rc.14/cases/left-pad/case.json | 7 + .../cases/left-pad/expected/package.json | 19 + .../left-pad/expected/packages/a/package.json | 8 + .../cases/left-pad/expected/vlt-lock.json | 42 + .../1.0.0-rc.14/cases/member-only/case.json | 7 + .../cases/member-only/expected/package.json | 19 + .../expected/packages/a/package.json | 8 + .../cases/member-only/expected/vlt-lock.json | 42 + .../1.0.0-rc.14/cases/optional-edge/case.json | 7 + .../cases/optional-edge/expected/package.json | 19 + .../expected/packages/a/package.json | 8 + .../optional-edge/expected/vlt-lock.json | 42 + .../npm/vlt/1.0.0-rc.14/cases/peer/case.json | 7 + .../cases/peer/expected/package.json | 19 + .../peer/expected/packages/a/package.json | 8 + .../cases/peer/expected/vlt-lock.json | 42 + .../vlt/1.0.0-rc.14/cases/scoped/case.json | 7 + .../cases/scoped/expected/package.json | 19 + .../scoped/expected/packages/a/package.json | 8 + .../cases/scoped/expected/vlt-lock.json | 42 + .../vlt/1.0.0-rc.14/cases/semver/case.json | 7 + .../cases/semver/expected/package.json | 19 + .../semver/expected/packages/a/package.json | 8 + .../cases/semver/expected/vlt-lock.json | 42 + .../cases/supports-color/case.json | 7 + .../supports-color/expected/package.json | 19 + .../expected/packages/a/package.json | 8 + .../supports-color/expected/vlt-lock.json | 42 + .../1.0.0-rc.14/cases/transitive/case.json | 6 + .../1.0.0-rc.14/projects/alias/package.json | 9 + .../1.0.0-rc.14/projects/alias/vlt-lock.json | 19 + .../vlt/1.0.0-rc.14/projects/alias/vlt.json | 1 + .../projects/workspace/package.json | 19 + .../workspace/packages/a/package.json | 8 + .../projects/workspace/vlt-lock.json | 42 + .../1.0.0-rc.14/projects/workspace/vlt.json | 3 + .../1.0.10/cases/alias-selfref-peer/case.json | 6 + .../npm/vlt/1.0.10/cases/alias/case.json | 7 + .../1.0.10/cases/alias/expected/package.json | 9 + .../1.0.10/cases/alias/expected/vlt-lock.json | 23 + .../npm/vlt/1.0.10/cases/dev-edge/case.json | 7 + .../cases/dev-edge/expected/package.json | 19 + .../dev-edge/expected/packages/a/package.json | 8 + .../cases/dev-edge/expected/vlt-lock.json | 46 + .../npm/vlt/1.0.10/cases/left-pad/case.json | 7 + .../cases/left-pad/expected/package.json | 19 + .../left-pad/expected/packages/a/package.json | 8 + .../cases/left-pad/expected/vlt-lock.json | 46 + .../vlt/1.0.10/cases/member-only/case.json | 7 + .../cases/member-only/expected/package.json | 19 + .../expected/packages/a/package.json | 8 + .../cases/member-only/expected/vlt-lock.json | 46 + .../vlt/1.0.10/cases/optional-edge/case.json | 7 + .../cases/optional-edge/expected/package.json | 19 + .../expected/packages/a/package.json | 8 + .../optional-edge/expected/vlt-lock.json | 46 + .../npm/vlt/1.0.10/cases/peer/case.json | 6 + .../npm/vlt/1.0.10/cases/scoped/case.json | 7 + .../1.0.10/cases/scoped/expected/package.json | 19 + .../scoped/expected/packages/a/package.json | 8 + .../cases/scoped/expected/vlt-lock.json | 46 + .../npm/vlt/1.0.10/cases/semver/case.json | 7 + .../1.0.10/cases/semver/expected/package.json | 19 + .../semver/expected/packages/a/package.json | 8 + .../cases/semver/expected/vlt-lock.json | 46 + .../vlt/1.0.10/cases/supports-color/case.json | 7 + .../supports-color/expected/package.json | 19 + .../expected/packages/a/package.json | 8 + .../supports-color/expected/vlt-lock.json | 46 + .../npm/vlt/1.0.10/cases/transitive/case.json | 6 + .../vlt/1.0.10/projects/alias/package.json | 9 + .../vlt/1.0.10/projects/alias/vlt-lock.json | 23 + .../npm/vlt/1.0.10/projects/alias/vlt.json | 7 + .../1.0.10/projects/workspace/package.json | 19 + .../workspace/packages/a/package.json | 8 + .../1.0.10/projects/workspace/vlt-lock.json | 46 + .../vlt/1.0.10/projects/workspace/vlt.json | 8 + .../1.2.0/cases/alias-selfref-peer/case.json | 6 + .../npm/vlt/1.2.0/cases/alias/case.json | 7 + .../1.2.0/cases/alias/expected/package.json | 9 + .../1.2.0/cases/alias/expected/vlt-lock.json | 23 + .../npm/vlt/1.2.0/cases/dev-edge/case.json | 7 + .../cases/dev-edge/expected/package.json | 19 + .../dev-edge/expected/packages/a/package.json | 8 + .../cases/dev-edge/expected/vlt-lock.json | 46 + .../npm/vlt/1.2.0/cases/left-pad/case.json | 7 + .../cases/left-pad/expected/package.json | 19 + .../left-pad/expected/packages/a/package.json | 8 + .../cases/left-pad/expected/vlt-lock.json | 46 + .../npm/vlt/1.2.0/cases/member-only/case.json | 7 + .../cases/member-only/expected/package.json | 19 + .../expected/packages/a/package.json | 8 + .../cases/member-only/expected/vlt-lock.json | 46 + .../vlt/1.2.0/cases/optional-edge/case.json | 7 + .../cases/optional-edge/expected/package.json | 19 + .../expected/packages/a/package.json | 8 + .../optional-edge/expected/vlt-lock.json | 46 + .../vendor/npm/vlt/1.2.0/cases/peer/case.json | 6 + .../npm/vlt/1.2.0/cases/scoped/case.json | 7 + .../1.2.0/cases/scoped/expected/package.json | 19 + .../scoped/expected/packages/a/package.json | 8 + .../1.2.0/cases/scoped/expected/vlt-lock.json | 46 + .../npm/vlt/1.2.0/cases/semver/case.json | 7 + .../1.2.0/cases/semver/expected/package.json | 19 + .../semver/expected/packages/a/package.json | 8 + .../1.2.0/cases/semver/expected/vlt-lock.json | 46 + .../vlt/1.2.0/cases/supports-color/case.json | 7 + .../supports-color/expected/package.json | 19 + .../expected/packages/a/package.json | 8 + .../supports-color/expected/vlt-lock.json | 46 + .../npm/vlt/1.2.0/cases/transitive/case.json | 6 + .../npm/vlt/1.2.0/projects/alias/package.json | 9 + .../vlt/1.2.0/projects/alias/vlt-lock.json | 23 + .../npm/vlt/1.2.0/projects/alias/vlt.json | 7 + .../vlt/1.2.0/projects/workspace/package.json | 19 + .../workspace/packages/a/package.json | 8 + .../1.2.0/projects/workspace/vlt-lock.json | 46 + .../npm/vlt/1.2.0/projects/workspace/vlt.json | 8 + .../fixtures/vendor/npm/vlt/regenerate.sh | 141 + .../tests/fixtures/vendor/npm/vlt/surgery.mjs | 242 ++ crates/socket-patch-core/tests/vlt_locks.rs | 506 ++++ 155 files changed, 7791 insertions(+), 87 deletions(-) create mode 100644 crates/socket-patch-core/src/vendor/lock_inventory/vlt.rs create mode 100644 crates/socket-patch-core/src/vendor/npm_dir.rs create mode 100644 crates/socket-patch-core/src/vendor/vlt_lock.rs create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias-selfref-peer/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias-selfref-peer/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias-selfref-peer/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/dev-edge/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/dev-edge/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/dev-edge/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/dev-edge/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/left-pad/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/left-pad/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/left-pad/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/left-pad/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/member-only/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/member-only/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/member-only/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/member-only/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/optional-edge/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/optional-edge/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/optional-edge/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/optional-edge/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/peer/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/peer/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/peer/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/peer/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/scoped/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/scoped/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/scoped/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/scoped/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/semver/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/semver/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/semver/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/semver/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/supports-color/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/supports-color/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/supports-color/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/supports-color/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/transitive/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/alias/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/alias/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/alias/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/workspace/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/workspace/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/workspace/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/workspace/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/alias-selfref-peer/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/alias/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/alias/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/alias/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/dev-edge/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/dev-edge/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/dev-edge/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/dev-edge/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/left-pad/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/left-pad/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/left-pad/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/left-pad/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/member-only/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/member-only/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/member-only/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/member-only/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/optional-edge/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/optional-edge/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/optional-edge/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/optional-edge/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/peer/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/scoped/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/scoped/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/scoped/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/scoped/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/semver/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/semver/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/semver/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/semver/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/supports-color/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/supports-color/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/supports-color/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/supports-color/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/transitive/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/alias/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/alias/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/alias/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/workspace/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/workspace/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/workspace/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/workspace/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/alias-selfref-peer/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/alias/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/alias/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/alias/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/dev-edge/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/dev-edge/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/dev-edge/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/dev-edge/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/left-pad/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/left-pad/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/left-pad/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/left-pad/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/member-only/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/member-only/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/member-only/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/member-only/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/optional-edge/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/optional-edge/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/optional-edge/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/optional-edge/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/peer/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/scoped/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/scoped/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/scoped/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/scoped/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/semver/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/semver/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/semver/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/semver/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/supports-color/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/supports-color/expected/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/supports-color/expected/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/supports-color/expected/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/transitive/case.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/alias/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/alias/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/alias/vlt.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/workspace/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/workspace/packages/a/package.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/workspace/vlt-lock.json create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/workspace/vlt.json create mode 100755 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/regenerate.sh create mode 100644 crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/surgery.mjs diff --git a/.gitattributes b/.gitattributes index 679f441f..5c7b89a0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -13,3 +13,6 @@ crates/socket-patch-core/tests/fixtures/pnpm-hosted/** -text # Captured vlt locks are byte-real; CRLF variants are derived in the tests. crates/socket-patch-core/tests/fixtures/vlt-locks/** -text + +# The vendored vlt fixtures pin locks real vlt wrote, byte for byte. +crates/socket-patch-core/tests/fixtures/vendor/** -text diff --git a/CHANGELOG.md b/CHANGELOG.md index 006973ea..a5aa36ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -308,6 +308,30 @@ into the new version's section — see docs/releasing.md. unchecked, whose lock a vlt release may ignore, or which also resolves from a non-default registry. vlt ledgers require the socket-patch release that adds vlt support. +- **`vendor` wires vlt projects.** A `vlt-lock.json` (lockfileVersion 0 + or 1) routes npm vendoring to the new vlt backend ahead of every other + lockfile. A direct dependency of the root or of a workspace member is + vendored as a patched package directory, + `.socket/vendor/npm//-/node_modules//`, so a + package that `require()`s its own name still resolves; its + `devDependencies` are dropped from the vendored `package.json`, and the + uuid dir's `.gitignore` re-includes the payload against the project's + own ignores while `.gitattributes` keeps EOL conversion off it. The + lock's node becomes a `file` node, its importer edges and the importers' + `package.json` specs move to the `file:` path, and every moved entry is + placed where vlt's own serializer puts it, so `vlt ci` keeps the lock + byte-identical (checked against vlt 1.2.0, 1.0.10 and 1.0.0-rc.14). + Transitive targets (`vendor_vlt_transitive_unsupported`), peer or + modifier variants, foreign registries, peer edges and dependencies + declared in several fields refuse before any write, as do locks vlt + cannot read and specs that no longer match the lock + (`vendor_vlt_lock_out_of_sync`); a payload git would ignore refuses with + `vendor_artifact_gitignored`, and a package already vendored through + another lockfile flavor with `vendor_flavor_changed`. `vendor --revert` + restores the registry node, edges and specs (keeping flags, trailing + slots and outgoing edge values vlt rewrote since) or keeps everything on + drift. Lock inventory reads `vlt-lock.json` too, Socket-hosted pins + included. - **`redirect_yarn_berry_mixed_line_endings` and `vendor_yarn_berry_mixed_line_endings`.** A `yarn.lock` (or, vendored, a root `package.json`) that mixes CRLF and LF line endings — or holds a bare diff --git a/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e.rs b/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e.rs index c934abd4..17168c4f 100644 --- a/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e.rs +++ b/crates/socket-patch-cli/tests/repair_vendor_flavors_e2e.rs @@ -602,7 +602,7 @@ async fn repair_rebuilds_corrupt_bun_tarball() { } /// An entry stamped with a flavor this release has no backend for (written -/// by a newer socket-patch, e.g. `vlt`) is never judged or rebuilt: repair +/// by a newer socket-patch, e.g. `future-pm`) is never judged or rebuilt: repair /// warns `vendor_wiring_unknown_revert_blocked` and leaves the ledger, the /// lock and the (here deleted) artifact exactly as found. #[tokio::test] @@ -618,7 +618,7 @@ async fn repair_skips_an_entry_with_an_unknown_flavor() { let state_path = tmp.path().join(".socket/vendor/state.json"); let mut v: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(&state_path).unwrap()).unwrap(); - v["entries"][PURL]["flavor"] = serde_json::json!("vlt"); + v["entries"][PURL]["flavor"] = serde_json::json!("future-pm"); std::fs::write(&state_path, serde_json::to_vec_pretty(&v).unwrap()).unwrap(); let state_before = std::fs::read(&state_path).unwrap(); let lock_before = std::fs::read(tmp.path().join(flavor.lock_name())).unwrap(); diff --git a/crates/socket-patch-core/src/constants.rs b/crates/socket-patch-core/src/constants.rs index 8aa1a1ae..3f872bbb 100644 --- a/crates/socket-patch-core/src/constants.rs +++ b/crates/socket-patch-core/src/constants.rs @@ -158,6 +158,19 @@ pub mod npm_family { redirect_candidate: true, detects_pnpm: false, }, + FileRow { + name: "vlt-lock.json", + vendor_probe: true, + redirect_candidate: true, + detects_pnpm: false, + }, + // vlt's config: a read-only redirect input, never wired. + FileRow { + name: "vlt.json", + vendor_probe: false, + redirect_candidate: true, + detects_pnpm: false, + }, // deno.lock is deliberately absent: deno is its own ecosystem // (JSR-crawled); no npm-family vendor/redirect/detection path treats // deno.lock as an npm lock today. Adding it here is a feature diff --git a/crates/socket-patch-core/src/vendor/lock_inventory/mod.rs b/crates/socket-patch-core/src/vendor/lock_inventory/mod.rs index 774ad377..1c3bc381 100644 --- a/crates/socket-patch-core/src/vendor/lock_inventory/mod.rs +++ b/crates/socket-patch-core/src/vendor/lock_inventory/mod.rs @@ -65,6 +65,7 @@ pub(crate) mod npm_family; pub(crate) mod pnpm; pub(crate) mod pypi; pub(crate) mod recover; +pub(crate) mod vlt; pub(crate) mod wired; pub(crate) mod yarn; @@ -92,6 +93,7 @@ use self::{ pnpm::{inventory_pnpm_lock, inventory_pnpm_lock_at}, pypi::{is_public_pypi_url, python_lock_inventory, socket_reference_coords}, recover::pure_wheel_from_uv_unit, + vlt::inventory_vlt, yarn::{inventory_yarn_berry, inventory_yarn_classic}, }; #[cfg(test)] @@ -430,6 +432,7 @@ mod architecture_tests { "pnpm.rs", "yarn.rs", "bun.rs", + "vlt.rs", "cargo.rs", "golang.rs", "composer.rs", diff --git a/crates/socket-patch-core/src/vendor/lock_inventory/npm_family.rs b/crates/socket-patch-core/src/vendor/lock_inventory/npm_family.rs index a82a4dda..ecc45c4a 100644 --- a/crates/socket-patch-core/src/vendor/lock_inventory/npm_family.rs +++ b/crates/socket-patch-core/src/vendor/lock_inventory/npm_family.rs @@ -4,13 +4,16 @@ use std::path::Path; -use crate::constants::npm_family::{BUN_LOCK, BUN_LOCKB, NPM_LOCKS, PNPM_SHRINKWRAP_LEGACY}; +use crate::constants::npm_family::{ + BUN_LOCK, BUN_LOCKB, NPM_LOCKS, PNPM_SHRINKWRAP_LEGACY, VLT_LOCK, +}; use crate::utils::purl::npm_purl; use crate::vendor::npm_flavor::{detect_npm_lock_flavor, NpmLockFlavor}; use super::bun::{bun_text_lock_present, inventory_bun, inventory_bun_binary}; use super::npm::inventory_package_lock; use super::pnpm::{inventory_pnpm_lock, inventory_pnpm_lock_at, inventory_rush_pnpm_locks}; +use super::vlt::inventory_vlt; use super::yarn::{inventory_yarn_berry, inventory_yarn_classic}; use super::{dedup_prefer_integrity, LockfileEntry, UnsupportedNpmLayout}; @@ -142,6 +145,7 @@ pub(super) async fn inventory_npm_lock_raw( Some(inventory_bun_binary(project_root).await?) } } + NpmLockFlavor::Vlt => inventory_vlt(project_root).await, }; Ok(raw.map(|raw| (flavor, guard_npm(raw)))) } @@ -150,8 +154,8 @@ pub(super) async fn inventory_npm_lock_raw( /// shadowing, or `None` when no sibling lock file exists at all. /// /// [`detect_npm_lock_flavor`] cannot be re-asked (it already refused on its -/// pnpm step), so this mirrors the rest of its precedence by hand — bun, -/// then yarn, then npm — on file EXISTENCE, and returns the first present +/// pnpm step), so this mirrors the rest of its precedence by hand — vlt, +/// bun, then yarn, then npm — on file EXISTENCE, and returns the first present /// sibling's inventory (possibly empty: presence alone proves the pnpm lock /// is migration debris, so the caller must not fall back to it). Raw /// entries — the caller guards and collapses them. @@ -162,7 +166,13 @@ pub(super) async fn inventory_live_sibling_lock( let p = root.join(name); async move { tokio::fs::metadata(&p).await.is_ok() } }; - // bun.lock — router step 2. That step runs BEFORE the pnpm sniff, so + if exists(VLT_LOCK).await { + return Some(( + NpmLockFlavor::Vlt, + inventory_vlt(root).await.unwrap_or_default(), + )); + } + // bun.lock — router step 3. That step runs BEFORE the pnpm sniff, so // when the version refusal fired no bun.lock can actually be present; // probed anyway to keep this a literal transcription of the router's // order. The binary lock shares the same routing precedence. @@ -178,7 +188,7 @@ pub(super) async fn inventory_live_sibling_lock( inventory_bun_binary(root).await.unwrap_or_default(), )); } - // yarn.lock — router step 4, where classic vs berry is a content + // yarn.lock — router step 5, where classic vs berry is a content // decision. Rather than re-deriving that head sniff, try both readers: // each yields entries only for its own grammar (classic's `version "…"` // fields vs berry's `resolution:` lines), so a non-empty result is the @@ -194,7 +204,7 @@ pub(super) async fn inventory_live_sibling_lock( inventory_yarn_berry(root).await.unwrap_or_default(), )); } - // npm — router step 5 (`inventory_package_lock` itself prefers the + // npm — router step 6 (`inventory_package_lock` itself prefers the // shrinkwrap when both exist, mirroring npm). if exists(NPM_LOCKS[0]).await || exists(NPM_LOCKS[1]).await { return Some(( diff --git a/crates/socket-patch-core/src/vendor/lock_inventory/recover.rs b/crates/socket-patch-core/src/vendor/lock_inventory/recover.rs index ebd8cdce..0e58a9a8 100644 --- a/crates/socket-patch-core/src/vendor/lock_inventory/recover.rs +++ b/crates/socket-patch-core/src/vendor/lock_inventory/recover.rs @@ -406,6 +406,22 @@ fn recover_npm_fragment( return Ok(mk(resolved, LockIntegrity::Sri(sri.to_string()))); } } + // vlt: the original is the registry node's entry text; slot [2] is its + // integrity and slot [3] (from vlt 1.0.0-rc.33) its tarball URL. + if let Some(text) = wiring_original(entry, &["vlt_lock_node"]).and_then(Value::as_str) { + if let Some(node) = crate::vendor::vlt_lock_text::parse_node_entry_text(text) { + let slot = |i: usize| { + node.slot(i) + .and_then(|raw| serde_json::from_str::(raw).ok()) + }; + if let Some(sri) = slot(2).filter(|s| is_sri_pin(s)) { + return Ok(mk( + slot(3).and_then(|u| http_url(&u)), + LockIntegrity::Sri(sri), + )); + } + } + } // bun: the original is the raw tuple line; the integrity is its last // quoted SRI string. if let Some(line) = diff --git a/crates/socket-patch-core/src/vendor/lock_inventory/recover_tests.rs b/crates/socket-patch-core/src/vendor/lock_inventory/recover_tests.rs index 5e93e21d..9c392a69 100644 --- a/crates/socket-patch-core/src/vendor/lock_inventory/recover_tests.rs +++ b/crates/socket-patch-core/src/vendor/lock_inventory/recover_tests.rs @@ -83,6 +83,51 @@ async fn npm_lock_entry_fragment_recovers_sri_and_url() { assert_eq!(got.integrity, LockIntegrity::Sri("sha512-AAAA".into())); } +#[tokio::test] +async fn vlt_lock_node_original_recovers_sri_and_url() { + let tmp = tempfile::tempdir().unwrap(); + let node = |tuple: &str| { + entry( + "npm", + "pkg:npm/%40scope/x@1.2.3", + vec![rec( + "vlt_lock_node", + serde_json::Value::String(format!("\"~npm~@scope+x@1.2.3\": {tuple}")), + )], + ) + }; + let got = recover_lock_entry( + tmp.path(), + &node( + r#"[0,"@scope/x","sha512-AAAA","https://registry.npmjs.org/@scope/x/-/x-1.2.3.tgz"]"#, + ), + ) + .await + .unwrap(); + assert_eq!( + (got.name.as_str(), got.version.as_str()), + ("@scope/x", "1.2.3") + ); + assert_eq!( + got.resolved.as_deref(), + Some("https://registry.npmjs.org/@scope/x/-/x-1.2.3.tgz") + ); + assert_eq!(got.integrity, LockIntegrity::Sri("sha512-AAAA".into())); + + let got = recover_lock_entry(tmp.path(), &node(r#"[0,"@scope/x","sha512-AAAA"]"#)) + .await + .unwrap(); + assert_eq!( + got.resolved, None, + "a 3-tuple leaves the URL to the fetcher" + ); + assert!( + recover_lock_entry(tmp.path(), &node(r#"[0,"@scope/x",null,"file:x"]"#)) + .await + .is_err() + ); +} + #[tokio::test] async fn bun_binary_snapshot_recovers_registry_metadata_and_checks_coordinates() { let tmp = tempfile::tempdir().unwrap(); diff --git a/crates/socket-patch-core/src/vendor/lock_inventory/tests.rs b/crates/socket-patch-core/src/vendor/lock_inventory/tests.rs index 2dbdf96b..a7366e5c 100644 --- a/crates/socket-patch-core/src/vendor/lock_inventory/tests.rs +++ b/crates/socket-patch-core/src/vendor/lock_inventory/tests.rs @@ -2425,3 +2425,137 @@ fn pnpm_resolution_tokens_cover_maps_the_grammar_refuses() { assert!(ok.resolution.is_some()); assert_eq!(ok.resolution_tokens(), vec!["integrity:", "sha512-ok"]); } + +// ── vlt ─────────────────────────────────────────────────────────────── + +const VLT_UUID: &str = "9f6b2c4e-1d3a-4f6b-8c2d-7e5a9b1c3d5f"; + +fn vlt_lock(options: &str, nodes: &[&str]) -> String { + format!( + "{{\n \"lockfileVersion\": 1,\n \"options\": {options},\n \"nodes\": {{\n{}\n }},\n \"edges\": {{}}\n}}\n", + nodes + .iter() + .map(|n| format!(" {n}")) + .collect::>() + .join(",\n") + ) +} + +#[tokio::test] +async fn vlt_registry_nodes_inventory_with_their_location_and_integrity() { + let hosted = format!("https://patch.socket.dev/patch/npm/t/{VLT_UUID}/left-pad-1.3.0.tgz"); + let vendored = format!(".socket/vendor/npm/{VLT_UUID}/ms-2.1.3/node_modules/ms"); + let lock = vlt_lock( + r#"{"registries": {"acme": "https://npm.acme.test"}}"#, + &[ + &format!(r#""~npm~left-pad@1.3.0": [0,"left-pad","sha512-PATCHED==","{hosted}"]"#), + r#""~npm~@scope+pkg@2.0.0": [0,"@scope/pkg","sha512-scoped=="]"#, + r#""~acme~private@1.0.0": [0,"private","sha512-acme=="]"#, + r#""~ghost~lost@1.0.0": [0,"lost","sha512-lost=="]"#, + r#""~http_c++127.0.0.1_c4873+~u@1.0.0": [0,"u"]"#, + r#""~npm~alias-name@1.0.0": [0,"other"]"#, + &format!( + r#""file~.socket+vendor+npm+{VLT_UUID}+ms-2.1.3+node__modules+ms": [0,"ms",null,"{vendored}"]"# + ), + r#""git~github_cu+p~": [0,"p"]"#, + ], + ); + let tmp = tempfile::tempdir().unwrap(); + write(tmp.path(), "vlt-lock.json", &lock).await; + let (flavor, entries) = inventory_npm_lock(tmp.path()).await.unwrap().unwrap(); + assert_eq!(flavor, NpmLockFlavor::Vlt); + let mut names: Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect(); + names.sort_unstable(); + assert_eq!(names, ["@scope/pkg", "left-pad", "lost", "private", "u"]); + + let left_pad = entry(&entries, "left-pad"); + assert_eq!(left_pad.resolved.as_deref(), Some(hosted.as_str())); + assert_eq!( + left_pad.integrity, + LockIntegrity::Sri("sha512-PATCHED==".into()) + ); + assert_eq!( + entry(&entries, "@scope/pkg").resolved.as_deref(), + Some("https://registry.npmjs.org/@scope/pkg/-/pkg-2.0.0.tgz") + ); + assert_eq!( + entry(&entries, "private").resolved.as_deref(), + Some("https://npm.acme.test/private/-/private-1.0.0.tgz") + ); + assert_eq!(entry(&entries, "lost").resolved, None); + let u = entry(&entries, "u"); + assert_eq!( + u.resolved.as_deref(), + Some("http://127.0.0.1:4873/u/-/u-1.0.0.tgz") + ); + assert_eq!(u.integrity, LockIntegrity::None); + assert_eq!(inventory_vlt(tmp.path()).await.unwrap().len(), 5); +} + +#[tokio::test] +async fn vlt_default_registry_base_follows_the_lock_options() { + for (options, want) in [ + (r#"{"registry": "https://r.test"}"#, "https://r.test/"), + ( + r#"{"registries": {"npm": "https://mirror.test/npm/"}}"#, + "https://mirror.test/npm/", + ), + ("{}", "https://registry.npmjs.org/"), + ] { + let tmp = tempfile::tempdir().unwrap(); + let lock = vlt_lock(options, &[r#""~npm~a@1.0.0": [0,"a","sha512-a=="]"#]); + write(tmp.path(), "vlt-lock.json", &lock).await; + let entries = inventory_vlt(tmp.path()).await.unwrap(); + assert_eq!( + entries[0].resolved.as_deref(), + Some(format!("{want}a/-/a-1.0.0.tgz").as_str()), + "{options}" + ); + } +} + +#[tokio::test] +async fn unreadable_vlt_locks_inventory_to_nothing() { + let good = vlt_lock("{}", &[r#""~npm~a@1.0.0": [0,"a","sha512-a=="]"#]); + for lock in [ + format!("\u{feff}{good}"), + good.replace("\"lockfileVersion\": 1", "\"lockfileVersion\": 2"), + "{\"lockfileVersion\": 1,".to_string(), + ] { + let tmp = tempfile::tempdir().unwrap(); + write(tmp.path(), "vlt-lock.json", &lock).await; + assert!(inventory_vlt(tmp.path()).await.is_none(), "{lock:?}"); + assert!(inventory_project(tmp.path()).await.is_empty(), "{lock:?}"); + } + // A pre-lockfileVersion lock is still read-only readable. + let tmp = tempfile::tempdir().unwrap(); + write( + tmp.path(), + "vlt-lock.json", + &good + .replace(" \"lockfileVersion\": 1,\n", "") + .replace("~npm~", "··"), + ) + .await; + assert_eq!(inventory_vlt(tmp.path()).await.unwrap().len(), 1); +} + +#[tokio::test] +async fn vlt_lock_wins_the_sibling_order_behind_a_refused_pnpm_lock() { + let tmp = tempfile::tempdir().unwrap(); + write( + tmp.path(), + "vlt-lock.json", + &vlt_lock("{}", &[r#""~npm~a@1.0.0": [0,"a","sha512-a=="]"#]), + ) + .await; + write(tmp.path(), "package-lock.json", PACKAGE_LOCK).await; + let (flavor, entries) = inventory_npm_lock(tmp.path()).await.unwrap().unwrap(); + assert_eq!(flavor, NpmLockFlavor::Vlt); + assert_eq!(entries.len(), 1); + let (flavor, entries) = super::npm_family::inventory_live_sibling_lock(tmp.path()) + .await + .unwrap(); + assert_eq!(flavor, NpmLockFlavor::Vlt); + assert_eq!(entries.len(), 1); +} diff --git a/crates/socket-patch-core/src/vendor/lock_inventory/vlt.rs b/crates/socket-patch-core/src/vendor/lock_inventory/vlt.rs new file mode 100644 index 00000000..e05d69db --- /dev/null +++ b/crates/socket-patch-core/src/vendor/lock_inventory/vlt.rs @@ -0,0 +1,138 @@ +//! `vlt-lock.json`: the entry model lockfile discovery shares +//! ([`vlt_lock_nodes`]) and the registry view (DESIGN §4.8). + +use std::path::Path; + +use serde_json::{Map, Value}; + +use crate::constants::npm_family::VLT_LOCK; +use crate::utils::fs::read_regular_to_string; +use crate::vendor::vlt_lock_text::{ + is_default_registry, sniff_lock, split_dep_id, DepId, DepIdKind, LockSniff, +}; + +use super::{http_url, LockIntegrity, LockfileEntry}; + +// ── entry model ── + +/// One node of a readable `vlt-lock.json`: its DepID split, slot [1] (the +/// package name) and the raw slot [2] integrity and slot [3] location. +#[derive(Debug, Clone)] +pub(crate) struct VltLockNode { + pub(crate) dep_id: DepId, + pub(crate) name: String, + pub(crate) integrity: Option, + pub(crate) location: Option, +} + +/// A readable `vlt-lock.json`: its `options` and every node whose id splits. +#[derive(Debug, Clone)] +pub(crate) struct VltLock { + pub(crate) options: Option>, + pub(crate) nodes: Vec, +} + +/// The nodes of a lock vlt itself can read; `None` for a BOM-prefixed, +/// unparseable or unknown-version lock (never BOM-stripped). +pub(crate) fn vlt_lock_nodes(text: &str) -> Option { + let LockSniff::Readable(lock) = sniff_lock(text) else { + return None; + }; + let string_slot = |tuple: &[Value], i: usize| { + tuple + .get(i) + .and_then(Value::as_str) + .filter(|s| !s.is_empty()) + .map(str::to_string) + }; + let nodes = lock + .nodes() + .map(|nodes| { + nodes + .iter() + .filter_map(|(id, tuple)| { + let tuple = tuple.as_array()?; + Some(VltLockNode { + dep_id: split_dep_id(id)?, + name: tuple.get(1)?.as_str()?.to_string(), + integrity: string_slot(tuple, 2), + location: string_slot(tuple, 3), + }) + }) + .collect() + }) + .unwrap_or_default(); + Some(VltLock { + options: lock.options().cloned(), + nodes, + }) +} + +fn with_slash(url: &str) -> String { + if url.ends_with('/') { + url.to_string() + } else { + format!("{url}/") + } +} + +/// The registry base a node's segment names: the segment URL itself, the +/// alias's `options.registries` URL, or for the default registry +/// `options.registry`, `options.registries.npm`, else the public registry. +/// `None` for an alias the options do not map. +fn registry_base(segment: &str, options: Option<&Map>) -> Option { + let option = |path: &[&str]| { + let mut value = options.map(|o| Value::Object(o.clone()))?; + for key in path { + value = value.get(*key)?.clone(); + } + value.as_str().map(str::to_string) + }; + if segment.starts_with("https://") || segment.starts_with("http://") { + return Some(with_slash(segment)); + } + if is_default_registry(segment, options) { + let base = option(&["registry"]) + .or_else(|| option(&["registries", "npm"])) + .unwrap_or_else(|| "https://registry.npmjs.org/".to_string()); + return Some(with_slash(&base)); + } + option(&["registries", segment]).map(|base| with_slash(&base)) +} + +/// The registry entries of a readable lock: every registry node whose slot +/// [1] is its DepID name. The location is slot [3] when it is an http(s) +/// URL (a Socket-hosted pin included: it is the installed pair), else the +/// conventional tarball URL of its registry. +pub(crate) fn vlt_registry_entries(lock: &VltLock) -> Vec { + let options = lock.options.as_ref(); + lock.nodes + .iter() + .filter(|node| node.dep_id.kind == DepIdKind::Registry) + .filter_map(|node| { + let (name, version) = node.dep_id.registry_identity()?; + if node.name != name { + return None; + } + let bare = name.rsplit('/').next().unwrap_or(name); + let resolved = node.location.as_deref().and_then(http_url).or_else(|| { + registry_base(&node.dep_id.first, options) + .map(|base| format!("{base}{name}/-/{bare}-{version}.tgz")) + }); + let integrity = node + .integrity + .clone() + .map_or(LockIntegrity::None, LockIntegrity::Sri); + Some(LockfileEntry::npm(name, version, resolved, integrity)) + }) + .collect() +} + +// ── registry view ── + +/// Inventory the root `vlt-lock.json`. Vendored `file` nodes are not +/// registry nodes and never appear; hosted pins stay (pnpm parity). +pub(super) async fn inventory_vlt(root: &Path) -> Option> { + let text = read_regular_to_string(&root.join(VLT_LOCK)).await.ok()?; + vlt_lock_nodes(&text).map(|lock| vlt_registry_entries(&lock)) +} diff --git a/crates/socket-patch-core/src/vendor/lock_inventory/wired.rs b/crates/socket-patch-core/src/vendor/lock_inventory/wired.rs index e2ee04ca..35c2a2f3 100644 --- a/crates/socket-patch-core/src/vendor/lock_inventory/wired.rs +++ b/crates/socket-patch-core/src/vendor/lock_inventory/wired.rs @@ -25,7 +25,9 @@ use super::LockIntegrity; /// /// package-lock/shrinkwrap are parsed as JSON; the text formats (pnpm, /// yarn classic/berry, bun) are scanned with a bounded forward window from -/// each reference line. +/// each reference line. vlt yields `None`: its `file` nodes pin no +/// integrity (slot [2] is `null`), and `vlt-lock.json` is never scanned, +/// because the forward window would pick up a neighbouring node's sha512. pub async fn wired_vendor_integrity( project_root: &Path, artifact_rel: &str, diff --git a/crates/socket-patch-core/src/vendor/mod.rs b/crates/socket-patch-core/src/vendor/mod.rs index da961b13..8bdd68db 100644 --- a/crates/socket-patch-core/src/vendor/mod.rs +++ b/crates/socket-patch-core/src/vendor/mod.rs @@ -13,7 +13,7 @@ //! //! | eco | artifact | wiring | //! |----------|---------------------|------------------------------------------------| -//! | npm | deterministic tgz | per lockfile flavor: package-lock `resolved`+`integrity`, yarn classic, yarn berry, pnpm, bun ([`npm_flavor`] routes) | +//! | npm | deterministic tgz (vlt: package dir) | per lockfile flavor: package-lock `resolved`+`integrity`, yarn classic, yarn berry, pnpm, bun, vlt ([`npm_flavor`] routes) | //! | cargo | crate dir | root `Cargo.toml` `[patch.crates-io]` + Cargo.lock surgery ([`cargo_manifest`]) | //! | golang | module dir | `go.mod` `replace` ([`ReplaceOwner::Vendor`]) | //! | composer | package dir | composer.lock `dist` → `{type: path}` | @@ -69,6 +69,7 @@ pub mod lock_inventory; pub(crate) mod maven_pom; pub mod maven_repo; pub(crate) mod npm_common; +pub(crate) mod npm_dir; pub mod npm_flavor; pub mod npm_lock; mod npm_pack; @@ -92,6 +93,7 @@ pub(crate) mod service_fetch; pub(crate) mod test_support; mod toml_surgery; pub(crate) mod verify; +pub mod vlt_lock; #[allow(dead_code)] pub(crate) mod vlt_lock_text; pub(crate) mod yarn_berry_lock; @@ -506,9 +508,12 @@ pub async fn harvest_artifact_blobs_from( } continue; } - // Dir-shaped artifacts (cargo/golang/composer/gem copies): the - // record keys are package-relative, so resolve each needed file - // directly instead of walking the whole tree. + // Dir-shaped artifacts (cargo/golang/composer/gem copies, vlt + // package dirs): the record keys are package-relative, so resolve + // each needed file directly instead of walking the whole tree. A vlt + // dir's package.json is post-transform, never the afterHash blob, + // and its node_modules holds vlt's links. + let vlt_dir = entry.ecosystem == "npm" && entry.flavor.as_deref() == Some(vlt_lock::FLAVOR); if tokio::fs::metadata(&artifact) .await .is_ok_and(|m| m.is_dir()) @@ -518,7 +523,9 @@ pub async fn harvest_artifact_blobs_from( continue; } let rel = normalize_file_path(file_name); - if !is_safe_relative_subpath(rel) { + if !is_safe_relative_subpath(rel) + || (vlt_dir && (rel == "package.json" || rel.starts_with("node_modules/"))) + { continue; } let path = artifact.join(rel); diff --git a/crates/socket-patch-core/src/vendor/npm_common.rs b/crates/socket-patch-core/src/vendor/npm_common.rs index fd345d07..67c2c002 100644 --- a/crates/socket-patch-core/src/vendor/npm_common.rs +++ b/crates/socket-patch-core/src/vendor/npm_common.rs @@ -712,9 +712,15 @@ pub(crate) fn is_safe_npm_name(name: &str) -> bool { /// The artifact path under the uuid dir: `[@scope/]-.tgz`, /// with the scope kept as a real subdirectory. pub(super) fn tgz_rel_leaf(name: &str, version: &str) -> String { + format!("{}.tgz", pkg_rel_leaf(name, version)) +} + +/// `[@scope/]-`: the tarball leaf without `.tgz`, which the +/// vlt directory artifact uses as its version-bearing level. +pub(crate) fn pkg_rel_leaf(name: &str, version: &str) -> String { match name.split_once('/') { - Some((scope, bare)) => format!("{scope}/{bare}-{version}.tgz"), - None => format!("{name}-{version}.tgz"), + Some((scope, bare)) => format!("{scope}/{bare}-{version}"), + None => format!("{name}-{version}"), } } @@ -733,7 +739,7 @@ pub(crate) fn tgz_leaf_version<'l>(name: &str, leaf: &'l str) -> Option<&'l str> /// to `Object.keys(bd)` for any other truthy value — so an OBJECT form /// bundles its keys too; any of these makes the package unvendorable (see /// the refusal site). -fn declares_bundled_deps(pkg: &Value) -> bool { +pub(super) fn declares_bundled_deps(pkg: &Value) -> bool { ["bundleDependencies", "bundledDependencies"] .iter() .any(|k| match pkg.get(*k) { diff --git a/crates/socket-patch-core/src/vendor/npm_dir.rs b/crates/socket-patch-core/src/vendor/npm_dir.rs new file mode 100644 index 00000000..d847a637 --- /dev/null +++ b/crates/socket-patch-core/src/vendor/npm_dir.rs @@ -0,0 +1,1028 @@ +//! Directory artifacts for the vlt backend (DESIGN §4.2–§4.4). +//! +//! vlt installs a `file:` directory dependency by linking it, so the vendored +//! artifact is the patched package directory itself, laid out as +//! `.socket/vendor/npm//[@s/]-/node_modules//`: +//! Node resolves a package's `require('')` by walking up from its +//! realpath, and only a `node_modules/` ancestor makes that work for +//! root, workspace-member and alias edges alike. The `-` +//! level keeps the version in the path, because a committed `file` node +//! records none. vlt writes the dependency links under the package's own +//! `node_modules/`, which socket-patch never inventories, reads or creates. +//! +//! `/.gitignore` re-includes the payload against the user's ignores +//! (JS repos routinely ignore `dist/`, `lib/` and `*.map`, exactly what +//! packages publish) while keeping vlt's links out, and `/.gitattributes` +//! stops EOL conversion from rewriting the payload on a Windows checkout. +//! +//! Two deterministic transforms run on every tree, service-built or local: +//! the top-level `devDependencies` member is cut out of `package.json` (vlt +//! installs a `file` node's devDependencies), and nothing else changes. The +//! verifiers then apply the vlt manifest exemption ([`super::verify`]). + +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; + +use serde_json::Value; + +use crate::manifest::schema::PatchRecord; +use crate::patch::apply::{normalize_file_path, ApplyResult, PatchSources}; +use crate::patch::copy_tree::{fresh_copy, remove_tree}; +use crate::utils::fs::atomic_write_bytes; + +use super::common::{already_patched_result, refused, service_offline_conflict}; +use super::npm_common::{ + declares_bundled_deps, done_failure, done_failure_unstage, guard_coordinates, +}; +use super::service_fetch::{fetch_verified_archive, ServiceArtifact}; +use super::state::VENDOR_MARKER_FILE; +use super::vlt_lock_text::vendored_dir_rel; +use super::{VendorOutcome, VendorServiceConfig, VendorWarning}; + +/// `/.gitignore`, exactly. +pub(crate) const UUID_GITIGNORE: &str = + "!*\n**/node_modules/*/node_modules/\n**/node_modules/@*/*/node_modules/\n"; +/// `/.gitattributes`, exactly. +pub(crate) const UUID_GITATTRIBUTES: &str = "* -text\n"; + +const GITIGNORE: &str = ".gitignore"; +const GITATTRIBUTES: &str = ".gitattributes"; +const NODE_MODULES: &str = "node_modules"; + +/// The staged directory artifact the vlt wiring consumes. +pub(super) struct NpmStagedDir { + /// `.socket/vendor/npm///node_modules/`. + pub rel_dir: String, + /// Every regular file under `rel_dir` but its `node_modules/`. + pub inventory: BTreeMap, + /// The uuid dir existed before this run wrote into it. + pub uuid_dir_preexisted: bool, + /// The patched manifest, when the patch rewrote `package.json`. + pub staged_pkg_json: Option, + /// The committed dir passed the reuse check; nothing was written. + pub reused: bool, +} + +// ── package.json spans ─────────────────────────────────────────────────── + +/// One member of a JSON object: its decoded key and byte offsets. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct JsonMember { + pub(crate) key: String, + pub(crate) key_start: usize, + pub(crate) value_start: usize, + pub(crate) value_end: usize, + /// The comma right after the value, when one follows. + pub(crate) comma: Option, +} + +fn skip_ws(b: &[u8], mut i: usize) -> usize { + while i < b.len() && matches!(b[i], b' ' | b'\t' | b'\n' | b'\r') { + i += 1; + } + i +} + +/// The index just past the string token starting at `i`. +fn scan_string(b: &[u8], i: usize) -> Option { + if b.get(i) != Some(&b'"') { + return None; + } + let mut j = i + 1; + while j < b.len() { + match b[j] { + b'\\' => j += 2, + b'"' => return Some(j + 1), + _ => j += 1, + } + } + None +} + +/// The index just past the value starting at `i`. +fn scan_value(b: &[u8], i: usize) -> Option { + match b.get(i)? { + b'"' => scan_string(b, i), + b'{' | b'[' => { + let mut depth = 0usize; + let mut j = i; + while j < b.len() { + match b[j] { + b'"' => { + j = scan_string(b, j)?; + continue; + } + b'{' | b'[' => depth += 1, + b'}' | b']' => { + depth -= 1; + if depth == 0 { + return Some(j + 1); + } + } + _ => {} + } + j += 1; + } + None + } + _ => { + let mut j = i; + while j < b.len() && !matches!(b[j], b',' | b'}' | b']' | b' ' | b'\t' | b'\n' | b'\r') + { + j += 1; + } + (j > i).then_some(j) + } + } +} + +/// The members of the object whose `{` is at `open`. +fn object_members(text: &str, open: usize) -> Option> { + let b = text.as_bytes(); + if b.get(open) != Some(&b'{') { + return None; + } + let mut members = Vec::new(); + let mut i = skip_ws(b, open + 1); + if b.get(i) == Some(&b'}') { + return Some(members); + } + loop { + let key_start = i; + let key_end = scan_string(b, key_start)?; + let key: String = serde_json::from_str(&text[key_start..key_end]).ok()?; + i = skip_ws(b, key_end); + if b.get(i) != Some(&b':') { + return None; + } + let value_start = skip_ws(b, i + 1); + let value_end = scan_value(b, value_start)?; + i = skip_ws(b, value_end); + let comma = (b.get(i) == Some(&b',')).then_some(i); + members.push(JsonMember { + key, + key_start, + value_start, + value_end, + comma, + }); + match b.get(i)? { + b',' => i = skip_ws(b, i + 1), + b'}' => return Some(members), + _ => return None, + } + } +} + +/// The top-level members of a JSON object document (a leading BOM is kept +/// out of the offsets' way, never stripped from the text). +pub(crate) fn root_members(text: &str) -> Option> { + let body = text.strip_prefix('\u{feff}').unwrap_or(text); + serde_json::from_str::>(body).ok()?; + let open = skip_ws(text.as_bytes(), text.len() - body.len()); + object_members(text, open) +} + +/// The one member named `key`; `Err` when it appears more than once. +fn unique_member<'m>(members: &'m [JsonMember], key: &str) -> Result, ()> { + let mut found = members.iter().filter(|m| m.key == key); + let first = found.next(); + if found.next().is_some() { + return Err(()); + } + Ok(first) +} + +/// Why a package.json span edit could not be made. +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum SpanError { + NotJson, + Duplicate(String), + Missing, + NotString, +} + +/// DESIGN §4.4: `package.json` with its top-level `devDependencies` member +/// cut out as a byte span (key, colon, value and the whitespace between +/// them, plus the following comma and the run up to the next token, else +/// the preceding comma). `Ok(None)` when the member is absent. +pub(crate) fn strip_dev_dependencies(text: &str) -> Result, SpanError> { + let members = root_members(text).ok_or(SpanError::NotJson)?; + let member = match unique_member(&members, "devDependencies") { + Ok(Some(member)) => member, + Ok(None) => return Ok(None), + Err(()) => return Err(SpanError::Duplicate("devDependencies".into())), + }; + let index = members.iter().position(|m| m == member).unwrap_or_default(); + let (start, end) = match member.comma { + Some(comma) => ( + member.key_start, + skip_ws(text.as_bytes(), comma + 1).min( + members + .get(index + 1) + .map_or(text.len(), |next| next.key_start), + ), + ), + None => match index.checked_sub(1).and_then(|i| members[i].comma) { + Some(prev_comma) => (prev_comma, member.value_end), + None => (member.key_start, member.value_end), + }, + }; + Ok(Some(format!("{}{}", &text[..start], &text[end..]))) +} + +/// The raw string token at `[field][name]` of a package.json and its byte +/// span. +fn dependency_span(text: &str, field: &str, name: &str) -> Result<(usize, usize), SpanError> { + let members = root_members(text).ok_or(SpanError::NotJson)?; + let table = unique_member(&members, field) + .map_err(|()| SpanError::Duplicate(field.into()))? + .ok_or(SpanError::Missing)?; + let inner = object_members(text, table.value_start).ok_or(SpanError::Missing)?; + let dep = unique_member(&inner, name) + .map_err(|()| SpanError::Duplicate(format!("{field}.{name}")))? + .ok_or(SpanError::Missing)?; + if text.as_bytes()[dep.value_start] != b'"' { + return Err(SpanError::NotString); + } + Ok((dep.value_start, dep.value_end)) +} + +/// The raw JSON token of `[field][name]` (quotes included). +pub(crate) fn dependency_token(text: &str, field: &str, name: &str) -> Result { + let (start, end) = dependency_span(text, field, name)?; + Ok(text[start..end].to_string()) +} + +/// `text` with the string token at `[field][name]` replaced by `raw` (a JSON +/// string token), nothing else re-serialized. +pub(crate) fn replace_dependency_token( + text: &str, + field: &str, + name: &str, + raw: &str, +) -> Result { + let (start, end) = dependency_span(text, field, name)?; + Ok(format!("{}{raw}{}", &text[..start], &text[end..])) +} + +/// The §4.4 transform on a staged tree's `package.json`. A refusal is the +/// ready outcome. +async fn apply_transforms( + stage: &Path, + name: &str, + version: &str, +) -> Result<(), Box> { + let path = stage.join("package.json"); + let Ok(text) = crate::utils::fs::read_regular_to_string(&path).await else { + return Ok(()); + }; + match strip_dev_dependencies(&text) { + Ok(None) => Ok(()), + Ok(Some(stripped)) => tokio::fs::write(&path, stripped).await.map_err(|e| { + Box::new(done_failure( + &format!("pkg:npm/{name}@{version}"), + format!("cannot write the staged package.json: {e}"), + )) + }), + Err(SpanError::Duplicate(_)) => Err(Box::new(refused( + "vendor_lock_entry_unsupported", + format!("{name}@{version}'s package.json declares duplicate devDependencies"), + ))), + Err(_) => Err(Box::new(refused( + "vendor_lock_entry_unsupported", + format!("{name}@{version}'s package.json is not a JSON object"), + ))), + } +} + +// ── tree checks ────────────────────────────────────────────────────────── + +/// DESIGN §4.2 structure rule: `//node_modules/` holds exactly +/// the package dir (for a scoped name, exactly `@scope/` holding exactly +/// ``). `rel_abs` is the package dir. +pub(crate) async fn structure_rule_holds(rel_abs: &Path, name: &str) -> bool { + let levels: Vec<&str> = name.split('/').collect(); + let mut dir = rel_abs.to_path_buf(); + for _ in &levels { + dir.pop(); + } + for level in levels { + let entries = crate::utils::fs::list_dir_entries(&dir).await; + if entries.len() != 1 || entries[0].file_name().to_str() != Some(level) { + return false; + } + match tokio::fs::symlink_metadata(entries[0].path()).await { + Ok(meta) if meta.is_dir() => {} + _ => return false, + } + dir.push(level); + } + true +} + +/// vlt's `node_modules/` inside the package dir holds only links, dirs and +/// `.bin/` scripts; anything else there was planted. +pub(crate) async fn node_modules_holds_only_links(rel_abs: &Path) -> bool { + let root = rel_abs.join(NODE_MODULES); + tokio::task::spawn_blocking(move || { + let Ok(meta) = std::fs::symlink_metadata(&root) else { + return true; + }; + if !meta.is_dir() { + return false; + } + let mut stack = vec![(root.clone(), false)]; + while let Some((dir, in_bin)) = stack.pop() { + let Ok(entries) = std::fs::read_dir(&dir) else { + return false; + }; + for entry in entries { + let Ok(entry) = entry else { + return false; + }; + let Ok(meta) = std::fs::symlink_metadata(entry.path()) else { + return false; + }; + let ty = meta.file_type(); + if ty.is_symlink() { + continue; + } + if ty.is_dir() { + let bin = in_bin || (dir == root && entry.file_name() == ".bin"); + stack.push((entry.path(), bin)); + continue; + } + if !(ty.is_file() && in_bin) { + return false; + } + } + } + true + }) + .await + .unwrap_or(false) +} + +/// Rewrite `/.gitignore` and `/.gitattributes` when absent or +/// different; neither is part of the artifact. +pub(crate) async fn restore_uuid_metadata(uuid_dir: &Path) -> std::io::Result<()> { + for (name, want) in [ + (GITIGNORE, UUID_GITIGNORE), + (GITATTRIBUTES, UUID_GITATTRIBUTES), + ] { + let path = uuid_dir.join(name); + let current = crate::utils::fs::read_regular_to_bytes(&path).await.ok(); + if current.as_deref() != Some(want.as_bytes()) { + atomic_write_bytes(&path, want.as_bytes()).await?; + } + } + Ok(()) +} + +// ── gitignore probe ────────────────────────────────────────────────────── + +async fn git_output( + git: &Path, + root: &Path, + args: &[&str], + stdin: Option, +) -> Option<(i32, String)> { + use tokio::io::AsyncWriteExt as _; + let mut child = tokio::process::Command::new(git) + .arg("-C") + .arg(root) + .args(args) + .stdin(if stdin.is_some() { + std::process::Stdio::piped() + } else { + std::process::Stdio::null() + }) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::null()) + .kill_on_drop(true) + .spawn() + .ok()?; + if let (Some(input), Some(mut pipe)) = (stdin, child.stdin.take()) { + pipe.write_all(input.as_bytes()).await.ok()?; + } + let output = tokio::time::timeout(std::time::Duration::from_secs(30), child.wait_with_output()) + .await + .ok()? + .ok()?; + Some(( + output.status.code()?, + String::from_utf8_lossy(&output.stdout).into_owned(), + )) +} + +/// DESIGN §4.2 probe: `git check-ignore -v --no-index` over `paths` +/// (project-relative). `Some(rules)` names what ignores them; `None` when +/// nothing is ignored, git is absent or the root is not a work tree. +pub(crate) async fn gitignored(project_root: &Path, paths: &[String]) -> Option { + let git = crate::utils::process::resolve_tool("git")?; + let (_, inside) = git_output( + &git, + project_root, + &["rev-parse", "--is-inside-work-tree"], + None, + ) + .await?; + if inside.trim() != "true" { + return None; + } + let input: String = paths.iter().map(|p| format!("{p}\n")).collect(); + let (code, out) = git_output( + &git, + project_root, + &["check-ignore", "-v", "--no-index", "--stdin"], + Some(input), + ) + .await?; + let lines: Vec<&str> = out + .lines() + .filter(|line| { + let rule = line.split('\t').next().unwrap_or_default(); + let pattern = rule.splitn(3, ':').nth(2).unwrap_or_default(); + !pattern.is_empty() && !pattern.starts_with('!') + }) + .collect(); + (code == 0 && !lines.is_empty()).then(|| { + let shown: Vec<&str> = lines.iter().take(3).copied().collect(); + let more = lines.len().saturating_sub(shown.len()); + let mut detail = shown.join("; "); + if more > 0 { + detail.push_str(&format!("; and {more} more")); + } + detail + }) +} + +fn gitignored_refusal(rel_dir: &str, rules: &str) -> VendorOutcome { + refused( + "vendor_artifact_gitignored", + format!( + "git would not commit the vendored artifact at {rel_dir} ({rules}); remove the \ + rule that ignores .socket/ (or .socket/vendor/) and vendor again" + ), + ) +} + +// ── pipeline ───────────────────────────────────────────────────────────── + +/// DESIGN §4.3: reuse the committed dir, else build it from the patch +/// service or the installed copy, then write it into place. Same result +/// shape as [`super::npm_common::stage_patch_pack`]: `Err` is a refusal or +/// a failure with the project untouched, `Ok((None, _))` a failed patch or +/// a dry run, `Ok((Some(dir), _))` the artifact on disk. +#[allow(clippy::too_many_arguments)] +pub(super) async fn stage_patch_dir( + purl: &str, + installed_dir: &Path, + project_root: &Path, + record: &PatchRecord, + sources: &PatchSources<'_>, + dry_run: bool, + force: bool, + warnings: &mut Vec, + service: Option<&VendorServiceConfig>, +) -> Result<(Option, ApplyResult), Box> { + let coords = guard_coordinates(purl, record)?; + let rel_dir = vendored_dir_rel(&record.uuid, &coords.name, &coords.version); + let uuid_dir = project_root.join(&coords.uuid_dir_rel); + let rel_abs = project_root.join(&rel_dir); + let touches_manifest = record + .files + .keys() + .any(|k| normalize_file_path(k) == "package.json"); + + let mut reusable = false; + match super::reuse::reusable_committed_dir(project_root, record, &rel_dir).await { + Ok(inventory) => { + if !dry_run { + if let Err(e) = restore_uuid_metadata(&uuid_dir).await { + return Err(Box::new(done_failure( + purl, + format!("cannot restore {}/.gitignore: {e}", coords.uuid_dir_rel), + ))); + } + let staged_pkg_json = if touches_manifest { + read_manifest(&rel_abs).await.ok() + } else { + None + }; + let result = already_patched_result(purl, &rel_abs, &record.files); + return Ok(( + Some(NpmStagedDir { + rel_dir, + inventory, + uuid_dir_preexisted: true, + staged_pkg_json, + reused: true, + }), + result, + )); + } + reusable = true; + } + Err(miss) => super::reuse::log_miss(purl, &miss), + } + + if let Some(refusal) = service_offline_conflict(service).filter(|_| !reusable) { + return Err(Box::new(refusal)); + } + + let stage_tmp = tempfile::tempdir().map_err(|e| { + Box::new(done_failure( + purl, + format!("cannot create staging tempdir: {e}"), + )) + })?; + let stage = stage_tmp.path().join("stage"); + let mut result = None; + if let Some(cfg) = service.filter(|cfg| cfg.service_enabled() && !dry_run) { + match try_service_dir( + purl, + record, + cfg, + &stage, + &coords.name, + &coords.version, + warnings, + ) + .await + { + ServiceDir::Used => { + result = Some(already_patched_result(purl, &rel_abs, &record.files)); + } + ServiceDir::HardFail(outcome) => return Err(outcome), + ServiceDir::FallBack => {} + } + } + let result = match result { + Some(result) => { + apply_transforms(&stage, &coords.name, &coords.version).await?; + result + } + None => { + if let Err(e) = fresh_copy(installed_dir, &stage, None).await { + return Err(Box::new(done_failure( + purl, + format!("cannot stage a copy of the installed package: {e}"), + ))); + } + if let Err(e) = remove_tree(&stage.join(NODE_MODULES)).await { + return Err(Box::new(done_failure( + purl, + format!("cannot prune staged node_modules: {e}"), + ))); + } + if let Ok(pkg) = read_manifest(&stage).await { + if declares_bundled_deps(&pkg) { + return Err(Box::new(refused( + "vendor_bundled_deps_unsupported", + format!( + "{}@{} declares bundleDependencies; vendoring would drop its \ + bundled node_modules and break installs", + coords.name, coords.version + ), + ))); + } + } + let result = super::force_apply_staged( + purl, + &stage, + record, + sources, + dry_run, + force, + &coords.name, + &coords.version, + warnings, + ) + .await; + if !result.success { + return Ok((None, result)); + } + apply_transforms(&stage, &coords.name, &coords.version).await?; + result + } + }; + if dry_run { + return Ok((None, result)); + } + + let uuid_dir_preexisted = tokio::fs::metadata(&uuid_dir).await.is_ok(); + let unstage = |error: String| { + done_failure_unstage( + purl, + error, + project_root, + &coords.uuid_dir_rel, + uuid_dir_preexisted, + ) + }; + if let Err(e) = write_into_place(&stage, &uuid_dir, &rel_abs).await { + return Err(Box::new( + unstage(format!("cannot write {rel_dir}: {e}")).await, + )); + } + if let Err(e) = restore_uuid_metadata(&uuid_dir).await { + return Err(Box::new( + unstage(format!( + "cannot write {}/.gitignore: {e}", + coords.uuid_dir_rel + )) + .await, + )); + } + let inventory = match super::verify::compute_package_dir_inventory(&rel_abs).await { + Ok(inventory) => inventory, + Err(e) => { + return Err(Box::new( + unstage(format!("cannot inventory {rel_dir}: {e}")).await, + )) + } + }; + let mut probe: Vec = inventory.keys().map(|k| format!("{rel_dir}/{k}")).collect(); + for name in [VENDOR_MARKER_FILE, GITIGNORE, GITATTRIBUTES] { + probe.push(format!("{}/{name}", coords.uuid_dir_rel)); + } + if let Some(rules) = gitignored(project_root, &probe).await { + let _ = unstage(String::new()).await; + return Err(Box::new(gitignored_refusal(&rel_dir, &rules))); + } + let staged_pkg_json = if touches_manifest { + match read_manifest(&rel_abs).await { + Ok(pkg) => Some(pkg), + Err(e) => return Err(Box::new(unstage(e).await)), + } + } else { + None + }; + Ok(( + Some(NpmStagedDir { + rel_dir, + inventory, + uuid_dir_preexisted, + staged_pkg_json, + reused: false, + }), + result, + )) +} + +async fn read_manifest(dir: &Path) -> Result { + let text = crate::utils::fs::read_regular_to_string(&dir.join("package.json")) + .await + .map_err(|e| format!("package.json unreadable: {e}"))?; + serde_json::from_str(crate::package_json::detect::strip_bom(&text)) + .map_err(|e| format!("package.json is not parseable JSON: {e}")) +} + +/// Copy the stage to `/.tmp-*`, then rename it over `rel_abs`. +async fn write_into_place(stage: &Path, uuid_dir: &Path, rel_abs: &Path) -> std::io::Result<()> { + tokio::fs::create_dir_all(uuid_dir).await?; + let parent: PathBuf = rel_abs + .parent() + .map(Path::to_path_buf) + .ok_or_else(|| std::io::Error::other("artifact path has no parent"))?; + tokio::fs::create_dir_all(&parent).await?; + let tmp = tempfile::Builder::new() + .prefix(".tmp-") + .tempdir_in(uuid_dir)? + .keep(); + if let Err(e) = fresh_copy(stage, &tmp, None).await { + let _ = remove_tree(&tmp).await; + return Err(e); + } + if tokio::fs::symlink_metadata(rel_abs).await.is_ok() { + remove_tree(rel_abs).await?; + } + if let Err(e) = tokio::fs::rename(&tmp, rel_abs).await { + let _ = remove_tree(&tmp).await; + return Err(e); + } + Ok(()) +} + +/// Every record file of the extracted tree hashes to its afterHash (the +/// archive check, run on the tree the first-component strip produced). +async fn tree_matches_after_hashes(stage: &Path, record: &PatchRecord) -> bool { + for (file_name, info) in &record.files { + let rel = normalize_file_path(file_name); + if !crate::patch::path_safety::is_safe_multi_segment(rel) { + return false; + } + let Ok(bytes) = crate::utils::fs::read_regular_to_bytes(&stage.join(rel)).await else { + return false; + }; + if !crate::hash::git_sha256::compute_git_sha256_from_bytes(&bytes) + .eq_ignore_ascii_case(&info.after_hash) + { + return false; + } + } + true +} + +enum ServiceDir { + Used, + HardFail(Box), + FallBack, +} + +/// The service fast path: the prebuilt tarball, integrity- and +/// afterHash-verified, extracted into `stage` with its first path component +/// stripped whatever it is called. The fallback policy is the tarball +/// backends' (`try_service_pack`). +async fn try_service_dir( + purl: &str, + record: &PatchRecord, + cfg: &VendorServiceConfig, + stage: &Path, + name: &str, + version: &str, + warnings: &mut Vec, +) -> ServiceDir { + let hard_fail = |detail: String| ServiceDir::HardFail(Box::new(done_failure(purl, detail))); + let fallback_or_fail = + |reason: String, code: &'static str, warnings: &mut Vec| { + if cfg.source.requires_service() { + hard_fail(reason) + } else { + warnings.push(VendorWarning::new( + code, + format!("{reason}; building locally instead"), + )); + ServiceDir::FallBack + } + }; + match fetch_verified_archive(cfg, &record.uuid).await { + ServiceArtifact::Ready(archive) => { + let (bytes, dest) = (archive.bytes, stage.to_path_buf()); + let extracted = tokio::task::spawn_blocking(move || { + super::registry_fetch::extract_tgz_strict(&bytes, &dest) + }) + .await + .map_err(|e| e.to_string()) + .and_then(|r| r); + if let Err(e) = extracted { + return hard_fail(format!( + "prebuilt tarball for {name}@{version} is unsafe: {e}" + )); + } + if !tree_matches_after_hashes(stage, record).await { + let _ = remove_tree(stage).await; + return fallback_or_fail( + format!( + "prebuilt tarball for {name}@{version} does not carry the patched files \ + at their recorded paths" + ), + "vendor_prebuilt_layout_mismatch", + warnings, + ); + } + warnings.push(VendorWarning::new( + "vendor_prebuilt_downloaded", + format!( + "vendored {name}@{version} from the patch service ({})", + archive.source_url + ), + )); + ServiceDir::Used + } + ServiceArtifact::IntegrityMismatch(reason) => hard_fail(format!( + "prebuilt artifact failed integrity verification ({reason}); refusing to fall back \ + to a local build on tampered bytes" + )), + ServiceArtifact::Pending => fallback_or_fail( + "prebuilt artifact is still building".to_string(), + "vendor_prebuilt_pending", + warnings, + ), + ServiceArtifact::Unavailable(reason) => { + if cfg.source.requires_service() { + hard_fail(format!("prebuilt artifact unavailable: {reason}")) + } else { + ServiceDir::FallBack + } + } + ServiceArtifact::Failed(reason) => fallback_or_fail( + format!("patch service request failed ({reason})"), + "vendor_prebuilt_unavailable", + warnings, + ), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn dev_dependencies_are_cut_out_as_one_span() { + let cases = [ + ( + "{\n \"name\": \"x\",\n \"devDependencies\": {\n \"a\": \"1\"\n },\n \"main\": \"i.js\"\n}\n", + Some("{\n \"name\": \"x\",\n \"main\": \"i.js\"\n}\n"), + ), + ( + "{\n \"name\": \"x\",\n \"devDependencies\": {}\n}\n", + Some("{\n \"name\": \"x\"\n}\n"), + ), + ("{\"devDependencies\": {\"a\": \"1\"}}", Some("{}")), + ( + "\u{feff}{\"devDependencies\":{},\"main\":\"i\"}", + Some("\u{feff}{\"main\":\"i\"}"), + ), + ( + "{\"name\":\"x\",\"scripts\":{\"devDependencies\":\"y\"}}", + None, + ), + ("{\"name\":\"x\"}", None), + ]; + for (input, want) in cases { + assert_eq!( + strip_dev_dependencies(input).unwrap().as_deref(), + want, + "{input}" + ); + } + assert_eq!( + strip_dev_dependencies("{\"devDependencies\":{},\"devDependencies\":{}}"), + Err(SpanError::Duplicate("devDependencies".into())) + ); + assert_eq!(strip_dev_dependencies("[1]"), Err(SpanError::NotJson)); + } + + #[test] + fn dependency_tokens_are_replaced_in_place() { + let text = "{\n \"dependencies\": {\n \"a\": \"1\",\n \"@s/b\" : \"^2\"\n },\n \"x\": [\"dependencies\"]\n}\n"; + assert_eq!( + dependency_token(text, "dependencies", "@s/b").unwrap(), + "\"^2\"" + ); + assert_eq!( + replace_dependency_token(text, "dependencies", "@s/b", "\"file:./v\"").unwrap(), + text.replace("\"^2\"", "\"file:./v\"") + ); + assert_eq!( + dependency_token(text, "devDependencies", "a"), + Err(SpanError::Missing) + ); + assert_eq!( + dependency_token("{\"dependencies\":{\"a\":1}}", "dependencies", "a"), + Err(SpanError::NotString) + ); + assert_eq!( + dependency_token( + "{\"dependencies\":{\"a\":\"1\",\"a\":\"2\"}}", + "dependencies", + "a" + ), + Err(SpanError::Duplicate("dependencies.a".into())) + ); + } + + #[tokio::test] + async fn the_structure_rule_admits_exactly_the_package_dir() { + let tmp = tempfile::tempdir().unwrap(); + let leaf = tmp.path().join("@s/b-1.0.0/node_modules"); + let pkg = leaf.join("@s/b"); + tokio::fs::create_dir_all(&pkg).await.unwrap(); + assert!(structure_rule_holds(&pkg, "@s/b").await); + tokio::fs::create_dir_all(leaf.join("@s/c")).await.unwrap(); + assert!(!structure_rule_holds(&pkg, "@s/b").await); + + let pkg = tmp.path().join("a-1.0.0/node_modules/a"); + tokio::fs::create_dir_all(&pkg).await.unwrap(); + assert!(structure_rule_holds(&pkg, "a").await); + tokio::fs::write(tmp.path().join("a-1.0.0/node_modules/.x"), b"") + .await + .unwrap(); + assert!(!structure_rule_holds(&pkg, "a").await); + } + + #[cfg(unix)] + #[tokio::test] + async fn only_links_dirs_and_bin_scripts_live_under_the_package_node_modules() { + let tmp = tempfile::tempdir().unwrap(); + let pkg = tmp.path(); + assert!(node_modules_holds_only_links(pkg).await, "absent is fine"); + let nm = pkg.join("node_modules"); + tokio::fs::create_dir_all(nm.join(".bin")).await.unwrap(); + tokio::fs::create_dir_all(nm.join("@s")).await.unwrap(); + std::os::unix::fs::symlink("/elsewhere", nm.join("dep")).unwrap(); + std::os::unix::fs::symlink("/elsewhere", nm.join("@s/dep")).unwrap(); + tokio::fs::write(nm.join(".bin/tool"), b"#!/bin/sh\n") + .await + .unwrap(); + assert!(node_modules_holds_only_links(pkg).await); + tokio::fs::write(nm.join("@s/planted.js"), b"x") + .await + .unwrap(); + assert!(!node_modules_holds_only_links(pkg).await); + } + + #[tokio::test] + async fn uuid_metadata_is_written_with_its_exact_bytes() { + let tmp = tempfile::tempdir().unwrap(); + tokio::fs::write(tmp.path().join(GITIGNORE), b"*\n") + .await + .unwrap(); + restore_uuid_metadata(tmp.path()).await.unwrap(); + assert_eq!( + std::fs::read_to_string(tmp.path().join(GITIGNORE)).unwrap(), + "!*\n**/node_modules/*/node_modules/\n**/node_modules/@*/*/node_modules/\n" + ); + assert_eq!( + std::fs::read_to_string(tmp.path().join(GITATTRIBUTES)).unwrap(), + "* -text\n" + ); + } + + fn tgz(entries: &[(&str, tar::EntryType, &[u8])]) -> Vec { + let mut builder = tar::Builder::new(flate2::write::GzEncoder::new( + Vec::new(), + flate2::Compression::default(), + )); + for (path, kind, bytes) in entries { + let mut header = tar::Header::new_gnu(); + header.set_entry_type(*kind); + header.set_size(bytes.len() as u64); + header.set_mode(0o644); + if !kind.is_file() && !kind.is_dir() { + header.set_link_name("target").unwrap(); + } + header.set_cksum(); + builder.append_data(&mut header, path, *bytes).unwrap(); + } + builder.into_inner().unwrap().finish().unwrap() + } + + #[test] + fn the_service_extract_strips_any_first_component_and_refuses_links() { + use tar::EntryType; + let tmp = tempfile::tempdir().unwrap(); + let dest = tmp.path().join("stage"); + let ok = tgz(&[ + ("left-pad/", EntryType::Directory, b""), + ("left-pad/index.js", EntryType::Regular, b"x"), + ]); + super::super::registry_fetch::extract_tgz_strict(&ok, &dest).unwrap(); + assert_eq!(std::fs::read(dest.join("index.js")).unwrap(), b"x"); + for kind in [ + EntryType::Symlink, + EntryType::Link, + EntryType::Char, + EntryType::Fifo, + ] { + let bad = tgz(&[ + ("package/index.js", EntryType::Regular, b"x"), + ("package/evil", kind, b""), + ]); + let err = + super::super::registry_fetch::extract_tgz_strict(&bad, &tmp.path().join("s2")) + .unwrap_err(); + assert!(err.contains("not a regular file"), "{kind:?}: {err}"); + } + } + + #[cfg(unix)] + #[tokio::test] + async fn the_gitignore_probe_ignores_tracked_state_and_names_the_rule() { + let Some(git) = crate::utils::process::resolve_tool("git") else { + return; + }; + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let status = std::process::Command::new(&git) + .args(["init", "-q"]) + .current_dir(root) + .status() + .unwrap(); + assert!(status.success()); + let paths = vec![".socket/vendor/npm/u/a-1.0.0/node_modules/a/dist/i.js".to_string()]; + assert_eq!(gitignored(root, &paths).await, None); + std::fs::write(root.join(".gitignore"), "dist/\n").unwrap(); + let uuid = root.join(".socket/vendor/npm/u"); + std::fs::create_dir_all(&uuid).unwrap(); + assert!( + gitignored(root, &paths).await.is_some(), + "a root dist/ rule" + ); + restore_uuid_metadata(&uuid).await.unwrap(); + assert_eq!( + gitignored(root, &paths).await, + None, + "the uuid .gitignore re-includes it" + ); + std::fs::write(root.join(".gitignore"), ".socket/\n").unwrap(); + let rules = gitignored(root, &paths).await.unwrap(); + assert!(rules.contains(".gitignore:1:.socket/"), "{rules}"); + assert!(gitignored(&tmp.path().join("missing"), &paths) + .await + .is_none()); + } +} diff --git a/crates/socket-patch-core/src/vendor/npm_flavor.rs b/crates/socket-patch-core/src/vendor/npm_flavor.rs index 1bb6a6b3..04ddb38d 100644 --- a/crates/socket-patch-core/src/vendor/npm_flavor.rs +++ b/crates/socket-patch-core/src/vendor/npm_flavor.rs @@ -11,11 +11,11 @@ //! package dirs) because the file: rewiring is unvalidated under that //! linker — each with its own code and remedy. //! -//! The router fans `vendor`/`revert` out per detected flavor. All five -//! flavors have real backends: package-lock ([`super::npm_lock`]), -//! yarn classic ([`super::yarn_classic_lock`]), yarn berry -//! ([`super::yarn_berry_lock`]), pnpm ([`super::pnpm_lock`]), and bun -//! ([`super::bun_lock`]); a lockfile the probe can't classify refuses with +//! The router fans `vendor`/`revert` out per detected flavor. Every flavor +//! has a real backend: package-lock ([`super::npm_lock`]), yarn classic +//! ([`super::yarn_classic_lock`]), yarn berry ([`super::yarn_berry_lock`]), +//! pnpm ([`super::pnpm_lock`]), bun ([`super::bun_lock`]) and vlt +//! ([`super::vlt_lock`]); a lockfile the probe can't classify refuses with //! a stable code. Reverts fail CLOSED on a flavor this build has no //! backend for — never guess at another flavor's wiring records. @@ -28,7 +28,7 @@ use crate::utils::fs::{read_regular_to_bytes, read_regular_to_string}; use super::pnpm_lock_legacy::PnpmLockGrammar; use super::state::VendorEntry; use super::{ - bun_lock, npm_lock, pnpm_lock, pnpm_lock_legacy, yarn_berry_lock, yarn_classic_lock, + bun_lock, npm_lock, pnpm_lock, pnpm_lock_legacy, vlt_lock, yarn_berry_lock, yarn_classic_lock, RevertOpts, RevertOutcome, VendorOutcome, VendorWarning, }; @@ -48,6 +48,8 @@ pub(crate) enum NpmLockFlavor { PnpmLegacy, /// `bun.lock` or native binary `bun.lockb`. Bun, + /// `vlt-lock.json`, lockfileVersion 0 or 1. + Vlt, } impl NpmLockFlavor { @@ -60,6 +62,7 @@ impl NpmLockFlavor { NpmLockFlavor::Pnpm => "pnpm", NpmLockFlavor::PnpmLegacy => pnpm_lock_legacy::FLAVOR, NpmLockFlavor::Bun => "bun", + NpmLockFlavor::Vlt => vlt_lock::FLAVOR, } } @@ -75,6 +78,7 @@ impl NpmLockFlavor { Some("pnpm") => Some(NpmLockFlavor::Pnpm), Some(pnpm_lock_legacy::FLAVOR) => Some(NpmLockFlavor::PnpmLegacy), Some("bun") => Some(NpmLockFlavor::Bun), + Some(vlt_lock::FLAVOR) => Some(NpmLockFlavor::Vlt), Some(_) => None, } } @@ -82,7 +86,9 @@ impl NpmLockFlavor { /// Yarn berry Plug'n'Play loaders: packages live inside `.yarn/cache/` zips, /// so there is nothing on disk to stage and no lockfile entry to rewire. -use crate::constants::npm_family::{BUN_LOCK, BUN_LOCKB, NPM_LOCKS, PNPM_LOCK, PNP_MARKERS}; +use crate::constants::npm_family::{ + BUN_LOCK, BUN_LOCKB, NPM_LOCKS, PNPM_LOCK, PNP_MARKERS, VLT_LOCK, +}; /// How many head lines the yarn content sniff reads (the v1 header sits in /// the leading comment block; berry's `__metadata:` is the first top-level @@ -92,7 +98,8 @@ const YARN_SNIFF_HEAD_LINES: usize = 30; /// Every lockfile name the probe knows, grouped into wiring families: the /// flavor that owns a family wires (or supersedes) every file in it, so only /// files OUTSIDE the detected family get the multiple-lockfiles warning. -const LOCKFILE_FAMILIES: [(NpmLockFlavor, &[&str]); 4] = [ +const LOCKFILE_FAMILIES: [(NpmLockFlavor, &[&str]); 5] = [ + (NpmLockFlavor::Vlt, &[VLT_LOCK]), // npm itself ignores package-lock.json when npm-shrinkwrap.json exists, // so the npm family never warns about its own sibling. (NpmLockFlavor::PackageLock, &NPM_LOCKS), @@ -122,19 +129,23 @@ pub(super) fn project_root_location(project_root: &Path) -> String { /// pnpm store + no yarn.lock, see /// [`crate::crawlers::pkg_managers::pnpm_pnp_layout`]) → Err /// `vendor_pnpm_pnp_unsupported` with a pnpm remedy; -/// 2. `bun.lock` or `bun.lockb` → Bun (text takes precedence); -/// 3. `pnpm-lock.yaml` → head-sniff `lockfileVersion`: `'9.0'` → Pnpm; +/// 2. `vlt-lock.json` → Vlt when it is a BOM-less JSON object with +/// `lockfileVersion` 0 or 1; any other shape → Err +/// `vendor_lockfile_version_unsupported` (the layout is not checked here, +/// so read-only consumers still read a pretty-printed lock); +/// 3. `bun.lock` or `bun.lockb` → Bun (text takes precedence); +/// 4. `pnpm-lock.yaml` → head-sniff `lockfileVersion`: `'9.0'` → Pnpm; /// `5.4`/`'6.0'` (pnpm 7/8) → PnpmLegacy; anything else → Err /// `vendor_lockfile_version_unsupported` (version-aware remedy); -/// 4. `yarn.lock` → head-sniff: column-0 `__metadata:` → Err +/// 5. `yarn.lock` → head-sniff: column-0 `__metadata:` → Err /// `vendor_yarn_berry_unsupported`; `# yarn lockfile v1` → YarnClassic; /// neither → Err `vendor_lockfile_version_unsupported`; -/// 5. `npm-shrinkwrap.json` | `package-lock.json` → PackageLock; -/// 6. nothing recognized, but `rush.json` present → Err +/// 6. `npm-shrinkwrap.json` | `package-lock.json` → PackageLock; +/// 7. nothing recognized, but `rush.json` present → Err /// `vendor_rush_unsupported` (Rush's generated-workspace install model /// can't carry vendor's relative `file:` specs — hosted mode edits the /// lock in place instead); -/// 7. nothing → Err `vendor_lockfile_missing`. +/// 8. nothing → Err `vendor_lockfile_missing`. /// /// `Ok` carries one `vendor_multiple_lockfiles` warning per OTHER known /// lockfile present (outside the detected flavor's family): installs driven @@ -182,13 +193,22 @@ pub(crate) async fn detect_npm_lock_flavor( } let detected = 'flavor: { - // 2. Bun's native backend accepts text and binary locks. Selection + // 2. vlt wins every other lock once PnP is ruled out. + if exists(VLT_LOCK).await { + let text = read_lock(project_root, VLT_LOCK).await?; + match vlt_lock::sniff_vendor_lock(&text) { + Ok(_) => break 'flavor NpmLockFlavor::Vlt, + Err(detail) => return Err(("vendor_lockfile_version_unsupported", detail)), + } + } + + // 3. Bun's native backend accepts text and binary locks. Selection // inside the backend and inventory preserves bun.lock precedence. if exists(BUN_LOCK).await || exists(BUN_LOCKB).await { break 'flavor NpmLockFlavor::Bun; } - // 3. pnpm: lockfileVersion 9.0 routes to the v9 backend, the legacy + // 4. pnpm: lockfileVersion 9.0 routes to the v9 backend, the legacy // grammars 5.4 (pnpm 7) / 6.0 (pnpm 8) to the legacy backend; // anything else refuses with the sniff's version-aware remedy. if exists(PNPM_LOCK).await { @@ -204,17 +224,17 @@ pub(crate) async fn detect_npm_lock_flavor( } } - // 4. yarn: classic v1 vs berry (node-modules linker), decided by content. + // 5. yarn: classic v1 vs berry (node-modules linker), decided by content. if exists("yarn.lock").await { break 'flavor sniff_yarn_lock(project_root).await?; } - // 5. npm (npm_lock itself prefers the shrinkwrap when both exist). + // 6. npm (npm_lock itself prefers the shrinkwrap when both exist). if exists(NPM_LOCKS[0]).await || exists(NPM_LOCKS[1]).await { break 'flavor NpmLockFlavor::PackageLock; } - // 6. nothing recognizable at the root. A Rush monorepo keeps its + // 7. nothing recognizable at the root. A Rush monorepo keeps its // single source-of-truth lock under common/config/rush/ (no root // package.json/lock pair), and its overrides live in // common/config/rush/pnpm-config.json rather than the lockfile — @@ -241,7 +261,7 @@ pub(crate) async fn detect_npm_lock_flavor( "vendor_lockfile_missing", format!( "no package-lock.json, npm-shrinkwrap.json, yarn.lock, pnpm-lock.yaml, \ - bun.lock, or bun.lockb {} — vendoring rewires the lockfile, so one must \ + bun.lock, bun.lockb, or vlt-lock.json {} — vendoring rewires the lockfile, so one must \ exist (run your package manager's install first)", project_root_location(project_root) ), @@ -347,6 +367,12 @@ pub async fn vendor_npm_any( Ok(found) => found, Err((code, detail)) => return VendorOutcome::Refused { code, detail }, }; + if let Some(detail) = flavor_change_refusal(project_root, purl, flavor).await { + return VendorOutcome::Refused { + code: "vendor_flavor_changed", + detail, + }; + } // Every backend takes the identical 9-argument tuple; the macro collapses // the five-way repetition (same shape as the CLI dispatcher's `vend!`). macro_rules! vend { @@ -372,6 +398,7 @@ pub async fn vendor_npm_any( NpmLockFlavor::Pnpm => vend!(pnpm_lock::vendor_pnpm), NpmLockFlavor::PnpmLegacy => vend!(pnpm_lock_legacy::vendor_pnpm_legacy), NpmLockFlavor::Bun => vend!(bun_lock::vendor_bun), + NpmLockFlavor::Vlt => vend!(vlt_lock::vendor_vlt), }; // Probe warnings (e.g. a sibling lockfile that will install UNPATCHED // bytes) precede the backend's own; the ledger records which flavor wired @@ -390,6 +417,37 @@ pub async fn vendor_npm_any( outcome } +/// The refusal for re-vendoring `purl` under `detected` when its ledger +/// entry was wired by a different flavor and either side is vlt: the other +/// backend's records name a lock this run never reads, so a revert through +/// either flavor would leave half of the wiring behind. Unreadable ledgers +/// are left to the caller's own persist step. +async fn flavor_change_refusal( + project_root: &Path, + purl: &str, + detected: NpmLockFlavor, +) -> Option { + let state = super::state::load_state(project_root).await.ok()?; + state.entries.iter().find_map(|(key, entry)| { + if entry.ecosystem != "npm" || !entry.covers_purl(key, purl) { + return None; + } + let prior = NpmLockFlavor::from_recorded(entry.flavor.as_deref()); + if prior == Some(detected) + || (prior != Some(NpmLockFlavor::Vlt) && detected != NpmLockFlavor::Vlt) + { + return None; + } + let prior = entry.flavor.as_deref().unwrap_or("package-lock"); + Some(format!( + "{purl} is vendored through the `{prior}` lockfile flavor, but this project now \ + installs through `{}`; run `socket-patch vendor --revert` for it while the lock it \ + was vendored under still drives installs, then vendor it again", + detected.as_str() + )) + }) +} + /// Is this npm-vendored entry still consumed by its lockfile's dependency /// graph? /// @@ -438,6 +496,7 @@ pub async fn vendored_entry_in_use(entry: &VendorEntry, project_root: &Path) -> .any(|package| package.resolution.contains(&needle)), ) } + NpmLockFlavor::Vlt => vlt_lock::vlt_entry_in_use(entry, project_root).await, } } @@ -516,6 +575,7 @@ pub async fn revert_npm_any_opts( pnpm_lock_legacy::revert_pnpm_legacy_opts(entry, project_root, opts).await } NpmLockFlavor::Bun => bun_lock::revert_bun_opts(entry, project_root, opts).await, + NpmLockFlavor::Vlt => vlt_lock::revert_vlt_opts(entry, project_root, opts).await, } } @@ -556,8 +616,8 @@ mod tests { assert_eq!( detail, "no package-lock.json, npm-shrinkwrap.json, yarn.lock, pnpm-lock.yaml, bun.lock, \ - or bun.lockb at /nonexistent-socket-patch-root — vendoring rewires the lockfile, \ - so one must exist (run your package manager's install first)" + bun.lockb, or vlt-lock.json at /nonexistent-socket-patch-root — vendoring rewires \ + the lockfile, so one must exist (run your package manager's install first)" ); } use crate::manifest::schema::PatchFileInfo; @@ -581,9 +641,17 @@ mod tests { #[test] fn flavor_strings_are_stable() { use NpmLockFlavor::*; - for flavor in [PackageLock, YarnClassic, YarnBerry, Pnpm, PnpmLegacy, Bun] { + for flavor in [ + PackageLock, + YarnClassic, + YarnBerry, + Pnpm, + PnpmLegacy, + Bun, + Vlt, + ] { match flavor { - PackageLock | YarnClassic | YarnBerry | Pnpm | PnpmLegacy | Bun => {} + PackageLock | YarnClassic | YarnBerry | Pnpm | PnpmLegacy | Bun | Vlt => {} } assert_eq!( NpmLockFlavor::from_recorded(Some(flavor.as_str())), @@ -596,6 +664,7 @@ mod tests { assert_eq!(NpmLockFlavor::Pnpm.as_str(), "pnpm"); assert_eq!(NpmLockFlavor::PnpmLegacy.as_str(), "pnpm-legacy"); assert_eq!(NpmLockFlavor::Bun.as_str(), "bun"); + assert_eq!(NpmLockFlavor::Vlt.as_str(), "vlt"); } #[tokio::test] @@ -853,6 +922,159 @@ mod tests { assert_eq!(flavor, NpmLockFlavor::PackageLock); } + const VLT_V1: &str = "{\n \"lockfileVersion\": 1,\n \"nodes\": {},\n \"edges\": {}\n}\n"; + + #[tokio::test] + async fn vlt_lock_sniff_accepts_v0_v1_and_refuses_every_other_shape() { + for version in ["0", "1"] { + let tmp = tempfile::tempdir().unwrap(); + touch( + tmp.path(), + "vlt-lock.json", + &format!("{{\"lockfileVersion\":{version},\n\"nodes\":{{\"a\": [0,\"a\"]}}}}"), + ) + .await; + let (flavor, warnings) = detect_npm_lock_flavor(tmp.path()).await.unwrap(); + assert_eq!( + flavor, + NpmLockFlavor::Vlt, + "{version}: layout is not checked here" + ); + assert!(warnings.is_empty()); + } + for (lock, needle) in [ + ("{\"nodes\": {}}", "has no lockfileVersion (vlt ≤ 0.0.0-18)"), + ( + "{\"lockfileVersion\": 2}", + "lockfileVersion 2; update socket-patch", + ), + ( + "{\"lockfileVersion\": 1e0}", + "lockfileVersion 1e0; update socket-patch", + ), + ( + "\u{feff}{\"lockfileVersion\": 1}", + "re-save vlt-lock.json with `vlt install`", + ), + ( + "{\"lockfileVersion\": 1", + "re-save vlt-lock.json with `vlt install`", + ), + ("[1]", "re-save vlt-lock.json with `vlt install`"), + ] { + let tmp = tempfile::tempdir().unwrap(); + touch(tmp.path(), "vlt-lock.json", lock).await; + touch(tmp.path(), "package-lock.json", "{}").await; + let (code, detail) = detect_npm_lock_flavor(tmp.path()).await.unwrap_err(); + assert_eq!(code, "vendor_lockfile_version_unsupported", "{lock}"); + assert!(detail.contains(needle), "{lock}: {detail}"); + } + } + + #[tokio::test] + async fn vlt_outranks_every_other_lock_after_pnp() { + let tmp = tempfile::tempdir().unwrap(); + touch(tmp.path(), "vlt-lock.json", VLT_V1).await; + touch(tmp.path(), "bun.lock", "{}").await; + touch(tmp.path(), "bun.lockb", "binary").await; + touch(tmp.path(), "pnpm-lock.yaml", PNPM_9).await; + touch(tmp.path(), "yarn.lock", YARN_V1).await; + touch(tmp.path(), "package-lock.json", "{}").await; + touch(tmp.path(), "npm-shrinkwrap.json", "{}").await; + let (flavor, warnings) = detect_npm_lock_flavor(tmp.path()).await.unwrap(); + assert_eq!(flavor, NpmLockFlavor::Vlt); + let mut named: Vec<&str> = warnings + .iter() + .map(|w| { + assert_eq!(w.code, "vendor_multiple_lockfiles"); + assert!(w.detail.contains("vlt vendor backend"), "{}", w.detail); + w.detail.split('`').nth(1).unwrap() + }) + .collect(); + named.sort_unstable(); + assert_eq!( + named, + [ + "bun.lock", + "bun.lockb", + "npm-shrinkwrap.json", + "package-lock.json", + "pnpm-lock.yaml", + "yarn.lock" + ] + ); + + let tmp = tempfile::tempdir().unwrap(); + touch(tmp.path(), ".pnp.cjs", "/* pnp */").await; + touch(tmp.path(), "vlt-lock.json", VLT_V1).await; + let (code, _) = detect_npm_lock_flavor(tmp.path()).await.unwrap_err(); + assert_eq!( + code, "vendor_yarn_berry_unsupported", + "PnP is still refused first" + ); + } + + #[tokio::test] + async fn flavor_change_to_or_from_vlt_refuses_before_any_write() { + let (tmp, record) = npm_project().await; + let mut state = super::super::state::VendorState::new(); + state.entries.insert( + "pkg:npm/left-pad@1.3.0".into(), + probe_entry(Some("package-lock")), + ); + super::super::state::save_state(tmp.path(), &state) + .await + .unwrap(); + touch(tmp.path(), "vlt-lock.json", VLT_V1).await; + let lock_before = tokio::fs::read(tmp.path().join("vlt-lock.json")) + .await + .unwrap(); + let VendorOutcome::Refused { code, detail } = vendor_any(tmp.path(), &record).await else { + panic!("expected the flavor guard"); + }; + assert_eq!(code, "vendor_flavor_changed"); + assert!( + detail.contains("`package-lock`") && detail.contains("`vlt`"), + "{detail}" + ); + assert_eq!( + tokio::fs::read(tmp.path().join("vlt-lock.json")) + .await + .unwrap(), + lock_before + ); + assert!(!tmp + .path() + .join(format!(".socket/vendor/npm/{UUID}")) + .exists()); + + // vlt → package-lock refuses the same way. + tokio::fs::remove_file(tmp.path().join("vlt-lock.json")) + .await + .unwrap(); + state + .entries + .insert("pkg:npm/left-pad@1.3.0".into(), probe_entry(Some("vlt"))); + super::super::state::save_state(tmp.path(), &state) + .await + .unwrap(); + let VendorOutcome::Refused { code, .. } = vendor_any(tmp.path(), &record).await else { + panic!("expected the flavor guard"); + }; + assert_eq!(code, "vendor_flavor_changed"); + + // Switches between the other flavors are not this guard's to judge. + state.entries.insert( + "pkg:npm/left-pad@1.3.0".into(), + probe_entry(Some("yarn-classic")), + ); + super::super::state::save_state(tmp.path(), &state) + .await + .unwrap(); + let outcome = vendor_any(tmp.path(), &record).await; + assert!(matches!(outcome, VendorOutcome::Done { .. }), "{outcome:?}"); + } + #[tokio::test] async fn precedence_and_multiple_lockfile_warnings() { // bun.lock beats pnpm beats yarn beats package-lock; every unwired @@ -1084,7 +1306,7 @@ mod tests { assert!(outcome.error.as_deref().unwrap().contains("future-pm")); assert!(!npm_flavor_is_known(Some("future-pm"))); - assert!(!npm_flavor_is_known(Some("vlt"))); + assert!(npm_flavor_is_known(Some("vlt"))); // Every known flavor routes to its backend; with no wiring records and // nothing on disk each reverts trivially (None = a pre-flavor ledger). @@ -1096,6 +1318,7 @@ mod tests { Some("pnpm".to_string()), Some("pnpm-legacy".to_string()), Some("bun".to_string()), + Some("vlt".to_string()), ] { assert!(npm_flavor_is_known(flavor.as_deref()), "{flavor:?}"); entry.flavor = flavor.clone(); @@ -1184,6 +1407,35 @@ mod tests { // Unknown flavor: undeterminable, fail-safe keep. let entry = probe_entry(Some("future-pm")); assert_eq!(vendored_entry_in_use(&entry, tmp.path()).await, None); + + // vlt is structural: only a `file` node under the uuid dir counts, + // never a mention in an edge spec or another node's slot. + let entry = probe_entry(Some("vlt")); + let tmp = tempfile::tempdir().unwrap(); + assert_eq!(vendored_entry_in_use(&entry, tmp.path()).await, None); + let rel = format!(".socket/vendor/npm/{UUID}/left-pad-1.3.0/node_modules/left-pad"); + let file_id = + format!("file~.socket+vendor+npm+{UUID}+left-pad-1.3.0+node__modules+left-pad"); + touch( + tmp.path(), + "vlt-lock.json", + &format!( + "{{\n \"lockfileVersion\": 1,\n \"nodes\": {{\n \"{file_id}\": [0,\"left-pad\",null,\"{rel}\"]\n }},\n \"edges\": {{}}\n}}\n" + ), + ) + .await; + assert_eq!(vendored_entry_in_use(&entry, tmp.path()).await, Some(true)); + touch( + tmp.path(), + "vlt-lock.json", + &format!( + "{{\n \"lockfileVersion\": 1,\n \"nodes\": {{\n \"~npm~x@1.0.0\": [0,\"x\",null,\"{rel}\"]\n }},\n \"edges\": {{\n \"file~_d x\": \"prod file:./{rel} ~npm~x@1.0.0\"\n }}\n}}\n" + ), + ) + .await; + assert_eq!(vendored_entry_in_use(&entry, tmp.path()).await, Some(false)); + touch(tmp.path(), "vlt-lock.json", "\u{feff}{}").await; + assert_eq!(vendored_entry_in_use(&entry, tmp.path()).await, None); } #[tokio::test] @@ -1337,12 +1589,15 @@ mod tests { mkfifo(&pnpm_dir.path().join("pnpm-lock.yaml")); let yarn_dir = tempfile::tempdir().unwrap(); mkfifo(&yarn_dir.path().join("yarn.lock")); + let vlt_dir = tempfile::tempdir().unwrap(); + mkfifo(&vlt_dir.path().join("vlt-lock.json")); let in_use_dir = tempfile::tempdir().unwrap(); - const IN_USE_LOCKS: [&str; 4] = [ + const IN_USE_LOCKS: [&str; 5] = [ "npm-shrinkwrap.json", "package-lock.json", "yarn.lock", "bun.lock", + "vlt-lock.json", ]; for name in IN_USE_LOCKS { mkfifo(&in_use_dir.path().join(name)); @@ -1353,12 +1608,14 @@ mod tests { ( detect_npm_lock_flavor(pnpm_dir.path()).await, detect_npm_lock_flavor(yarn_dir.path()).await, + detect_npm_lock_flavor(vlt_dir.path()).await, vendored_entry_in_use(&probe_entry(Some("package-lock")), in_use_dir.path()).await, vendored_entry_in_use(&probe_entry(Some("yarn-classic")), in_use_dir.path()).await, vendored_entry_in_use(&probe_entry(Some("bun")), in_use_dir.path()).await, + vendored_entry_in_use(&probe_entry(Some("vlt")), in_use_dir.path()).await, ) }; - let Ok((pnpm, yarn, npm_use, yarn_use, bun_use)) = + let Ok((pnpm, yarn, vlt, npm_use, yarn_use, bun_use, vlt_use)) = tokio::time::timeout(deadline, all).await else { // On timeout the open is wedged in a `spawn_blocking` thread the @@ -1368,6 +1625,7 @@ mod tests { let stuck = [ pnpm_dir.path().join("pnpm-lock.yaml"), yarn_dir.path().join("yarn.lock"), + vlt_dir.path().join("vlt-lock.json"), ]; for path in stuck .iter() @@ -1395,5 +1653,9 @@ mod tests { assert_eq!(npm_use, None); assert_eq!(yarn_use, None); assert_eq!(bun_use, None); + let (code, detail) = vlt.unwrap_err(); + assert_eq!(code, "vendor_lockfile_missing", "{detail}"); + assert!(detail.contains("vlt-lock.json"), "{detail}"); + assert_eq!(vlt_use, None); } } diff --git a/crates/socket-patch-core/src/vendor/npm_lock.rs b/crates/socket-patch-core/src/vendor/npm_lock.rs index 2541d5db..7ad1f30b 100644 --- a/crates/socket-patch-core/src/vendor/npm_lock.rs +++ b/crates/socket-patch-core/src/vendor/npm_lock.rs @@ -426,7 +426,9 @@ pub async fn vendor_npm( /// FAIL-CLOSED revert guard for a ledger entry with NO wiring records, /// shared by the four TEXTUAL npm-family backends (npm / yarn classic / /// yarn berry / bun) — the flavor-parameterized sibling of -/// [`super::pnpm_lock::guard_unwired_revert`]. +/// [`super::pnpm_lock::guard_unwired_revert`]. vlt has its own structural +/// guard in [`super::vlt_lock`]: its lock names the artifact in DepID keys +/// and slot [3], so the textual probe is not exact there. /// /// Such entries come out of `repair`'s no-ledger reconstruction (the /// npm-family pre-vendor lock fragments are not offline-recoverable, so the diff --git a/crates/socket-patch-core/src/vendor/path.rs b/crates/socket-patch-core/src/vendor/path.rs index b7b8a278..0b946b17 100644 --- a/crates/socket-patch-core/src/vendor/path.rs +++ b/crates/socket-patch-core/src/vendor/path.rs @@ -20,7 +20,7 @@ //! //! | eco | leaf | //! |----------|----------------------------------------| -//! | npm | `[@scope/]-.tgz` | +//! | npm | `[@scope/]-.tgz`; vlt: `[@scope/]-/node_modules//` | //! | cargo | `-/` | //! | golang | `@/` (nested dirs) | //! | composer | `/@/` | @@ -230,11 +230,16 @@ fn split_nuget_leaf(stem: &str) -> Option<(&str, &str)> { /// the latter for lockfile-discovered references. pub(crate) fn leaf_to_purl(eco: &str, leaf: &str) -> Option { match eco { - "npm" => { - let stem = leaf.strip_suffix(".tgz")?; - let (name, version) = split_name_version(stem)?; - Some(format!("pkg:npm/{name}@{version}")) - } + "npm" => match leaf.strip_suffix(".tgz") { + Some(stem) => { + let (name, version) = split_name_version(stem)?; + Some(format!("pkg:npm/{name}@{version}")) + } + None => { + let (name, version) = super::vlt_lock_text::parse_vendored_dir_leaf(leaf)?; + Some(format!("pkg:npm/{name}@{version}")) + } + }, "cargo" => { let (name, version) = split_name_version(leaf)?; Some(format!("pkg:cargo/{name}@{version}")) @@ -406,6 +411,55 @@ mod tests { const UUID: &str = "9f6b2c4e-1d3a-4f6b-8c2d-7e5a9b1c3d5f"; + #[test] + fn vlt_dir_leaves_name_their_package() { + for (leaf, want) in [ + ( + "left-pad-1.3.0/node_modules/left-pad", + Some("pkg:npm/left-pad@1.3.0"), + ), + ( + "@s/b-2.0.0-rc.1/node_modules/@s/b", + Some("pkg:npm/@s/b@2.0.0-rc.1"), + ), + ( + "base-64-1.0.0/node_modules/base-64", + Some("pkg:npm/base-64@1.0.0"), + ), + ( + "a-1.0.0-1.0.0/node_modules/a-1.0.0", + Some("pkg:npm/a-1.0.0@1.0.0"), + ), + ("@s/b-1.0.0/node_modules/@t/b", None), + ("left-pad-1.3.0/node_modules/right-pad", None), + ("left-pad-1.3/node_modules/left-pad", None), + ("left-pad-1.3.0", None), + ] { + assert_eq!(leaf_to_purl("npm", leaf).as_deref(), want, "{leaf}"); + } + } + + #[tokio::test] + async fn the_sweep_recognizes_a_vlt_dir_and_never_descends_into_it() { + let tmp = tempfile::tempdir().unwrap(); + let pkg = tmp.path().join(format!( + ".socket/vendor/npm/{UUID}/@s/b-1.0.0/node_modules/@s/b" + )); + tokio::fs::create_dir_all(pkg.join("node_modules/dep-9.9.9/node_modules/dep")) + .await + .unwrap(); + tokio::fs::write( + tmp.path() + .join(format!(".socket/vendor/npm/{UUID}/.gitignore")), + "", + ) + .await + .unwrap(); + let swept = sweep_vendor_dirs(tmp.path()).await; + assert_eq!(swept.len(), 1); + assert_eq!(swept[0].purls, ["pkg:npm/@s/b@1.0.0"]); + } + #[test] fn uuid_dir_is_validated() { assert_eq!( diff --git a/crates/socket-patch-core/src/vendor/registry_fetch.rs b/crates/socket-patch-core/src/vendor/registry_fetch.rs index fa2cafed..bd53dea5 100644 --- a/crates/socket-patch-core/src/vendor/registry_fetch.rs +++ b/crates/socket-patch-core/src/vendor/registry_fetch.rs @@ -326,7 +326,10 @@ async fn resolve_pypi_url_by_hash( entry.version ); let resp = client.get(&api).send().await.map_err(|e| { - FetchError::Failed(format!("PyPI JSON API request for {} failed: {e}", entry.purl)) + FetchError::Failed(format!( + "PyPI JSON API request for {} failed: {e}", + entry.purl + )) })?; if !resp.status().is_success() { return Err(FetchError::Failed(format!( @@ -336,7 +339,10 @@ async fn resolve_pypi_url_by_hash( ))); } let body: serde_json::Value = resp.json().await.map_err(|e| { - FetchError::Failed(format!("PyPI JSON API response for {} is not JSON: {e}", entry.purl)) + FetchError::Failed(format!( + "PyPI JSON API response for {} is not JSON: {e}", + entry.purl + )) })?; let digest_matches = |file: &serde_json::Value| { file.get("digests") @@ -899,6 +905,53 @@ pub async fn stage_local_artifact( }) } +/// Stage a package from a committed vlt directory artifact (the +/// fresh-clone re-vendor path): an inventory-verified copy of `dir` without +/// its `node_modules/`. Refused when the ledger records no inventory, and +/// on any missing, extra or modified file. +pub async fn stage_local_dir_artifact( + dir: &Path, + inventory: Option<&std::collections::BTreeMap>, +) -> Result { + let Some(inventory) = inventory else { + return Err(FetchError::Unverifiable( + "the vendor ledger records no file inventory for the artifact".to_string(), + )); + }; + let actual = super::verify::compute_package_dir_inventory(dir) + .await + .map_err(|e| FetchError::Failed(format!("{}: {e}", dir.display())))?; + if &actual != inventory { + return Err(FetchError::Failed(format!( + "{}: the committed dir does not match the vendor ledger's file inventory", + dir.display() + ))); + } + let tmp = tempfile::tempdir() + .map_err(|e| FetchError::Failed(format!("cannot create staging tempdir: {e}")))?; + let staged = tmp.path().join("package"); + crate::patch::copy_tree::fresh_copy(dir, &staged, None) + .await + .map_err(|e| FetchError::Failed(format!("cannot stage {}: {e}", dir.display())))?; + crate::patch::copy_tree::remove_tree(&staged.join("node_modules")) + .await + .map_err(|e| FetchError::Failed(format!("cannot stage {}: {e}", dir.display())))?; + let copied = super::verify::compute_package_dir_inventory(&staged) + .await + .map_err(|e| FetchError::Failed(format!("{}: {e}", dir.display())))?; + if &copied != inventory { + return Err(FetchError::Failed(format!( + "{}: the staged copy does not match the vendor ledger's file inventory", + dir.display() + ))); + } + Ok(FetchedPackage { + dir: staged, + url: format!("file:{}", dir.display()), + _tmp: tmp, + }) +} + /// Capped download. http(s) only; the cap is enforced on the declared /// Content-Length AND the actual stream (a lying server cannot blow past /// it). @@ -1134,13 +1187,20 @@ fn strip_first_component(path: &Path) -> Option { /// `.crate` (tar.gz, single top-level `{name}-{version}/` prefix) into the /// vendor copy dir — the same content the local `fresh_copy` produces. pub(crate) fn extract_tgz(bytes: &[u8], dest: &Path) -> Result<(), String> { - extract_tar_gz(bytes, dest, /*strip_first=*/ true) + extract_tar_gz(bytes, dest, /*strip_first=*/ true, false) +} + +/// [`extract_tgz`] that refuses the archive instead of skipping a symlink, +/// hardlink, device or FIFO entry (a directory artifact is committed as +/// extracted, so nothing the archive carries may be silently dropped). +pub(crate) fn extract_tgz_strict(bytes: &[u8], dest: &Path) -> Result<(), String> { + extract_tar_gz(bytes, dest, /*strip_first=*/ true, true) } /// Like [`extract_tgz`] but keeps entry paths verbatim (gem `data.tar.gz` /// archives carry package content at the root, no prefix dir). fn extract_tgz_no_strip(bytes: &[u8], dest: &Path) -> Result<(), String> { - extract_tar_gz(bytes, dest, /*strip_first=*/ false) + extract_tar_gz(bytes, dest, /*strip_first=*/ false, false) } /// Extract a `.gem`'s package content into `dest`. A `.gem` is a plain @@ -1179,7 +1239,12 @@ pub(crate) fn extract_gem_data(gem_bytes: &[u8], dest: &Path) -> Result<(), Stri Err("the .gem carries no data.tar.gz".to_string()) } -fn extract_tar_gz(bytes: &[u8], dest: &Path, strip_first: bool) -> Result<(), String> { +fn extract_tar_gz( + bytes: &[u8], + dest: &Path, + strip_first: bool, + strict: bool, +) -> Result<(), String> { use std::io::Read as _; let gz = flate2::read::GzDecoder::new(bytes).take(MAX_TOTAL_DECOMPRESSED_BYTES); let mut archive = tar::Archive::new(gz); @@ -1195,7 +1260,21 @@ fn extract_tar_gz(bytes: &[u8], dest: &Path, strip_first: bool) -> Result<(), St } // Regular files only: symlinks/hardlinks/devices never extract // (a symlink could redirect later entries out of the stage). - if !entry.header().entry_type().is_file() { + let kind = entry.header().entry_type(); + if !kind.is_file() { + if strict + && !(kind.is_dir() + || kind.is_pax_global_extensions() + || kind.is_pax_local_extensions()) + { + return Err(format!( + "tarball entry `{}` is not a regular file or directory — refusing the artifact", + entry + .path() + .map(|p| p.display().to_string()) + .unwrap_or_default() + )); + } continue; } let raw = entry @@ -1934,7 +2013,6 @@ mod tests { .dir() .join("requests-2.28.0.dist-info/RECORD") .is_file()); - } /// poetry.lock records wheel hashes but no URLs: the fetcher resolves the @@ -1944,7 +2022,10 @@ mod tests { async fn pypi_hash_only_entry_is_resolved_through_the_json_api() { let wheel = make_zip(&[ ("requests/__init__.py", b"__version__ = '2.28.0'\n"), - ("requests-2.28.0.dist-info/RECORD", b"requests/__init__.py,sha256=abc,24\n"), + ( + "requests-2.28.0.dist-info/RECORD", + b"requests/__init__.py,sha256=abc,24\n", + ), ]); let sha = hex::encode(Sha256::digest(&wheel)); let mock = MockServer::start().await; @@ -2014,7 +2095,10 @@ mod tests { async fn pypi_digest_set_entry_picks_the_pure_wheel_by_hash() { let wheel = make_zip(&[ ("requests/__init__.py", b"__version__ = '2.28.0'\n"), - ("requests-2.28.0.dist-info/RECORD", b"requests/__init__.py,sha256=abc,24\n"), + ( + "requests-2.28.0.dist-info/RECORD", + b"requests/__init__.py,sha256=abc,24\n", + ), ]); let wheel_sha = hex::encode(Sha256::digest(&wheel)); let sdist_sha = "0".repeat(64); @@ -2079,11 +2163,21 @@ mod tests { restore(); let fetched = fetched.unwrap(); assert!(fetched.dir().join("requests/__init__.py").is_file()); - assert!(fetched.url.ends_with("requests-2.28.0-py3-none-any.whl"), "{}", fetched.url); - for (label, result) in [("no pure wheel", no_pure_result), ("unknown", unknown_result)] { + assert!( + fetched.url.ends_with("requests-2.28.0-py3-none-any.whl"), + "{}", + fetched.url + ); + for (label, result) in [ + ("no pure wheel", no_pure_result), + ("unknown", unknown_result), + ] { match result { Err(FetchError::Unverifiable(msg)) => { - assert!(msg.contains("none-any.whl") && msg.contains("digests"), "{label}: {msg}") + assert!( + msg.contains("none-any.whl") && msg.contains("digests"), + "{label}: {msg}" + ) } other => panic!("{label}: expected Unverifiable, got {other:?}"), } @@ -2791,7 +2885,10 @@ mod tests { let tmp = tempfile::tempdir().unwrap(); extract_zip(&bytes, tmp.path(), /*strip_first=*/ false).unwrap(); assert!(tmp.path().join("m@v1/go.mod").is_file()); - assert!(!tmp.path().join("m@v1/d").exists(), "dir entry must not materialize"); + assert!( + !tmp.path().join("m@v1/d").exists(), + "dir entry must not materialize" + ); // The dirhash covers FILES only — the dir entry must not add a line. assert_eq!( @@ -3011,8 +3108,11 @@ mod tests { let zip_bytes = make_module_zip("m@v1/", &[("go.mod", b"module m\n")]); let h1 = go_h1_of_zip(&zip_bytes).unwrap(); verify_go_h1(&zip_bytes, &h1).expect("a matching dirhash must verify"); - let err = verify_go_h1(&zip_bytes, "h1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") - .unwrap_err(); + let err = verify_go_h1( + &zip_bytes, + "h1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + ) + .unwrap_err(); assert!(err.contains("mismatch"), "{err}"); } @@ -3044,9 +3144,8 @@ mod tests { // GoH1 has a dedicated fetch-path verifier; None is reachable from a // repair against an npm-era lock recording no integrity. Both refuse. - let err = - artifact_matches_integrity(b"x", "pkg", &LockIntegrity::GoH1("h1:x".into())) - .unwrap_err(); + let err = artifact_matches_integrity(b"x", "pkg", &LockIntegrity::GoH1("h1:x".into())) + .unwrap_err(); assert!(err.contains("dedicated ecosystem fetcher"), "{err}"); let err = artifact_matches_integrity(b"x", "pkg", &LockIntegrity::None).unwrap_err(); assert!(err.contains("no integrity recorded"), "{err}"); diff --git a/crates/socket-patch-core/src/vendor/reuse.rs b/crates/socket-patch-core/src/vendor/reuse.rs index 79a9eeb5..fd8b19fa 100644 --- a/crates/socket-patch-core/src/vendor/reuse.rs +++ b/crates/socket-patch-core/src/vendor/reuse.rs @@ -268,6 +268,58 @@ pub(crate) async fn reusable_committed_artifact( verify_committed_artifact(project_root, &entry, record).await } +/// DESIGN §4.3 step 2: the committed vlt package dir at `rel_dir`, when +/// the ledger records it for `record.uuid` with an inventory and the tree +/// still verifies (no link on the path, the structure rule, only links and +/// `.bin/` scripts under its `node_modules/`, every member and the whole +/// inventory). Returns the recorded inventory. Read-only. +pub(crate) async fn reusable_committed_dir( + project_root: &Path, + record: &PatchRecord, + rel_dir: &str, +) -> Result, ReuseMiss> { + if record.files.is_empty() { + return Err(ReuseMiss::NoFiles); + } + let state = load_state(project_root) + .await + .map_err(|_| ReuseMiss::NoLedger)?; + let mut hits: Vec = state + .entries + .into_values() + .filter(|e| { + e.ecosystem == "npm" + && e.uuid == record.uuid + && e.flavor.as_deref() == Some(super::vlt_lock::FLAVOR) + && norm(&e.artifact.path) == rel_dir + && e.artifact.file_inventory.is_some() + }) + .collect(); + let Some(entry) = hits.pop() else { + return Err(ReuseMiss::NoEntry); + }; + if hits + .iter() + .any(|e| e.artifact.file_inventory != entry.artifact.file_inventory) + { + return Err(ReuseMiss::Ambiguous); + } + let mut prefix = project_root.to_path_buf(); + for seg in rel_dir.split('/') { + prefix.push(seg); + match tokio::fs::symlink_metadata(&prefix).await { + Ok(meta) if meta.file_type().is_symlink() => return Err(ReuseMiss::NotRegular), + Ok(_) => {} + Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Err(ReuseMiss::Missing), + Err(_) => return Err(ReuseMiss::Unreadable), + } + } + super::verify::verify_vendored_patch_record(project_root, &entry, record) + .await + .map_err(ReuseMiss::MemberMismatch)?; + Ok(entry.artifact.file_inventory.unwrap_or_default()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/socket-patch-core/src/vendor/state.rs b/crates/socket-patch-core/src/vendor/state.rs index 6dd6f56c..489fe85e 100644 --- a/crates/socket-patch-core/src/vendor/state.rs +++ b/crates/socket-patch-core/src/vendor/state.rs @@ -228,8 +228,10 @@ pub struct VendorEntry { #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub took_over_go_patches: bool, /// Which wiring flavor was used, for the multi-flavor ecosystems — - /// npm: `package-lock` | `yarn-classic` | `yarn-berry` | `pnpm` | `bun` - /// (absent on pre-flavor entries ⇒ `package-lock`); pypi: `uv` | `requirements` | + /// npm: `package-lock` | `yarn-classic` | `yarn-berry` | `pnpm` | + /// `pnpm-legacy` | `bun` | `vlt` (absent on pre-flavor entries ⇒ + /// `package-lock`; a `vlt` artifact is a package directory, whose + /// `fileInventory` excludes its `node_modules/`); pypi: `uv` | `requirements` | /// `poetry` | `pdm` | `pipenv`. Reverts route on this and fail closed /// on flavors this build has no backend for. #[serde(default, skip_serializing_if = "Option::is_none")] diff --git a/crates/socket-patch-core/src/vendor/verify.rs b/crates/socket-patch-core/src/vendor/verify.rs index 2b034320..159328ab 100644 --- a/crates/socket-patch-core/src/vendor/verify.rs +++ b/crates/socket-patch-core/src/vendor/verify.rs @@ -92,6 +92,9 @@ pub async fn verify_vendored_patch_record( // `.nupkg` (a plain OPC zip) / `.jar` (a plain zip) via the bounded zip // reader — their member paths are package-relative, exactly the manifest // key space. Everything else is a dir-shaped copy hashed in place. + if is_vlt_dir_entry(entry) { + return verify_vlt_dir(project_root, &artifact, entry, record).await; + } let path_str = artifact.to_string_lossy(); let is_tarball = path_str.ends_with(".tgz") || path_str.ends_with(".tar.gz"); let is_zip = @@ -163,6 +166,112 @@ async fn verify_dir_members( Ok(()) } +/// A vlt package-dir entry (DESIGN §4.2): npm, flavor `vlt`, not a tarball. +pub(crate) fn is_vlt_dir_entry(entry: &VendorEntry) -> bool { + entry.ecosystem == "npm" + && entry.flavor.as_deref() == Some(super::vlt_lock::FLAVOR) + && !artifact_is_file_shaped(&entry.artifact.path) +} + +/// The largest afterHash blob the vlt manifest exemption reads. +const MAX_MANIFEST_BLOB_BYTES: u64 = 16 * 1024 * 1024; + +/// A vlt package dir: the §4.2 structure rule, only links and `.bin/` +/// scripts under its `node_modules/`, every record member at its afterHash +/// with the vlt manifest exemption for `package.json` (§4.4), and the +/// inventory of everything but `node_modules/`. +async fn verify_vlt_dir( + project_root: &Path, + dir: &Path, + entry: &VendorEntry, + record: &PatchRecord, +) -> Result<(), String> { + let Some((name, _)) = parse_vendor_path(&entry.artifact.path) + .and_then(|p| super::vlt_lock_text::parse_vendored_dir_leaf(&p.leaf)) + else { + return Err("vendor_path_unsafe".to_string()); + }; + if !super::npm_dir::structure_rule_holds(dir, &name).await + || !super::npm_dir::node_modules_holds_only_links(dir).await + { + return Err("vendor_inventory_mismatch".to_string()); + } + let inventory = entry.artifact.file_inventory.as_ref(); + for (file_name, info) in &record.files { + if normalize_file_path(file_name) == "package.json" { + if let Some(pin) = inventory.and_then(|inv| inv.get("package.json")) { + if vlt_manifest_matches(project_root, dir, pin, &info.after_hash).await { + continue; + } + return Err("vendor_hash_mismatch".to_string()); + } + } + match verify_file_patch(dir, file_name, info).await.status { + VerifyStatus::AlreadyPatched => {} + VerifyStatus::Ready | VerifyStatus::HashMismatch => { + return Err("vendor_hash_mismatch".to_string()) + } + VerifyStatus::NotFound => return Err("file_not_found".to_string()), + } + } + if let Some(inventory) = inventory { + let actual = compute_package_dir_inventory(dir) + .await + .map_err(|_| "vendor_artifact_unreadable".to_string())?; + let same = actual.len() == inventory.len() + && inventory + .iter() + .all(|(rel, sha)| actual.get(rel).is_some_and(|a| a.eq_ignore_ascii_case(sha))); + if !same { + return Err("vendor_inventory_mismatch".to_string()); + } + } + Ok(()) +} + +/// The vlt manifest exemption (DESIGN §4.4): the committed `package.json` +/// is post-transform, so it verifies iff it hashes to the inventory pin and, +/// when the afterHash blob is in the local blob store, the blob with its +/// devDependencies stripped hashes to that pin too. +async fn vlt_manifest_matches( + project_root: &Path, + dir: &Path, + pin: &str, + after_hash: &str, +) -> bool { + use sha2::{Digest, Sha256}; + let Ok(on_disk) = crate::utils::fs::read_regular_to_bytes(&dir.join("package.json")).await + else { + return false; + }; + if !hex::encode(Sha256::digest(&on_disk)).eq_ignore_ascii_case(pin) { + return false; + } + let blob_path = project_root.join(".socket/blobs").join(after_hash); + let blob = match tokio::fs::metadata(&blob_path).await { + Ok(meta) if meta.is_file() && meta.len() <= MAX_MANIFEST_BLOB_BYTES => { + crate::utils::fs::read_regular_to_bytes(&blob_path) + .await + .ok() + } + _ => None, + }; + let Some(blob) = + blob.filter(|b| compute_git_sha256_from_bytes(b).eq_ignore_ascii_case(after_hash)) + else { + return true; + }; + let Ok(text) = String::from_utf8(blob) else { + return false; + }; + let stripped = match super::npm_dir::strip_dev_dependencies(&text) { + Ok(Some(stripped)) => stripped, + Ok(None) => text, + Err(_) => return false, + }; + hex::encode(Sha256::digest(stripped.as_bytes())).eq_ignore_ascii_case(pin) +} + /// Does the cargo copy's `Cargo.toml`, Socket tag dropped, hash to /// `after_hash` — with the tag being exactly `uuid`'s? A manifest tagged for /// another patch (a hand edit, a merged vendored tree, a half-applied uuid @@ -308,6 +417,21 @@ pub fn artifact_is_file_shaped(path: &str) -> bool { /// could escape the artifact dir or wedge the audit), a non-UTF-8 name, an /// unreadable file, or a tree past the entry cap. pub async fn compute_dir_inventory(dir: &Path) -> Result, String> { + inventory_walk(dir, false).await +} + +/// [`compute_dir_inventory`] of a vlt package dir, leaving out its top-level +/// `node_modules/` (vlt's links, never part of the artifact). +pub(crate) async fn compute_package_dir_inventory( + dir: &Path, +) -> Result, String> { + inventory_walk(dir, true).await +} + +async fn inventory_walk( + dir: &Path, + skip_node_modules: bool, +) -> Result, String> { let root = dir.to_path_buf(); tokio::task::spawn_blocking(move || { use sha2::{Digest, Sha256}; @@ -324,6 +448,9 @@ pub async fn compute_dir_inventory(dir: &Path) -> Result>(), + ["index.js"] + ); + assert_eq!( + check_vendored_artifact(root, &ent, &rec).await, + ArtifactHealth::Healthy + ); + + tokio::fs::write(dir.join("node_modules/planted.js"), b"x") + .await + .unwrap(); + assert_eq!( + verify_vendored_patch_record(root, &ent, &rec).await, + Err("vendor_inventory_mismatch".to_string()) + ); + tokio::fs::remove_file(dir.join("node_modules/planted.js")) + .await + .unwrap(); + + let beside = root.join(format!( + ".socket/vendor/npm/{UUID}/left-pad-1.3.0/node_modules/other" + )); + tokio::fs::create_dir_all(&beside).await.unwrap(); + assert_eq!( + verify_vendored_patch_record(root, &ent, &rec).await, + Err("vendor_inventory_mismatch".to_string()) + ); + tokio::fs::remove_dir(&beside).await.unwrap(); + + tokio::fs::write(dir.join("extra.js"), b"x").await.unwrap(); + assert!(matches!( + check_vendored_artifact(root, &ent, &rec).await, + ArtifactHealth::Corrupt { .. } + )); + } + + #[tokio::test] + async fn vlt_manifest_blob_pins_the_inventory() { + use sha2::Digest; + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path(); + let rel = format!(".socket/vendor/npm/{UUID}/a-1.0.0/node_modules/a"); + let dir = root.join(&rel); + tokio::fs::create_dir_all(&dir).await.unwrap(); + let blob: &[u8] = b"{\"name\":\"a\",\"devDependencies\":{\"t\":\"1\"},\"main\":\"m.js\"}"; + let committed = "{\"name\":\"a\",\"main\":\"x.js\"}"; + tokio::fs::write(dir.join("package.json"), committed) + .await + .unwrap(); + let mut rec = record(UUID, "package/index.js"); + rec.files.clear(); + rec.files.insert( + "package/package.json".into(), + PatchFileInfo { + before_hash: "b".into(), + after_hash: compute_git_sha256_from_bytes(blob), + }, + ); + let mut ent = entry("npm", UUID, &rel); + ent.flavor = Some("vlt".into()); + ent.artifact.file_inventory = Some(BTreeMap::from([( + "package.json".to_string(), + hex::encode(sha2::Sha256::digest(committed.as_bytes())), + )])); + assert_eq!(verify_vendored_patch_record(root, &ent, &rec).await, Ok(())); + let blobs = root.join(".socket/blobs"); + tokio::fs::create_dir_all(&blobs).await.unwrap(); + tokio::fs::write(blobs.join(compute_git_sha256_from_bytes(blob)), blob) + .await + .unwrap(); + assert_eq!( + verify_vendored_patch_record(root, &ent, &rec).await, + Err("vendor_hash_mismatch".to_string()), + "the blob stripped is not what the inventory pins" + ); + tokio::fs::write( + dir.join("package.json"), + "{\"name\":\"a\",\"main\":\"m.js\"}", + ) + .await + .unwrap(); + ent.artifact.file_inventory = Some(BTreeMap::from([( + "package.json".to_string(), + hex::encode(sha2::Sha256::digest(b"{\"name\":\"a\",\"main\":\"m.js\"}")), + )])); + assert_eq!(verify_vendored_patch_record(root, &ent, &rec).await, Ok(())); + } + #[tokio::test] async fn unknown_npm_flavor_is_never_judged_by_this_builds_layout() { let tmp = tempfile::tempdir().unwrap(); @@ -1454,11 +1696,11 @@ mod tests { .unwrap(); let rec = record(UUID, "package/index.js"); let mut ent = entry("npm", UUID, &rel); - ent.flavor = Some("vlt".into()); + ent.flavor = Some("future-pm".into()); assert_eq!( check_vendored_artifact(root, &ent, &rec).await, ArtifactHealth::UnknownFlavor { - flavor: "vlt".into() + flavor: "future-pm".into() } ); ent.flavor = Some("bun".into()); diff --git a/crates/socket-patch-core/src/vendor/vlt_lock.rs b/crates/socket-patch-core/src/vendor/vlt_lock.rs new file mode 100644 index 00000000..f1ffaed9 --- /dev/null +++ b/crates/socket-patch-core/src/vendor/vlt_lock.rs @@ -0,0 +1,2399 @@ +//! vlt vendor backend: `vlt-lock.json` + importer `package.json` surgery +//! for a direct dependency (DESIGN §4.5). +//! +//! The target's default-registry node becomes a `file` node naming the +//! directory artifact ([`super::npm_dir`]); its importer edges and the +//! importers' package.json specs move to `file:`, and its own outgoing edges are re-keyed to the new DepID. +//! Every other line of the lock stays byte-identical, and the moved entries +//! are placed where vlt's own serializer puts them (§4.5.4), so `vlt ci` +//! keeps the lock byte-stable. +//! +//! Transitive targets are refused: vlt re-resolves a non-importer edge to a +//! `file` node away on the next `install`, `update` or workspace edit, and +//! nothing tells the user. + +use std::cmp::Ordering; +use std::collections::{BTreeMap, BTreeSet}; +use std::path::Path; + +use serde_json::Value; + +use crate::constants::npm_family::VLT_LOCK; +use crate::constants::SOCKET_DIR; +use crate::manifest::schema::PatchRecord; +use crate::patch::apply::PatchSources; +use crate::patch::path_safety::is_safe_multi_segment; +use crate::utils::fs::{atomic_write_bytes_preserving_mode, read_regular_to_string}; +use crate::utils::socket_dir::remove_tree_and_prune; + +use super::common::{already_patched_result, done, refused}; +use super::npm_common::{done_failure_unstage, guard_coordinates, guard_revert_uuid_dir}; +use super::npm_dir::{dependency_token, replace_dependency_token, stage_patch_dir, SpanError}; +use super::state::{ + load_state, write_marker_or_warn, VendorArtifact, VendorEntry, VendorMarker, WiringAction, + WiringRecord, +}; +use super::vlt_lock_text::{ + edges_block, entry_text, file_dep_id, is_default_registry, is_importer_dep_id, nodes_block, + parse_edge_entry_text, parse_edge_line, parse_node_entry_text, parse_node_line, + parse_vendored_dir_path, render_entry_line, render_tuple_with_slots, sniff_lock, split_dep_id, + split_lines, vendored_dir_rel, vlt_collate, vlt_edge_cmp, DepIdEra, DepIdKind, LockSniff, + ParsedLock, SectionSpan, +}; +use super::{RevertOpts, RevertOutcome, VendorOutcome, VendorWarning}; + +/// The flavor string vlt entries record. +pub const FLAVOR: &str = "vlt"; + +const KIND_PKG_DEP: &str = "vlt_pkg_dep"; +const KIND_LOCK_NODE: &str = "vlt_lock_node"; +const KIND_LOCK_EDGE: &str = "vlt_lock_edge"; + +const PACKAGE_JSON: &str = "package.json"; +const UNSUPPORTED: &str = "vendor_lock_entry_unsupported"; +const OUT_OF_SYNC: &str = "vendor_vlt_lock_out_of_sync"; +const NOT_CANONICAL: &str = + "vlt-lock.json is not in vlt's canonical layout; re-save it with `vlt install`"; + +/// A refusal: a stable code and its detail. +pub type Refusal = (&'static str, String); + +/// DESIGN §4.1 lock sniff: a BOM-less JSON object with `lockfileVersion` 0 +/// or 1. The `Err` detail goes with `vendor_lockfile_version_unsupported`. +pub(crate) fn sniff_vendor_lock(text: &str) -> Result { + match sniff_lock(text) { + LockSniff::Readable(lock) if lock.version.is_some() => Ok(lock), + LockSniff::Readable(_) => Err( + "vlt-lock.json has no lockfileVersion (vlt ≤ 0.0.0-18); re-lock with vlt ≥ 1.0.0" + .to_string(), + ), + LockSniff::UnsupportedVersion(raw) => Err(format!( + "vlt-lock.json has lockfileVersion {raw}; update socket-patch" + )), + LockSniff::Bom => Err( + "vlt-lock.json starts with a byte-order mark; re-save vlt-lock.json with `vlt install`" + .to_string(), + ), + LockSniff::NotJsonObject => Err( + "vlt-lock.json is not a JSON object; re-save vlt-lock.json with `vlt install`" + .to_string(), + ), + } +} + +// ── lock document ──────────────────────────────────────────────────────── + +/// One node or edge line: key, raw value, and its own `\r`. +#[derive(Debug, Clone, PartialEq, Eq)] +struct Entry { + key: String, + value: String, + cr: bool, +} + +impl Entry { + fn text(&self) -> String { + entry_text(&self.key, &self.value) + } + + fn from_text(text: &str, cr: bool, node: bool) -> Option { + let (key, value) = if node { + let entry = parse_node_entry_text(text)?; + (entry.key.to_string(), entry.tuple.to_string()) + } else { + let entry = parse_edge_entry_text(text)?; + (entry.key.to_string(), entry.raw_value.to_string()) + }; + Some(Entry { key, value, cr }) + } + + fn edge_from(&self) -> &str { + self.key.split_once(' ').map_or("", |(from, _)| from) + } + + fn edge_dep(&self) -> &str { + self.key.split_once(' ').map_or("", |(_, dep)| dep) + } +} + +#[derive(Debug, Clone)] +struct Block { + span: Option, + entries: Vec, +} + +/// A lock in vlt's canonical one-entry-per-line layout. +struct LockDoc { + lines: Vec, + parsed: ParsedLock, + era: DepIdEra, + nodes: Block, + edges: Block, +} + +fn parse_block( + lines: &[&str], + span: Option, + expected: usize, + node: bool, +) -> Option { + let Some(span) = span else { + return (expected == 0).then(|| Block { + span: None, + entries: Vec::new(), + }); + }; + let range = span.entry_lines(); + let last = range.end.checked_sub(1); + let mut entries = Vec::new(); + for i in range { + let (key, value, comma, cr) = if node { + let line = parse_node_line(lines[i])?; + ( + line.entry.key.to_string(), + line.entry.tuple.to_string(), + line.comma, + line.cr, + ) + } else { + let line = parse_edge_line(lines[i])?; + ( + line.entry.key.to_string(), + line.entry.raw_value.to_string(), + line.comma, + line.cr, + ) + }; + if comma == (Some(i) == last) { + return None; + } + entries.push(Entry { key, value, cr }); + } + let unique: BTreeSet<&str> = entries.iter().map(|e| e.key.as_str()).collect(); + (entries.len() == expected && unique.len() == expected).then_some(Block { + span: Some(span), + entries, + }) +} + +fn parse_doc(text: &str) -> Result { + let parsed = sniff_vendor_lock(text).map_err(|d| ("vendor_lockfile_version_unsupported", d))?; + let lines = split_lines(text); + let not_canonical = || { + ( + "vendor_lockfile_version_unsupported", + NOT_CANONICAL.to_string(), + ) + }; + let node_count = parsed.nodes().map_or(0, |n| n.len()); + let edge_count = parsed.edges().map_or(0, |e| e.len()); + let nodes = + parse_block(&lines, nodes_block(&lines), node_count, true).ok_or_else(not_canonical)?; + let edges = + parse_block(&lines, edges_block(&lines), edge_count, false).ok_or_else(not_canonical)?; + Ok(LockDoc { + era: parsed.new_id_era(), + lines: lines.into_iter().map(str::to_string).collect(), + parsed, + nodes, + edges, + }) +} + +fn render_block(out: &mut Vec, header: &str, entries: &[Entry], inline_comma: bool) { + if entries.is_empty() { + out.push(header.to_string()); + return; + } + let open = header.trim_end_matches(['\r']).trim_end_matches(','); + let cr = header.ends_with('\r'); + out.push(format!( + "{}{}", + open.trim_end_matches('}'), + if cr { "\r" } else { "" } + )); + let last = entries.len() - 1; + for (i, e) in entries.iter().enumerate() { + out.push(render_entry_line(&e.text(), i != last, e.cr)); + } + out.push(format!( + " }}{}{}", + if inline_comma { "," } else { "" }, + if cr { "\r" } else { "" } + )); +} + +impl LockDoc { + fn render(&self) -> String { + let mut spans: Vec<(SectionSpan, &Block)> = [&self.nodes, &self.edges] + .into_iter() + .filter_map(|b| b.span.map(|s| (s, b))) + .collect(); + spans.sort_by_key(|(s, _)| match s { + SectionSpan::Inline { line } => *line, + SectionSpan::Block { open, .. } => *open, + }); + let mut out = Vec::with_capacity(self.lines.len() + 4); + let mut i = 0; + for (span, block) in spans { + match span { + SectionSpan::Block { open, close } => { + out.extend(self.lines[i..=open].iter().cloned()); + let last = block.entries.len().saturating_sub(1); + for (j, e) in block.entries.iter().enumerate() { + out.push(render_entry_line(&e.text(), j != last, e.cr)); + } + i = close; + } + SectionSpan::Inline { line } => { + out.extend(self.lines[i..line].iter().cloned()); + let header = &self.lines[line]; + let comma = header.trim_end_matches('\r').ends_with(','); + render_block(&mut out, header, &block.entries, comma); + i = line + 1; + } + } + } + out.extend(self.lines[i..].iter().cloned()); + out.join("\n") + } + + fn options(&self) -> Option<&serde_json::Map> { + self.parsed.options() + } + + fn legacy_default_keys(&self) -> bool { + self.nodes.entries.iter().any(|e| e.key.starts_with("··")) + } +} + +// ── placement (§4.5.4) ─────────────────────────────────────────────────── + +fn node_cmp(a: &Entry, b: &Entry) -> Option { + vlt_collate(&a.key, &b.key) +} + +fn edge_cmp(a: &Entry, b: &Entry) -> Option { + let a_text = a.text(); + let b_text = b.text(); + let a = parse_edge_entry_text(&a_text)?; + let b = parse_edge_entry_text(&b_text)?; + vlt_edge_cmp(a.sort_key(), b.sort_key()) +} + +/// Replace the entries at the touched indices with their replacements: +/// untouched entries keep their order, and each replacement (in the given +/// order) goes before the first entry it sorts below. When any comparison +/// is outside the collation table, every replacement stays in place. +fn place( + entries: &[Entry], + touched: &[(usize, Entry)], + cmp: fn(&Entry, &Entry) -> Option, +) -> Vec { + let touched_at: BTreeSet = touched.iter().map(|(i, _)| *i).collect(); + let mut list: Vec = entries + .iter() + .enumerate() + .filter(|(i, _)| !touched_at.contains(i)) + .map(|(_, e)| e.clone()) + .collect(); + for (_, new) in touched { + let mut at = list.len(); + for (i, e) in list.iter().enumerate() { + match cmp(new, e) { + Some(Ordering::Less) => { + at = i; + break; + } + Some(_) => {} + None => { + let mut in_place = entries.to_vec(); + for (i, replacement) in touched { + in_place[*i] = replacement.clone(); + } + return in_place; + } + } + } + list.insert(at, new.clone()); + } + list +} + +// ── target analysis (§4.5.1) ───────────────────────────────────────────── + +/// An importer edge into the target. +#[derive(Debug, Clone)] +struct ImporterEdge { + index: usize, + edge_type: String, + spec: String, + dep: String, + /// `""` for the root, else the workspace path. + dir: String, + field: &'static str, +} + +impl ImporterEdge { + fn pkg_rel(&self) -> String { + if self.dir.is_empty() { + PACKAGE_JSON.to_string() + } else { + format!("{}/{PACKAGE_JSON}", self.dir) + } + } +} + +/// The target's current node and its importer edges. +#[derive(Debug, Clone)] +struct Target { + index: usize, + key: String, + /// The node is already one of socket-patch's vendored dirs. + ours: bool, + importers: Vec, +} + +fn field_for(edge_type: &str) -> Option<&'static str> { + match edge_type { + "prod" => Some("dependencies"), + "dev" => Some("devDependencies"), + "optional" => Some("optionalDependencies"), + _ => None, + } +} + +fn importer_dir(from: &str) -> Option { + if from == "file~_d" || from == "file·." { + return Some(String::new()); + } + let dep_id = split_dep_id(from)?; + (dep_id.kind == DepIdKind::Workspace && is_safe_multi_segment(&dep_id.first)) + .then_some(dep_id.first) +} + +fn find_target(doc: &LockDoc, name: &str, version: &str) -> Result { + let options = doc.options(); + let mut defaults = Vec::new(); + let mut foreign = Vec::new(); + let mut ours = Vec::new(); + for (i, e) in doc.nodes.entries.iter().enumerate() { + let Some(dep_id) = split_dep_id(&e.key) else { + continue; + }; + match dep_id.kind { + DepIdKind::Registry if dep_id.registry_identity() == Some((name, version)) => { + if is_default_registry(&dep_id.first, options) { + defaults.push((i, dep_id.extra.is_some())); + } else { + foreign.push(i); + } + } + DepIdKind::File => { + let named = parse_node_entry_text(&e.text()) + .and_then(|n| n.name()) + .is_some_and(|n| n == name); + let vendored = parse_vendored_dir_path(&dep_id.first) + .is_some_and(|p| p.name == name && p.version == version); + if named && vendored { + ours.push(i); + } + } + _ => {} + } + } + let key = |i: usize| doc.nodes.entries[i].key.clone(); + if let Some(&i) = foreign.first() { + return Err(( + UNSUPPORTED, + format!( + "vlt-lock.json resolves {name}@{version} as {}, which is not from vlt's default \ + registry; vendoring rewires only default-registry packages", + key(i) + ), + )); + } + let (index, is_ours) = match (defaults.as_slice(), ours.as_slice()) { + ([(i, false)], []) => (*i, false), + ([], [i]) => (*i, true), + ([], []) => { + return Err(( + "vendor_lock_entry_not_found", + format!( + "vlt-lock.json has no default-registry entry for {name}@{version}; run `vlt \ + install` first" + ), + )) + } + _ => { + let ids: Vec = defaults + .iter() + .map(|(i, _)| key(*i)) + .chain(ours.iter().map(|i| key(*i))) + .collect(); + return Err(( + UNSUPPORTED, + format!( + "vlt-lock.json holds {} ({}): peer/modifier variants; use --mode hosted", + if ids.len() == 1 { + "a variant instance".to_string() + } else { + format!("{} instances", ids.len()) + }, + ids.join(", ") + ), + )); + } + }; + let target_key = key(index); + let mut importers = Vec::new(); + for (i, e) in doc.edges.entries.iter().enumerate() { + let text = e.text(); + let Some(edge) = parse_edge_entry_text(&text) else { + continue; + }; + if edge.target() != target_key { + continue; + } + if !is_importer_dep_id(edge.from()) { + return Err(( + "vendor_vlt_transitive_unsupported", + format!( + "{name}@{version} is a transitive dependency ({} depends on it); vendored \ + mode rewires only direct dependencies of the root or a workspace — use \ + --mode hosted", + edge.from() + ), + )); + } + let Some(field) = field_for(edge.edge_type()) else { + return Err(( + UNSUPPORTED, + format!( + "`{}` reaches {name}@{version} through a {} peer edge; use --mode hosted", + edge.key, + edge.edge_type() + ), + )); + }; + let Some(dir) = importer_dir(edge.from()) else { + return Err(( + UNSUPPORTED, + format!( + "vlt-lock.json importer `{}` is not a safe workspace path", + edge.from() + ), + )); + }; + importers.push(ImporterEdge { + index: i, + edge_type: edge.edge_type().to_string(), + spec: edge.spec().to_string(), + dep: edge.dep_name().to_string(), + dir, + field, + }); + } + if importers.is_empty() { + return Err(( + UNSUPPORTED, + format!("no root or workspace importer depends on {name}@{version} in vlt-lock.json"), + )); + } + Ok(Target { + index, + key: target_key, + ours: is_ours, + importers, + }) +} + +/// `posix_relative(from_dir, to)` for project-relative forward-slashed +/// paths (`""` is the root). +fn posix_relative(from_dir: &str, to: &str) -> String { + let from: Vec<&str> = from_dir.split('/').filter(|s| !s.is_empty()).collect(); + let to: Vec<&str> = to.split('/').filter(|s| !s.is_empty()).collect(); + let common = from.iter().zip(&to).take_while(|(a, b)| a == b).count(); + let mut parts: Vec<&str> = vec![".."; from.len() - common]; + parts.extend(&to[common..]); + parts.join("/") +} + +/// The `file:` spec an importer at `dir` declares for `rel`. +fn importer_spec(dir: &str, rel: &str) -> String { + let r = posix_relative(dir, rel); + if r.starts_with("../") { + format!("file:{r}") + } else { + format!("file:./{r}") + } +} + +fn json_string(s: &str) -> String { + serde_json::to_string(s).expect("a str serializes to JSON infallibly") +} + +/// DESIGN §4.5.1 declaration checks on the importers' package.json files. +fn check_declarations( + target: &Target, + pkgs: &BTreeMap, + rel: &str, +) -> Result<(), Refusal> { + for edge in &target.importers { + let pkg_rel = edge.pkg_rel(); + let text = pkgs.get(&pkg_rel).ok_or_else(|| { + ( + OUT_OF_SYNC, + format!("{pkg_rel} is missing; run `vlt install` first"), + ) + })?; + let value: Value = serde_json::from_str(crate::package_json::detect::strip_bom(text)) + .map_err(|_| (OUT_OF_SYNC, format!("{pkg_rel} is not valid JSON")))?; + let declared = ["dependencies", "devDependencies", "optionalDependencies"] + .iter() + .filter(|f| value.get(**f).and_then(|t| t.get(&edge.dep)).is_some()) + .count(); + if declared > 1 { + return Err(( + UNSUPPORTED, + format!( + "{} is declared in multiple dependency fields of {pkg_rel}; keep it in one \ + and re-run `vlt install`", + edge.dep + ), + )); + } + let current = value + .get(edge.field) + .and_then(|t| t.get(&edge.dep)) + .and_then(Value::as_str); + let ours = importer_spec(&edge.dir, rel); + if current != Some(edge.spec.as_str()) && current != Some(ours.as_str()) { + return Err(( + OUT_OF_SYNC, + format!( + "{pkg_rel} declares {}.{} as {}, but vlt-lock.json locks `{}`; run `vlt \ + install` first", + edge.field, + edge.dep, + current.map_or_else(|| "nothing".to_string(), |c| format!("`{c}`")), + edge.spec + ), + )); + } + match dependency_token(text, edge.field, &edge.dep) { + Ok(_) => {} + Err(SpanError::Duplicate(what)) => { + return Err(( + UNSUPPORTED, + format!("{pkg_rel} declares {what} more than once"), + )) + } + Err(_) => { + return Err(( + OUT_OF_SYNC, + format!("{pkg_rel} has no string {}.{}", edge.field, edge.dep), + )) + } + } + } + Ok(()) +} + +async fn read_importer_pkgs(project_root: &Path, target: &Target) -> BTreeMap { + let mut pkgs = BTreeMap::new(); + for edge in &target.importers { + let rel = edge.pkg_rel(); + if pkgs.contains_key(&rel) { + continue; + } + if let Ok(text) = read_regular_to_string(&project_root.join(&rel)).await { + pkgs.insert(rel, text); + } + } + pkgs +} + +/// Everything the vendored wiring decides before touching the artifact: +/// the parsed lock, the target, and the importer package.json texts. +struct Analysis { + doc: LockDoc, + target: Target, + pkgs: BTreeMap, + rel: String, +} + +async fn analyze( + project_root: &Path, + name: &str, + version: &str, + uuid: &str, +) -> Result { + let text = read_regular_to_string(&project_root.join(VLT_LOCK)) + .await + .map_err(|e| { + ( + "vendor_lockfile_missing", + format!("cannot read {VLT_LOCK}: {e} — run `vlt install` first"), + ) + })?; + let doc = parse_doc(&text)?; + let target = find_target(&doc, name, version)?; + let pkgs = read_importer_pkgs(project_root, &target).await; + let rel = vendored_dir_rel(uuid, name, version); + check_declarations(&target, &pkgs, &rel)?; + Ok(Analysis { + doc, + target, + pkgs, + rel, + }) +} + +/// The read-only vendored-mode refusals vlt can decide before any write: +/// the lock sniff and layout, the target analysis with its declaration +/// checks, and the installed store copy's `bundleDependencies` and +/// duplicate `devDependencies` (DESIGN §4.6, core part). +pub async fn vlt_vendor_preflight( + project_root: &Path, + purl: &str, + uuid: &str, +) -> Result<(), Refusal> { + let Some((name, version)) = super::npm_common::parse_npm_purl(purl) else { + return Err(( + "unsafe_coordinates", + format!("cannot parse an npm name@version out of `{purl}`"), + )); + }; + let analysis = analyze(project_root, &name, &version, uuid).await?; + if analysis.target.ours { + return Ok(()); + } + let store = project_root + .join(crate::constants::npm_family::VLT_STORE_DIR) + .join(&analysis.target.key) + .join("node_modules") + .join(&name) + .join(PACKAGE_JSON); + if let Ok(text) = read_regular_to_string(&store).await { + if let Ok(pkg) = + serde_json::from_str::(crate::package_json::detect::strip_bom(&text)) + { + if super::npm_common::declares_bundled_deps(&pkg) { + return Err(( + "vendor_bundled_deps_unsupported", + format!("{name}@{version} declares bundleDependencies; vendoring would drop its bundled node_modules and break installs"), + )); + } + } + if let Err(SpanError::Duplicate(_)) = super::npm_dir::strip_dev_dependencies(&text) { + return Err(( + UNSUPPORTED, + format!("{name}@{version}'s package.json declares duplicate devDependencies"), + )); + } + } + Ok(()) +} + +// ── wiring ─────────────────────────────────────────────────────────────── + +/// A planned edit: the record plus the in-memory change behind it. +struct Wiring { + records: Vec, + pkgs: BTreeMap, + lock: Option, +} + +fn wiring_record( + file: &str, + kind: &str, + key: &str, + original: Option, + new: String, +) -> WiringRecord { + WiringRecord { + file: file.to_string(), + kind: kind.to_string(), + action: WiringAction::Rewritten, + key: Some(key.to_string()), + original: original.map(Value::String), + new: Some(Value::String(new)), + } +} + +/// The prior entry's `original` for a record of this file and kind whose +/// key satisfies `matches`. +fn prior_original<'p>( + prior: Option<&'p VendorEntry>, + file: &str, + kind: &str, + matches: impl Fn(&str) -> bool, +) -> Option<&'p WiringRecord> { + prior? + .wiring + .iter() + .find(|r| r.file == file && r.kind == kind && r.key.as_deref().is_some_and(&matches)) +} + +fn original_text(rec: Option<&WiringRecord>) -> Option { + rec.and_then(|r| r.original.as_ref()) + .and_then(Value::as_str) + .map(str::to_string) +} + +/// DESIGN §4.5.2–§4.5.4: the records and the new surfaces, or `None` when +/// every surface already names `rel`. +fn plan_wiring( + analysis: &Analysis, + prior: Option<&VendorEntry>, +) -> Result, Refusal> { + let Analysis { + doc, + target, + pkgs, + rel, + } = analysis; + let file_id = file_dep_id(rel, doc.era); + let node = &doc.nodes.entries[target.index]; + let node_text = node.text(); + let node_entry = parse_node_entry_text(&node_text).ok_or_else(|| { + ( + "vendor_lockfile_version_unsupported", + NOT_CANONICAL.to_string(), + ) + })?; + let file_tuple = + render_tuple_with_slots(&node_entry.elems, Some("null"), Some(&json_string(rel))); + let new_node = Entry { + key: file_id.clone(), + value: file_tuple, + cr: node.cr, + }; + + let mut records = Vec::new(); + let mut new_pkgs = BTreeMap::new(); + let mut importers = target.importers.clone(); + importers.sort_by(|a, b| (&a.dir, a.field, &a.dep).cmp(&(&b.dir, b.field, &b.dep))); + for edge in &importers { + let pkg_rel = edge.pkg_rel(); + let text = new_pkgs + .get(&pkg_rel) + .or_else(|| pkgs.get(&pkg_rel)) + .cloned() + .unwrap_or_default(); + let current = dependency_token(&text, edge.field, &edge.dep).map_err(|_| { + ( + OUT_OF_SYNC, + format!("{pkg_rel} has no string {}.{}", edge.field, edge.dep), + ) + })?; + let new = json_string(&importer_spec(&edge.dir, rel)); + if current == new { + continue; + } + let key = format!("{}/{}", edge.field, edge.dep); + let original = if target.ours { + original_text(prior_original(prior, &pkg_rel, KIND_PKG_DEP, |k| k == key)) + } else { + Some(current) + }; + let edited = replace_dependency_token(&text, edge.field, &edge.dep, &new) + .map_err(|_| (OUT_OF_SYNC, format!("cannot edit {pkg_rel}")))?; + new_pkgs.insert(pkg_rel.clone(), edited); + records.push(wiring_record(&pkg_rel, KIND_PKG_DEP, &key, original, new)); + } + + let mut node_touch = Vec::new(); + if *node != new_node { + let (key, original) = if target.ours { + let prior_rec = prior_original(prior, VLT_LOCK, KIND_LOCK_NODE, |_| true); + ( + prior_rec + .and_then(|r| r.key.clone()) + .unwrap_or_else(|| node.key.clone()), + original_text(prior_rec), + ) + } else { + (node.key.clone(), Some(node.text())) + }; + records.push(wiring_record( + VLT_LOCK, + KIND_LOCK_NODE, + &key, + original, + new_node.text(), + )); + node_touch.push((target.index, new_node)); + } + + let mut edge_touch = Vec::new(); + let mut edge_order: Vec = target.importers.iter().map(|e| e.index).collect(); + edge_order.sort_unstable(); + for index in edge_order { + let edge = target + .importers + .iter() + .find(|e| e.index == index) + .expect("importer index"); + let current = &doc.edges.entries[index]; + let spec = importer_spec(&edge.dir, rel); + let new = Entry { + key: current.key.clone(), + value: json_string(&format!("{} {spec} {file_id}", edge.edge_type)), + cr: current.cr, + }; + if *current == new { + continue; + } + let original = if target.ours { + original_text(prior_original(prior, VLT_LOCK, KIND_LOCK_EDGE, |k| { + k == current.key + })) + } else { + Some(current.text()) + }; + records.push(wiring_record( + VLT_LOCK, + KIND_LOCK_EDGE, + ¤t.key, + original, + new.text(), + )); + edge_touch.push((index, new)); + } + for (index, current) in doc.edges.entries.iter().enumerate() { + if current.edge_from() != target.key || target.key == file_id { + continue; + } + let new = Entry { + key: format!("{file_id} {}", current.edge_dep()), + value: current.value.clone(), + cr: current.cr, + }; + let (key, original) = if target.ours { + let dep = current.edge_dep().to_string(); + let prior_rec = prior_original(prior, VLT_LOCK, KIND_LOCK_EDGE, |k| { + k.split_once(' ') + .is_some_and(|(from, d)| d == dep && !is_importer_dep_id(from)) + }); + ( + prior_rec + .and_then(|r| r.key.clone()) + .unwrap_or_else(|| current.key.clone()), + original_text(prior_rec), + ) + } else { + (current.key.clone(), Some(current.text())) + }; + records.push(wiring_record( + VLT_LOCK, + KIND_LOCK_EDGE, + &key, + original, + new.text(), + )); + edge_touch.push((index, new)); + } + + if records.is_empty() { + return Ok(None); + } + let lock = if node_touch.is_empty() && edge_touch.is_empty() { + None + } else { + let next = LockDoc { + lines: doc.lines.clone(), + parsed: doc.parsed.clone(), + era: doc.era, + nodes: Block { + span: doc.nodes.span, + entries: place(&doc.nodes.entries, &node_touch, node_cmp), + }, + edges: Block { + span: doc.edges.span, + entries: place(&doc.edges.entries, &edge_touch, edge_cmp), + }, + }; + Some(next.render()) + }; + Ok(Some(Wiring { + records, + pkgs: new_pkgs, + lock, + })) +} + +/// Write every changed package.json (path order), then the lock; a lock +/// write failure puts the package.json files back. +async fn commit( + project_root: &Path, + wiring: &Wiring, + originals: &BTreeMap, +) -> Result<(), String> { + let mut written: Vec<&String> = Vec::new(); + for (rel, text) in &wiring.pkgs { + if let Err(e) = + atomic_write_bytes_preserving_mode(&project_root.join(rel), text.as_bytes()).await + { + unwind(project_root, &written, originals).await; + return Err(format!("cannot write {rel}: {e}")); + } + written.push(rel); + } + if let Some(lock) = &wiring.lock { + if let Err(e) = + atomic_write_bytes_preserving_mode(&project_root.join(VLT_LOCK), lock.as_bytes()).await + { + unwind(project_root, &written, originals).await; + return Err(format!( + "cannot write {VLT_LOCK}: {e} (package.json files restored to their original bytes)" + )); + } + } + Ok(()) +} + +async fn unwind(project_root: &Path, written: &[&String], originals: &BTreeMap) { + for rel in written { + if let Some(text) = originals.get(*rel) { + let _ = + atomic_write_bytes_preserving_mode(&project_root.join(rel), text.as_bytes()).await; + } + } +} + +/// The ledger entry this project already has for `purl` under vlt. +async fn prior_vlt_entry(project_root: &Path, purl: &str) -> Option { + let state = load_state(project_root).await.ok()?; + state.entries.into_iter().find_map(|(key, entry)| { + (entry.ecosystem == "npm" + && entry.flavor.as_deref() == Some(FLAVOR) + && entry.covers_purl(&key, purl)) + .then_some(entry) + }) +} + +/// Vendor one installed npm package into a vlt project (see the module +/// doc). Same contract as the other npm backends: refuse-early / wire-last, +/// `entry` present iff `result.success` and not a dry run, and an in-sync +/// re-run synthesizes AlreadyPatched with no entry. +#[allow(clippy::too_many_arguments)] +pub(crate) async fn vendor_vlt( + purl: &str, + installed_dir: &Path, + project_root: &Path, + record: &PatchRecord, + sources: &PatchSources<'_>, + vendored_at: &str, + dry_run: bool, + force: bool, + service: Option<&super::VendorServiceConfig>, +) -> VendorOutcome { + let coords = match guard_coordinates(purl, record) { + Ok(coords) => coords, + Err(outcome) => return *outcome, + }; + let (name, version) = (coords.name.as_str(), coords.version.as_str()); + let analysis = match analyze(project_root, name, version, &record.uuid).await { + Ok(analysis) => analysis, + Err((code, detail)) => return refused(code, detail), + }; + let mut warnings = Vec::new(); + if analysis.doc.legacy_default_keys() { + warnings.push(VendorWarning::new( + "vendor_vlt_legacy_lockfile", + "vlt-lock.json was written by vlt 0.0.0-19 … 1.0.0-rc.8 (`··` ids); those releases \ + install the vendored lock but fail if it is deleted and re-created — upgrade vlt", + )); + } + let prior = prior_vlt_entry(project_root, purl).await; + let wiring = match plan_wiring(&analysis, prior.as_ref()) { + Ok(wiring) => wiring, + Err((code, detail)) => return refused(code, detail), + }; + + let (staged, result) = match stage_patch_dir( + purl, + installed_dir, + project_root, + record, + sources, + dry_run, + force, + &mut warnings, + service, + ) + .await + { + Ok(pair) => pair, + Err(outcome) => return *outcome, + }; + let Some(staged) = staged else { + return done(result, None, warnings); + }; + if staged.staged_pkg_json.is_some() { + warnings.push(VendorWarning::new( + "vendor_dep_manifest_stale", + format!( + "the patch rewrites {name}@{version}'s package.json; vlt-lock.json keeps the \ + node's recorded dependency edges — if the patch changed dependency ranges, run \ + `vlt install` to re-resolve them" + ), + )); + } + let entry_for = |wiring: Vec| VendorEntry { + ecosystem: "npm".to_string(), + base_purl: coords.base_purl.clone(), + uuid: record.uuid.clone(), + artifact: VendorArtifact { + path: staged.rel_dir.clone(), + sha256: String::new(), + size: None, + platform_locked: None, + file_inventory: Some(staged.inventory.clone()), + }, + wiring, + lock: None, + took_over_go_patches: false, + detached: false, + record: None, + flavor: Some(FLAVOR.to_string()), + uv: None, + pnpm: None, + poetry: None, + pdm: None, + pipenv: None, + }; + let marker = VendorMarker::new("npm", &coords.base_purl, record, vendored_at); + let uuid_dir = project_root.join(&coords.uuid_dir_rel); + + let Some(wiring) = wiring else { + if staged.reused { + let rel_abs = project_root.join(&staged.rel_dir); + return done( + already_patched_result(purl, &rel_abs, &record.files), + None, + warnings, + ); + } + warnings.push(VendorWarning::new( + "vendor_artifact_rebuilt", + format!( + "the committed vendored dir for {name}@{version} was missing or stale; rebuilt \ + at {} (vlt-lock.json and package.json untouched)", + staged.rel_dir + ), + )); + write_marker_or_warn(&uuid_dir, &marker, &mut warnings).await; + return done(result, Some(entry_for(Vec::new())), warnings); + }; + if let Err(e) = commit(project_root, &wiring, &analysis.pkgs).await { + return done_failure_unstage( + purl, + e, + project_root, + &coords.uuid_dir_rel, + staged.uuid_dir_preexisted, + ) + .await; + } + write_marker_or_warn(&uuid_dir, &marker, &mut warnings).await; + done(result, Some(entry_for(wiring.records)), warnings) +} + +/// Rewrite a vlt entry's `/.gitignore` and `/.gitattributes` +/// when absent or changed (DESIGN §4.8 health: neither is part of the +/// artifact, so repairing them is no rebuild). +pub async fn restore_vlt_uuid_metadata( + entry: &VendorEntry, + project_root: &Path, +) -> std::io::Result<()> { + let Some(uuid_dir) = super::path::vendor_uuid_dir_rel("npm", &entry.uuid) else { + return Err(std::io::Error::other(format!( + "`{}` is not a canonical patch uuid", + entry.uuid + ))); + }; + super::npm_dir::restore_uuid_metadata(&project_root.join(uuid_dir)).await +} + +// ── in use ─────────────────────────────────────────────────────────────── + +fn lock_has_file_node_under(text: &str, uuid: &str) -> Option { + let LockSniff::Readable(lock) = sniff_lock(text) else { + return None; + }; + let prefix = format!(".socket/vendor/npm/{uuid}/"); + Some(lock.nodes().is_some_and(|nodes| { + nodes.keys().any(|id| { + split_dep_id(id) + .is_some_and(|d| d.kind == DepIdKind::File && d.first.starts_with(&prefix)) + }) + })) +} + +/// Is this vlt-vendored entry still consumed? Structural: `true` iff some +/// `file` node's decoded path is under `.socket/vendor/npm//`. +/// `None` when the lock is missing or unreadable. +pub async fn vlt_entry_in_use(entry: &VendorEntry, project_root: &Path) -> Option { + let text = read_regular_to_string(&project_root.join(VLT_LOCK)) + .await + .ok()?; + lock_has_file_node_under(&text, &entry.uuid) +} + +// ── revert (§4.7) ──────────────────────────────────────────────────────── + +fn drifted(detail: impl Into) -> VendorWarning { + VendorWarning::new("vendor_lock_entry_drifted", detail.into()) +} + +async fn guard_unwired( + project_root: &Path, + entry: &VendorEntry, + uuid_dir_rel: &str, +) -> Option { + let clause = match vlt_entry_in_use(entry, project_root).await { + Some(false) => return None, + Some(true) => format!("{VLT_LOCK} still resolves through it"), + None => match tokio::fs::try_exists(project_root.join(VLT_LOCK)).await { + Ok(false) => return None, + _ => format!( + "{VLT_LOCK} exists but could not be read to prove it no longer references it" + ), + }, + }; + let detail = format!( + "refusing to remove {uuid_dir_rel}: the ledger entry records no pre-vendor wiring to \ + replay (it was likely reconstructed by `socket-patch repair`) and {clause} — deleting \ + the artifact would make every subsequent install fail; restore the registry \ + dependency in package.json, run `vlt install`, then re-run `vendor --revert`" + ); + Some(RevertOutcome { + success: false, + warnings: vec![VendorWarning::new( + "vendor_wiring_unknown_revert_blocked", + detail.clone(), + )], + error: Some(detail), + kept_artifact: false, + }) +} + +fn fragment(v: &Option) -> Option<&str> { + v.as_ref().and_then(Value::as_str) +} + +/// The recorded key's dependency field and name (`"/"`; the name may +/// itself contain `/`). +fn split_pkg_key(key: &str) -> Option<(&str, &str)> { + let (field, name) = key.split_once('/')?; + matches!( + field, + "dependencies" | "devDependencies" | "optionalDependencies" + ) + .then_some((field, name)) +} + +/// The package.json files a revert may write: the root one, and those of +/// workspace importers the live lock or the entry's own edge records name. +fn allowed_pkg_files(doc: Option<&LockDoc>, entry: &VendorEntry) -> BTreeSet { + let mut dirs: BTreeSet = BTreeSet::from([String::new()]); + let mut add = |from: &str| { + if let Some(dir) = importer_dir(from) { + dirs.insert(dir); + } + }; + if let Some(doc) = doc { + for e in &doc.edges.entries { + add(e.edge_from()); + } + } + for rec in entry.wiring.iter().filter(|r| r.kind == KIND_LOCK_EDGE) { + if let Some((from, _)) = rec.key.as_deref().and_then(|k| k.split_once(' ')) { + add(from); + } + } + dirs.into_iter() + .map(|d| { + if d.is_empty() { + PACKAGE_JSON.to_string() + } else { + format!("{d}/{PACKAGE_JSON}") + } + }) + .collect() +} + +/// The lock being reverted: each block's entries, restored in place, and +/// the indices restored so far (re-placed when rendering). +struct Staged { + nodes: Vec, + edges: Vec, + touched_nodes: Vec, + touched_edges: Vec, +} + +enum Step { + Applied, + AlreadyReverted, + Drift(String), +} + +fn slot_value(raw: Option<&str>) -> Option { + raw.filter(|s| *s != "null") + .and_then(|s| serde_json::from_str::(s).ok()) +} + +fn revert_node(staged: &mut Staged, rec: &WiringRecord) -> Step { + let (Some(new), Some(original)) = (fragment(&rec.new), fragment(&rec.original)) else { + return Step::Drift("the vlt_lock_node record has no pre-vendor original".into()); + }; + let (Some(new), Some(original)) = (parse_node_entry_text(new), parse_node_entry_text(original)) + else { + return Step::Drift("the vlt_lock_node record is not vlt node entry text".into()); + }; + if let Some(i) = staged.nodes.iter().position(|e| e.key == new.key) { + let current = staged.nodes[i].clone(); + let current_text = current.text(); + let Some(live) = parse_node_entry_text(¤t_text) else { + return Step::Drift(format!("{} is outside vlt's node grammar", new.key)); + }; + if live.slot(2) != Some("null") || slot_value(live.slot(3)) != slot_value(new.slot(3)) { + return Step::Drift(format!("{} drifted from the vendored wiring", new.key)); + } + let tuple = render_tuple_with_slots( + &live.elems, + original.slot(2).filter(|s| *s != "null"), + original.slot(3).filter(|s| *s != "null"), + ); + staged.nodes[i] = Entry { + key: original.key.to_string(), + value: tuple, + cr: current.cr, + }; + staged.touched_nodes.push(i); + return Step::Applied; + } + let restored = staged + .nodes + .iter() + .find(|e| e.key == original.key) + .and_then(|e| { + let text = e.text(); + parse_node_entry_text(&text) + .map(|live| (slot_value(live.slot(2)), slot_value(live.slot(3)))) + }); + if restored == Some((slot_value(original.slot(2)), slot_value(original.slot(3)))) { + Step::AlreadyReverted + } else { + Step::Drift(format!("vlt-lock.json no longer has {}", new.key)) + } +} + +fn revert_edge(staged: &mut Staged, rec: &WiringRecord) -> Step { + let (Some(new), Some(original)) = (fragment(&rec.new), fragment(&rec.original)) else { + return Step::Drift("the vlt_lock_edge record has no pre-vendor original".into()); + }; + let (Some(new), Some(original)) = ( + Entry::from_text(new, false, false), + Entry::from_text(original, false, false), + ) else { + return Step::Drift("the vlt_lock_edge record is not vlt edge entry text".into()); + }; + let find = |key: &str| staged.edges.iter().position(|e| e.key == key); + if is_importer_dep_id(original.edge_from()) { + return match find(&new.key) { + Some(i) if staged.edges[i].value == new.value => { + let cr = staged.edges[i].cr; + staged.edges[i] = Entry { cr, ..original }; + staged.touched_edges.push(i); + Step::Applied + } + Some(i) if staged.edges[i].value == original.value => Step::AlreadyReverted, + Some(_) => Step::Drift(format!( + "vlt-lock.json edge `{}` changed since vendoring", + new.key + )), + None => Step::Drift(format!( + "vlt-lock.json no longer has the edge `{}`", + new.key + )), + }; + } + match (find(&new.key), find(&original.key)) { + (Some(i), _) => { + staged.edges[i].key = original.key.clone(); + staged.touched_edges.push(i); + Step::Applied + } + (None, Some(_)) => Step::AlreadyReverted, + (None, None) => Step::Drift(format!( + "vlt-lock.json no longer has the edge `{}`", + new.key + )), + } +} + +fn revert_pkg(pkgs: &mut BTreeMap, rec: &WiringRecord) -> Step { + let (Some(new), Some(original)) = (fragment(&rec.new), fragment(&rec.original)) else { + return Step::Drift(format!( + "the {} record has no pre-vendor original", + rec.file + )); + }; + let Some((field, name)) = rec.key.as_deref().and_then(split_pkg_key) else { + return Step::Drift(format!("unknown vlt_pkg_dep key in {}", rec.file)); + }; + let Some(text) = pkgs.get(&rec.file) else { + return Step::Drift(format!("{} is missing", rec.file)); + }; + match dependency_token(text, field, name) { + Ok(token) if token == new => match replace_dependency_token(text, field, name, original) { + Ok(edited) => { + pkgs.insert(rec.file.clone(), edited); + Step::Applied + } + Err(_) => Step::Drift(format!("cannot edit {}", rec.file)), + }, + Ok(token) if token == original => Step::AlreadyReverted, + _ => Step::Drift(format!( + "{} {field}.{name} changed since vendoring", + rec.file + )), + } +} + +/// The DESIGN §4.7 cross-grammar drift detail: the user re-created the lock +/// under the other DepID grammar while package.json still names our dir. +fn cross_grammar_detail(entry: &VendorEntry, doc: &LockDoc) -> Option { + let node = entry.wiring.iter().find(|r| r.kind == KIND_LOCK_NODE)?; + let recorded = fragment(&node.new).and_then(parse_node_entry_text)?; + let recorded_era = split_dep_id(recorded.key)?.era; + if recorded_era == doc.era + || !doc + .nodes + .entries + .iter() + .any(|e| e.key != recorded.key && lock_key_under(&e.key, &entry.uuid)) + { + return None; + } + let pkg = entry.wiring.iter().find(|r| r.kind == KIND_PKG_DEP)?; + let (_, name) = pkg.key.as_deref().and_then(split_pkg_key)?; + let original = fragment(&pkg.original) + .and_then(|raw| serde_json::from_str::(raw).ok()) + .unwrap_or_default(); + Some(format!( + "vlt-lock.json was re-created by a different vlt lockfile grammar; set {name} in {} back \ + to {original}, run `vlt install`, then re-run `socket-patch vendor --revert` to remove \ + the artifact", + pkg.file + )) +} + +fn lock_key_under(key: &str, uuid: &str) -> bool { + let prefix = format!(".socket/vendor/npm/{uuid}/"); + split_dep_id(key).is_some_and(|d| d.kind == DepIdKind::File && d.first.starts_with(&prefix)) +} + +/// Undo one vlt-vendored package: every record through its §4.5.3 inverse, +/// all or nothing, then remove the artifact. +pub async fn revert_vlt_opts( + entry: &VendorEntry, + project_root: &Path, + opts: RevertOpts, +) -> RevertOutcome { + let RevertOpts { + dry_run, + keep_artifact, + } = opts; + let uuid_dir_rel = match guard_revert_uuid_dir(&entry.uuid) { + Ok(d) => d, + Err(outcome) => return outcome, + }; + if !keep_artifact && entry.wiring.is_empty() { + if let Some(blocked) = guard_unwired(project_root, entry, &uuid_dir_rel).await { + return blocked; + } + } + if dry_run { + return RevertOutcome::ok(); + } + let mut outcome = RevertOutcome::ok(); + + let lock_text = match read_regular_to_string(&project_root.join(VLT_LOCK)).await { + Ok(text) => Some(text), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => None, + Err(e) => return RevertOutcome::failed(format!("cannot read {VLT_LOCK}: {e}")), + }; + let doc = match lock_text.as_deref().map(parse_doc).transpose() { + Ok(doc) => doc, + Err((_, detail)) => return RevertOutcome::failed(detail), + }; + let allowed = allowed_pkg_files(doc.as_ref(), entry); + let mut pkgs: BTreeMap = BTreeMap::new(); + for rec in &entry.wiring { + let known = + rec.file == VLT_LOCK || (rec.kind == KIND_PKG_DEP && allowed.contains(&rec.file)); + if !known { + outcome.warnings.push(drifted(format!( + "ignoring wiring record `{}` for non-allowlisted file `{}`", + rec.kind, rec.file + ))); + continue; + } + if rec.kind == KIND_PKG_DEP && !pkgs.contains_key(&rec.file) { + match read_regular_to_string(&project_root.join(&rec.file)).await { + Ok(text) => { + pkgs.insert(rec.file.clone(), text); + } + Err(e) if e.kind() == std::io::ErrorKind::NotFound => {} + Err(e) => return RevertOutcome::failed(format!("cannot read {}: {e}", rec.file)), + } + } + } + if outcome.drift_skipped() { + outcome.keep_artifact(&uuid_dir_rel); + return outcome; + } + + let wired_lock = doc.as_ref().is_some_and(|d| { + d.nodes + .entries + .iter() + .any(|e| lock_key_under(&e.key, &entry.uuid)) + }); + let wired_pkg = entry + .wiring + .iter() + .filter(|r| r.kind == KIND_PKG_DEP) + .any(|rec| { + let (Some(new), Some((field, name))) = ( + fragment(&rec.new), + rec.key.as_deref().and_then(split_pkg_key), + ) else { + return false; + }; + pkgs.get(&rec.file) + .and_then(|t| dependency_token(t, field, name).ok()) + .is_some_and(|t| t == new) + }); + let already_reverted = !wired_lock && !wired_pkg; + + if !already_reverted { + let mut staged = doc.as_ref().map(|d| Staged { + nodes: d.nodes.entries.clone(), + edges: d.edges.entries.clone(), + touched_nodes: Vec::new(), + touched_edges: Vec::new(), + }); + let mut new_pkgs = pkgs.clone(); + let mut drift: Option = None; + for rec in entry.wiring.iter().rev() { + let step = match (rec.kind.as_str(), staged.as_mut()) { + (KIND_PKG_DEP, _) => revert_pkg(&mut new_pkgs, rec), + (KIND_LOCK_NODE, Some(staged)) => revert_node(staged, rec), + (KIND_LOCK_EDGE, Some(staged)) => revert_edge(staged, rec), + (KIND_LOCK_NODE | KIND_LOCK_EDGE, None) => { + Step::Drift(format!("{VLT_LOCK} no longer exists")) + } + (other, _) => Step::Drift(format!("unknown vlt wiring kind `{other}`")), + }; + if let Step::Drift(detail) = step { + drift = Some(detail); + break; + } + } + if let Some(detail) = drift { + let detail = doc + .as_ref() + .and_then(|d| cross_grammar_detail(entry, d)) + .unwrap_or(detail); + outcome.warnings.push(drifted(detail)); + outcome.keep_artifact(&uuid_dir_rel); + return outcome; + } + let new_lock = match (doc.as_ref(), staged) { + (Some(doc), Some(staged)) => Some(render_restored(doc, staged)), + _ => None, + }; + if let Some(lock) = new_lock.filter(|l| Some(l) != lock_text.as_ref()) { + if let Err(e) = + atomic_write_bytes_preserving_mode(&project_root.join(VLT_LOCK), lock.as_bytes()) + .await + { + return RevertOutcome::failed(format!("cannot write {VLT_LOCK}: {e}")); + } + } + for (rel, text) in &new_pkgs { + if pkgs.get(rel) == Some(text) { + continue; + } + if let Err(e) = + atomic_write_bytes_preserving_mode(&project_root.join(rel), text.as_bytes()).await + { + return RevertOutcome::failed(format!("cannot write {rel}: {e}")); + } + } + } + + if !keep_artifact { + let uuid_dir = project_root.join(&uuid_dir_rel); + if let Err(e) = remove_tree_and_prune(&uuid_dir, &project_root.join(SOCKET_DIR)).await { + return RevertOutcome::failed(format!("cannot remove {uuid_dir_rel}: {e}")); + } + } + outcome +} + +/// The lock with the restored entries re-placed (§4.5.4), in the forward +/// record order. +fn render_restored(doc: &LockDoc, staged: Staged) -> String { + let touched = |entries: &[Entry], order: &[usize]| -> Vec<(usize, Entry)> { + order + .iter() + .rev() + .map(|&i| (i, entries[i].clone())) + .collect() + }; + let nodes = place( + &staged.nodes, + &touched(&staged.nodes, &staged.touched_nodes), + node_cmp, + ); + let edges = place( + &staged.edges, + &touched(&staged.edges, &staged.touched_edges), + edge_cmp, + ); + LockDoc { + lines: doc.lines.clone(), + parsed: doc.parsed.clone(), + era: doc.era, + nodes: Block { + span: doc.nodes.span, + entries: nodes, + }, + edges: Block { + span: doc.edges.span, + entries: edges, + }, + } + .render() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::hash::git_sha256::compute_git_sha256_from_bytes; + use crate::manifest::schema::PatchFileInfo; + use crate::vendor::state::{save_state, VendorState}; + use std::collections::HashMap; + + const UUID: &str = "9f6b2c4e-1d3a-4f6b-8c2d-7e5a9b1c3d5f"; + const UUID2: &str = "0a1b2c3d-4e5f-4a7b-8c9d-0e1f2a3b4c5d"; + const ORIG: &[u8] = b"module.exports = 'orig';\n"; + const PATCHED: &[u8] = b"module.exports = 'patched';\n"; + const PURL: &str = "pkg:npm/left-pad@1.3.0"; + const REG: &str = "~npm~left-pad@1.3.0"; + const REG_NODE: &str = r#""~npm~left-pad@1.3.0": [0,"left-pad","sha512-REG==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"]"#; + + fn render(version: u8, nodes: &[&str], edges: &[&str]) -> String { + let block = |entries: &[&str]| { + if entries.is_empty() { + return "{}".to_string(); + } + let lines: Vec = entries + .iter() + .enumerate() + .map(|(i, e)| format!(" {e}{}", if i + 1 < entries.len() { "," } else { "" })) + .collect(); + format!("{{\n{}\n }}", lines.join("\n")) + }; + format!( + "{{\n \"lockfileVersion\": {version},\n \"options\": {{}},\n \"nodes\": {},\n \"edges\": {}\n}}\n", + block(nodes), + block(edges) + ) + } + + fn basic_lock() -> String { + render( + 1, + &[ + r#""~npm~a@1.0.0": [0,"a","sha512-A=="]"#, + REG_NODE, + r#""~npm~z@1.0.0": [0,"z","sha512-Z=="]"#, + ], + &[ + r#""file~_d a": "prod 1.0.0 ~npm~a@1.0.0""#, + r#""file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0""#, + r#""~npm~left-pad@1.3.0 z": "prod ^1.0.0 ~npm~z@1.0.0""#, + ], + ) + } + + const ROOT_PKG: &str = + "{\n \"name\": \"root\",\n \"dependencies\": {\n \"a\": \"1.0.0\",\n \"left-pad\": \"1.3.0\"\n }\n}\n"; + + struct Fx { + _tmp: tempfile::TempDir, + root: std::path::PathBuf, + installed: std::path::PathBuf, + blobs: std::path::PathBuf, + } + + async fn fx(lock: &str, pkgs: &[(&str, &str)]) -> Fx { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path().join("p"); + tokio::fs::create_dir_all(&root).await.unwrap(); + tokio::fs::write(root.join(VLT_LOCK), lock).await.unwrap(); + for (rel, text) in pkgs { + let path = root.join(rel); + tokio::fs::create_dir_all(path.parent().unwrap()) + .await + .unwrap(); + tokio::fs::write(path, text).await.unwrap(); + } + let installed = tmp.path().join("installed"); + tokio::fs::create_dir_all(&installed).await.unwrap(); + tokio::fs::write( + installed.join(PACKAGE_JSON), + "{\"name\":\"left-pad\",\"version\":\"1.3.0\",\"devDependencies\":{\"t\":\"1\"}}", + ) + .await + .unwrap(); + tokio::fs::write(installed.join("index.js"), ORIG) + .await + .unwrap(); + let blobs = tmp.path().join("blobs"); + tokio::fs::create_dir_all(&blobs).await.unwrap(); + tokio::fs::write(blobs.join(compute_git_sha256_from_bytes(PATCHED)), PATCHED) + .await + .unwrap(); + Fx { + _tmp: tmp, + root, + installed, + blobs, + } + } + + fn record(uuid: &str) -> PatchRecord { + let mut files = HashMap::new(); + files.insert( + "package/index.js".to_string(), + PatchFileInfo { + before_hash: compute_git_sha256_from_bytes(ORIG), + after_hash: compute_git_sha256_from_bytes(PATCHED), + }, + ); + PatchRecord { + uuid: uuid.to_string(), + exported_at: String::new(), + files, + vulnerabilities: HashMap::new(), + description: String::new(), + license: String::new(), + tier: String::new(), + } + } + + async fn run(fx: &Fx, uuid: &str, dry_run: bool) -> VendorOutcome { + let sources = PatchSources::blobs_only(&fx.blobs); + vendor_vlt( + PURL, + &fx.installed, + &fx.root, + &record(uuid), + &sources, + "t", + dry_run, + false, + None, + ) + .await + } + + fn refusal(outcome: VendorOutcome) -> (&'static str, String) { + match outcome { + VendorOutcome::Refused { code, detail } => (code, detail), + other => panic!("expected a refusal, got {other:?}"), + } + } + + fn entry_of(outcome: VendorOutcome) -> (VendorEntry, Vec) { + match outcome { + VendorOutcome::Done { + result, + entry: Some(entry), + warnings, + } if result.success => (entry, warnings), + other => panic!("expected a wired entry, got {other:?}"), + } + } + + async fn read(fx: &Fx, rel: &str) -> String { + tokio::fs::read_to_string(fx.root.join(rel)).await.unwrap() + } + + async fn persist(fx: &Fx, entry: &VendorEntry) { + let mut state = VendorState::new(); + state.entries.insert(PURL.into(), entry.clone()); + save_state(&fx.root, &state).await.unwrap(); + } + + fn entry(key: &str) -> Entry { + Entry { + key: key.into(), + value: "[0,\"x\"]".into(), + cr: false, + } + } + + #[test] + fn placement_inserts_among_untouched_entries_and_falls_back_in_place() { + let entries = vec![ + entry("~npm~b@1.0.0"), + entry("~npm~d@1.0.0"), + entry("~npm~f@1.0.0"), + ]; + let placed = place(&entries, &[(0, entry("~npm~e@1.0.0"))], node_cmp); + let keys: Vec<&str> = placed.iter().map(|e| e.key.as_str()).collect(); + assert_eq!(keys, ["~npm~d@1.0.0", "~npm~e@1.0.0", "~npm~f@1.0.0"]); + let placed = place(&entries, &[(1, entry("~npm~z@1.0.0"))], node_cmp); + assert_eq!(placed.last().unwrap().key, "~npm~z@1.0.0"); + + // `é` is outside the collation table: every replacement stays put. + let placed = place(&entries, &[(0, entry("file~é"))], node_cmp); + let keys: Vec<&str> = placed.iter().map(|e| e.key.as_str()).collect(); + assert_eq!(keys, ["file~é", "~npm~d@1.0.0", "~npm~f@1.0.0"]); + } + + #[test] + fn posix_relative_specs_from_every_importer() { + let rel = format!(".socket/vendor/npm/{UUID}/a-1.0.0/node_modules/a"); + assert_eq!(importer_spec("", &rel), format!("file:./{rel}")); + assert_eq!( + importer_spec("packages/a", &rel), + format!("file:../../{rel}") + ); + assert_eq!( + importer_spec(".socket", &rel), + format!("file:./{}", &rel[8..]) + ); + } + + #[tokio::test] + async fn wires_the_node_edges_and_package_json_in_vlt_order() { + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (entry, warnings) = entry_of(run(&fx, UUID, false).await); + assert!(warnings.is_empty(), "{warnings:?}"); + let rel = format!(".socket/vendor/npm/{UUID}/left-pad-1.3.0/node_modules/left-pad"); + let file_id = + format!("file~.socket+vendor+npm+{UUID}+left-pad-1.3.0+node__modules+left-pad"); + assert_eq!( + read(&fx, VLT_LOCK).await, + render( + 1, + &[ + r#""~npm~a@1.0.0": [0,"a","sha512-A=="]"#, + r#""~npm~z@1.0.0": [0,"z","sha512-Z=="]"#, + &format!(r#""{file_id}": [0,"left-pad",null,"{rel}"]"#), + ], + &[ + r#""file~_d a": "prod 1.0.0 ~npm~a@1.0.0""#, + &format!(r#""file~_d left-pad": "prod file:./{rel} {file_id}""#), + &format!(r#""{file_id} z": "prod ^1.0.0 ~npm~z@1.0.0""#), + ], + ) + ); + assert_eq!( + read(&fx, PACKAGE_JSON).await, + ROOT_PKG.replace( + "\"left-pad\": \"1.3.0\"", + &format!("\"left-pad\": \"file:./{rel}\"") + ) + ); + let kinds: Vec<(&str, Option<&str>)> = entry + .wiring + .iter() + .map(|r| (r.kind.as_str(), r.key.as_deref())) + .collect(); + assert_eq!( + kinds, + [ + (KIND_PKG_DEP, Some("dependencies/left-pad")), + (KIND_LOCK_NODE, Some(REG)), + (KIND_LOCK_EDGE, Some("file~_d left-pad")), + (KIND_LOCK_EDGE, Some("~npm~left-pad@1.3.0 z")), + ] + ); + assert_eq!(fragment(&entry.wiring[0].original), Some("\"1.3.0\"")); + assert_eq!(fragment(&entry.wiring[1].original), Some(REG_NODE)); + assert_eq!(entry.artifact.path, rel); + assert!(entry.artifact.sha256.is_empty()); + } + + #[tokio::test] + async fn dry_run_writes_nothing() { + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + match run(&fx, UUID, true).await { + VendorOutcome::Done { + result, + entry: None, + .. + } => assert!(result.success), + other => panic!("{other:?}"), + } + assert_eq!(read(&fx, VLT_LOCK).await, basic_lock()); + assert_eq!(read(&fx, PACKAGE_JSON).await, ROOT_PKG); + assert!(!fx.root.join(".socket").exists()); + } + + #[tokio::test] + async fn target_analysis_refusals() { + let url_node = r#""~npm~left-pad@1.3.0~peer.1": [0,"left-pad","sha512-P=="]"#; + let cases: Vec<(String, &str, &str, &str)> = vec![ + ( + render(1, &[r#""~acme~left-pad@1.3.0": [0,"left-pad","sha512-A=="]"#], &[r#""file~_d left-pad": "prod 1.3.0 ~acme~left-pad@1.3.0""#]), + ROOT_PKG, + UNSUPPORTED, + "not from vlt's default registry", + ), + ( + render(1, &[REG_NODE, url_node], &[r#""file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0""#]), + ROOT_PKG, + UNSUPPORTED, + "peer/modifier variants; use --mode hosted", + ), + ( + render(1, &[url_node], &[r#""file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0~peer.1""#]), + ROOT_PKG, + UNSUPPORTED, + "peer/modifier variants", + ), + ( + render(1, &[r#""~npm~a@1.0.0": [0,"a"]"#], &[]), + ROOT_PKG, + "vendor_lock_entry_not_found", + "no default-registry entry for left-pad@1.3.0", + ), + ( + render(1, &[r#""~npm~a@1.0.0": [0,"a"]"#, REG_NODE], &[r#""file~_d a": "prod 1.0.0 ~npm~a@1.0.0""#, r#""~npm~a@1.0.0 left-pad": "prod ^1 ~npm~left-pad@1.3.0""#]), + ROOT_PKG, + "vendor_vlt_transitive_unsupported", + "~npm~a@1.0.0 depends on it", + ), + ( + render(1, &[REG_NODE], &[r#""file~_d left-pad": "peer ^1 ~npm~left-pad@1.3.0""#]), + ROOT_PKG, + UNSUPPORTED, + "peer edge", + ), + ( + render(1, &[REG_NODE], &[r#""file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0""#]), + "{\"dependencies\":{\"left-pad\":\"1.3.0\"},\"devDependencies\":{\"left-pad\":\"1.3.0\"}}", + UNSUPPORTED, + "declared in multiple dependency fields", + ), + ( + render(1, &[REG_NODE], &[r#""file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0""#]), + "{\"dependencies\":{\"left-pad\":\"^1.3.0\"}}", + OUT_OF_SYNC, + "declares dependencies.left-pad as `^1.3.0`", + ), + ( + render(1, &[REG_NODE], &[r#""file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0""#]), + "{\"devDependencies\":{\"left-pad\":\"1.3.0\"}}", + OUT_OF_SYNC, + "as nothing", + ), + ( + serde_json::to_string_pretty(&serde_json::from_str::(&basic_lock()).unwrap()).unwrap(), + ROOT_PKG, + "vendor_lockfile_version_unsupported", + "canonical layout", + ), + ( + format!("\u{feff}{}", basic_lock()), + ROOT_PKG, + "vendor_lockfile_version_unsupported", + "byte-order mark", + ), + ]; + for (lock, pkg, code, needle) in cases { + let fx = fx(&lock, &[(PACKAGE_JSON, pkg)]).await; + let (got, detail) = refusal(run(&fx, UUID, false).await); + assert_eq!(got, code, "{lock}: {detail}"); + assert!(detail.contains(needle), "{detail}"); + assert_eq!(read(&fx, VLT_LOCK).await, lock); + assert!(!fx.root.join(".socket").exists()); + } + } + + #[tokio::test] + async fn a_peer_range_beside_the_dev_edge_is_left_untouched() { + let lock = render( + 1, + &[REG_NODE], + &[r#""file~_d left-pad": "dev 1.3.0 ~npm~left-pad@1.3.0""#], + ); + let pkg = "{\n \"devDependencies\": {\n \"left-pad\": \"1.3.0\"\n },\n \"peerDependencies\": {\n \"left-pad\": \"^1\"\n }\n}\n"; + let fx = fx(&lock, &[(PACKAGE_JSON, pkg)]).await; + entry_of(run(&fx, UUID, false).await); + let after = read(&fx, PACKAGE_JSON).await; + assert!( + after.contains("\"peerDependencies\": {\n \"left-pad\": \"^1\""), + "{after}" + ); + assert!( + after.contains("\"left-pad\": \"file:./.socket/vendor/npm/"), + "{after}" + ); + } + + #[tokio::test] + async fn the_legacy_era_warns_and_wires_with_its_own_grammar() { + let lock = render( + 0, + &[r#""··left-pad@1.3.0": [0,"left-pad","sha512-REG=="]"#], + &[r#""file·. left-pad": "prod 1.3.0 ··left-pad@1.3.0""#], + ); + let fx = fx(&lock, &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (_, warnings) = entry_of(run(&fx, UUID, false).await); + assert_eq!(warnings[0].code, "vendor_vlt_legacy_lockfile"); + let wired = read(&fx, VLT_LOCK).await; + assert!( + wired.contains(&format!( + "\"file·.socket§vendor§npm§{UUID}§left-pad-1.3.0§node_modules§left-pad\": [0,\"left-pad\",null," + )), + "{wired}" + ); + } + + #[tokio::test] + async fn revert_restores_slots_under_a_changed_flag_and_refuses_drift() { + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (entry, _) = entry_of(run(&fx, UUID, false).await); + let wired = read(&fx, VLT_LOCK).await; + let reflagged = wired.replace("[0,\"left-pad\",null,", "[2,\"left-pad\",null,"); + tokio::fs::write(fx.root.join(VLT_LOCK), &reflagged) + .await + .unwrap(); + let out = revert_vlt_opts(&entry, &fx.root, RevertOpts::new(false)).await; + assert!(out.success && out.warnings.is_empty(), "{out:?}"); + assert_eq!( + read(&fx, VLT_LOCK).await, + basic_lock().replace( + "[0,\"left-pad\",\"sha512-REG==\"", + "[2,\"left-pad\",\"sha512-REG==\"" + ) + ); + assert_eq!(read(&fx, PACKAGE_JSON).await, ROOT_PKG); + + let fx = self::fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (entry, _) = entry_of(run(&fx, UUID, false).await); + let wired = read(&fx, VLT_LOCK).await; + let drifted = wired.replace("\"prod file:./", "\"dev file:./"); + tokio::fs::write(fx.root.join(VLT_LOCK), &drifted) + .await + .unwrap(); + let pkg = read(&fx, PACKAGE_JSON).await; + let out = revert_vlt_opts(&entry, &fx.root, RevertOpts::new(false)).await; + assert!( + out.success && out.drift_skipped() && out.kept_artifact, + "{out:?}" + ); + assert_eq!(read(&fx, VLT_LOCK).await, drifted, "a drift writes nothing"); + assert_eq!(read(&fx, PACKAGE_JSON).await, pkg); + assert!(fx.root.join(&entry.artifact.path).exists()); + } + + #[tokio::test] + async fn revert_after_the_user_already_undid_the_wiring_only_removes_the_artifact() { + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (entry, _) = entry_of(run(&fx, UUID, false).await); + let relocked = basic_lock() + .replace("\"sha512-REG==\"", "\"sha512-NEW==\"") + .replace("prod 1.3.0 ~npm~left-pad", "prod ^1.3.0 ~npm~left-pad"); + tokio::fs::write(fx.root.join(VLT_LOCK), &relocked) + .await + .unwrap(); + let pkg = ROOT_PKG.replace("\"left-pad\": \"1.3.0\"", "\"left-pad\": \"^1.3.0\""); + tokio::fs::write(fx.root.join(PACKAGE_JSON), &pkg) + .await + .unwrap(); + let out = revert_vlt_opts(&entry, &fx.root, RevertOpts::new(false)).await; + assert!(out.success && out.warnings.is_empty(), "{out:?}"); + assert_eq!(read(&fx, VLT_LOCK).await, relocked); + assert_eq!(read(&fx, PACKAGE_JSON).await, pkg); + assert!(!fx.root.join(format!(".socket/vendor/npm/{UUID}")).exists()); + } + + #[tokio::test] + async fn a_cross_grammar_relock_names_the_manual_step() { + let lock = render( + 0, + &[r#""·npm·left-pad@1.3.0": [0,"left-pad","sha512-REG=="]"#], + &[r#""file·. left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0""#], + ); + let fx = fx(&lock, &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (entry, _) = entry_of(run(&fx, UUID, false).await); + let rel = entry.artifact.path.clone(); + let file_id = + format!("file~.socket+vendor+npm+{UUID}+left-pad-1.3.0+node__modules+left-pad"); + let relocked = render( + 1, + &[&format!(r#""{file_id}": [0,"left-pad",null,"{rel}"]"#)], + &[&format!( + r#""file~_d left-pad": "prod file:./{rel} {file_id}""# + )], + ); + tokio::fs::write(fx.root.join(VLT_LOCK), &relocked) + .await + .unwrap(); + let out = revert_vlt_opts(&entry, &fx.root, RevertOpts::new(false)).await; + assert!(out.kept_artifact, "{out:?}"); + let detail = &out.warnings[0].detail; + assert!( + detail.contains("re-created by a different vlt lockfile grammar") + && detail.contains("set left-pad in package.json back to 1.3.0"), + "{detail}" + ); + + tokio::fs::write(fx.root.join(PACKAGE_JSON), ROOT_PKG) + .await + .unwrap(); + tokio::fs::write(fx.root.join(VLT_LOCK), basic_lock()) + .await + .unwrap(); + let out = revert_vlt_opts(&entry, &fx.root, RevertOpts::new(false)).await; + assert!(out.success && !out.kept_artifact, "{out:?}"); + } + + #[tokio::test] + async fn a_new_uuid_rewires_our_dir_and_keeps_the_pristine_originals() { + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (first, _) = entry_of(run(&fx, UUID, false).await); + persist(&fx, &first).await; + let (second, _) = entry_of(run(&fx, UUID2, false).await); + let lock = read(&fx, VLT_LOCK).await; + assert!(lock.contains(UUID2) && !lock.contains(UUID), "{lock}"); + assert_eq!(second.wiring.len(), first.wiring.len()); + for (a, b) in first.wiring.iter().zip(&second.wiring) { + assert_eq!( + (&a.kind, &a.key, &a.original), + (&b.kind, &b.key, &b.original) + ); + } + let out = revert_vlt_opts(&second, &fx.root, RevertOpts::new(false)).await; + assert!(out.success && out.warnings.is_empty(), "{out:?}"); + assert_eq!(read(&fx, VLT_LOCK).await, basic_lock()); + assert_eq!(read(&fx, PACKAGE_JSON).await, ROOT_PKG); + } + + #[tokio::test] + async fn uuid_metadata_is_restored_for_the_entry() { + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (entry, _) = entry_of(run(&fx, UUID, false).await); + let gitignore = fx + .root + .join(format!(".socket/vendor/npm/{UUID}/.gitignore")); + tokio::fs::remove_file(&gitignore).await.unwrap(); + restore_vlt_uuid_metadata(&entry, &fx.root).await.unwrap(); + assert_eq!( + tokio::fs::read_to_string(&gitignore).await.unwrap(), + super::super::npm_dir::UUID_GITIGNORE + ); + } + + #[tokio::test] + async fn an_unwired_entry_refuses_while_the_lock_still_names_its_dir() { + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (mut entry, _) = entry_of(run(&fx, UUID, false).await); + entry.wiring.clear(); + let out = revert_vlt_opts(&entry, &fx.root, RevertOpts::new(false)).await; + assert!(!out.success); + assert_eq!(out.warnings[0].code, "vendor_wiring_unknown_revert_blocked"); + assert!(fx.root.join(&entry.artifact.path).exists()); + assert_eq!(vlt_entry_in_use(&entry, &fx.root).await, Some(true)); + } + + #[tokio::test] + async fn a_lock_write_failure_puts_package_json_back() { + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let wiring = Wiring { + records: Vec::new(), + pkgs: BTreeMap::from([(PACKAGE_JSON.to_string(), "{}".to_string())]), + lock: Some("{}".to_string()), + }; + tokio::fs::remove_file(fx.root.join(VLT_LOCK)) + .await + .unwrap(); + tokio::fs::create_dir_all(fx.root.join(VLT_LOCK).join("occupied")) + .await + .unwrap(); + let originals = BTreeMap::from([(PACKAGE_JSON.to_string(), ROOT_PKG.to_string())]); + let err = commit(&fx.root, &wiring, &originals).await.unwrap_err(); + assert!(err.contains("package.json files restored"), "{err}"); + assert_eq!(read(&fx, PACKAGE_JSON).await, ROOT_PKG); + } + + #[tokio::test] + async fn a_manifest_patch_warns_that_the_lock_keeps_its_edges() { + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let patched_pkg = + b"{\"name\":\"left-pad\",\"version\":\"1.3.0\",\"devDependencies\":{\"t\":\"1\"},\"main\":\"i.js\"}"; + tokio::fs::write( + fx.blobs.join(compute_git_sha256_from_bytes(patched_pkg)), + patched_pkg, + ) + .await + .unwrap(); + let mut rec = record(UUID); + rec.files.insert( + "package/package.json".into(), + PatchFileInfo { + before_hash: String::new(), + after_hash: compute_git_sha256_from_bytes(patched_pkg), + }, + ); + let sources = PatchSources::blobs_only(&fx.blobs); + let outcome = vendor_vlt( + PURL, + &fx.installed, + &fx.root, + &rec, + &sources, + "t", + false, + true, + None, + ) + .await; + let (entry, warnings) = entry_of(outcome); + assert!( + warnings + .iter() + .any(|w| w.code == "vendor_dep_manifest_stale"), + "{warnings:?}" + ); + let committed = read(&fx, &format!("{}/package.json", entry.artifact.path)).await; + assert_eq!( + committed, + "{\"name\":\"left-pad\",\"version\":\"1.3.0\",\"main\":\"i.js\"}" + ); + crate::vendor::verify::verify_vendored_patch_record(&fx.root, &entry, &rec) + .await + .expect("the inventory pin verifies the stripped manifest without the blob"); + let local_blobs = fx.root.join(".socket/blobs"); + tokio::fs::create_dir_all(&local_blobs).await.unwrap(); + tokio::fs::write( + local_blobs.join(compute_git_sha256_from_bytes(patched_pkg)), + patched_pkg, + ) + .await + .unwrap(); + crate::vendor::verify::verify_vendored_patch_record(&fx.root, &entry, &rec) + .await + .expect("and with the blob"); + tokio::fs::write( + fx.root.join(&entry.artifact.path).join(PACKAGE_JSON), + "{\"name\":\"left-pad\",\"version\":\"6.6.6\"}", + ) + .await + .unwrap(); + assert_eq!( + crate::vendor::verify::verify_vendored_patch_record(&fx.root, &entry, &rec).await, + Err("vendor_hash_mismatch".to_string()) + ); + } + + #[tokio::test] + async fn the_preflight_decides_every_refusal_without_writing() { + let fx = fx( + &render( + 1, + &[REG_NODE], + &[r#""file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0""#], + ), + &[(PACKAGE_JSON, ROOT_PKG)], + ) + .await; + assert_eq!(vlt_vendor_preflight(&fx.root, PURL, UUID).await, Ok(())); + tokio::fs::write( + fx.root.join(PACKAGE_JSON), + "{\"dependencies\":{\"left-pad\":\"^1\"}}", + ) + .await + .unwrap(); + let (code, _) = vlt_vendor_preflight(&fx.root, PURL, UUID) + .await + .unwrap_err(); + assert_eq!(code, OUT_OF_SYNC); + tokio::fs::write(fx.root.join(PACKAGE_JSON), ROOT_PKG) + .await + .unwrap(); + let store = fx + .root + .join("node_modules/.vlt") + .join(REG) + .join("node_modules/left-pad"); + tokio::fs::create_dir_all(&store).await.unwrap(); + tokio::fs::write(store.join(PACKAGE_JSON), "{\"bundleDependencies\":true}") + .await + .unwrap(); + let (code, _) = vlt_vendor_preflight(&fx.root, PURL, UUID) + .await + .unwrap_err(); + assert_eq!(code, "vendor_bundled_deps_unsupported"); + tokio::fs::write( + store.join(PACKAGE_JSON), + "{\"devDependencies\":{},\"devDependencies\":{}}", + ) + .await + .unwrap(); + let (code, detail) = vlt_vendor_preflight(&fx.root, PURL, UUID) + .await + .unwrap_err(); + assert_eq!(code, UNSUPPORTED); + assert!(detail.contains("duplicate devDependencies"), "{detail}"); + } + + fn service_tgz(entries: &[(&str, tar::EntryType, &[u8])]) -> Vec { + let mut builder = tar::Builder::new(flate2::write::GzEncoder::new( + Vec::new(), + flate2::Compression::default(), + )); + for (path, kind, bytes) in entries { + let mut header = tar::Header::new_gnu(); + header.set_entry_type(*kind); + header.set_size(bytes.len() as u64); + header.set_mode(0o644); + if *kind == tar::EntryType::Symlink { + header.set_link_name("/etc/passwd").unwrap(); + } + header.set_cksum(); + builder.append_data(&mut header, path, *bytes).unwrap(); + } + builder.into_inner().unwrap().finish().unwrap() + } + + async fn run_with(fx: &Fx, cfg: &crate::vendor::VendorServiceConfig) -> VendorOutcome { + let sources = PatchSources::blobs_only(&fx.blobs); + vendor_vlt( + PURL, + &fx.installed, + &fx.root, + &record(UUID), + &sources, + "t", + false, + false, + Some(cfg), + ) + .await + } + + #[tokio::test] + async fn the_service_tree_is_extracted_under_any_first_component_and_transformed() { + use crate::vendor::test_support::{mount_granted, request_count, service_cfg}; + use crate::vendor::VendorSource; + let server = wiremock::MockServer::start().await; + let tgz = service_tgz(&[ + ( + "left-pad/package.json", + tar::EntryType::Regular, + b"{\"name\":\"left-pad\",\"devDependencies\":{\"x\":\"1\"},\"version\":\"1.3.0\"}", + ), + ("left-pad/index.js", tar::EntryType::Regular, PATCHED), + ("left-pad/README.md", tar::EntryType::Regular, b"service"), + ]); + mount_granted(&server, UUID, "left-pad-1.3.0.tgz", &tgz).await; + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let cfg = service_cfg(&server.uri(), VendorSource::Auto, false); + let (entry, warnings) = entry_of(run_with(&fx, &cfg).await); + assert!( + warnings + .iter() + .any(|w| w.code == "vendor_prebuilt_downloaded"), + "{warnings:?}" + ); + assert_eq!( + read(&fx, &format!("{}/package.json", entry.artifact.path)).await, + "{\"name\":\"left-pad\",\"version\":\"1.3.0\"}" + ); + assert_eq!( + read(&fx, &format!("{}/README.md", entry.artifact.path)).await, + "service" + ); + persist(&fx, &entry).await; + let before = request_count(&server).await; + match run_with(&fx, &cfg).await { + VendorOutcome::Done { + entry: None, + result, + .. + } => assert!(result.success), + other => panic!("the rerun reuses the committed dir: {other:?}"), + } + assert_eq!( + request_count(&server).await, + before, + "reuse never calls the service" + ); + } + + #[tokio::test] + async fn service_failures_follow_the_tarball_policy() { + use crate::vendor::test_support::{mount_503, mount_granted, service_cfg}; + use crate::vendor::VendorSource; + + let server = wiremock::MockServer::start().await; + mount_503(&server).await; + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (_, warnings) = + entry_of(run_with(&fx, &service_cfg(&server.uri(), VendorSource::Auto, false)).await); + assert!( + warnings + .iter() + .any(|w| w.code == "vendor_prebuilt_unavailable"), + "{warnings:?}" + ); + + let fx = self::fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + match run_with( + &fx, + &service_cfg(&server.uri(), VendorSource::Service, false), + ) + .await + { + VendorOutcome::Done { result, entry, .. } => { + assert!(!result.success && entry.is_none(), "{:?}", result.error) + } + other => panic!("{other:?}"), + } + assert_eq!(read(&fx, VLT_LOCK).await, basic_lock()); + assert!(!fx.root.join(".socket/vendor").exists()); + + let server = wiremock::MockServer::start().await; + let bad = service_tgz(&[ + ("package/index.js", tar::EntryType::Regular, PATCHED), + ("package/link", tar::EntryType::Symlink, b""), + ]); + mount_granted(&server, UUID, "left-pad-1.3.0.tgz", &bad).await; + let fx = self::fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + match run_with(&fx, &service_cfg(&server.uri(), VendorSource::Auto, false)).await { + VendorOutcome::Done { result, entry, .. } => { + assert!(!result.success && entry.is_none()); + assert!(result.error.unwrap().contains("unsafe")); + } + other => panic!("{other:?}"), + } + assert!(!fx.root.join(".socket/vendor").exists()); + } +} diff --git a/crates/socket-patch-core/src/vendor/vlt_lock_text.rs b/crates/socket-patch-core/src/vendor/vlt_lock_text.rs index 684c3454..8df8928d 100644 --- a/crates/socket-patch-core/src/vendor/vlt_lock_text.rs +++ b/crates/socket-patch-core/src/vendor/vlt_lock_text.rs @@ -881,14 +881,15 @@ fn leaf_version<'l>(bare: &str, leaf: &'l str) -> Option<&'l str> { .filter(|v| is_npm_semver(v)) } -/// A decoded `file` path of the vendored directory shape, with the name -/// read from its `node_modules/` segments. -pub(crate) fn parse_vendored_dir_path(path: &str) -> Option { - let segments: Vec<&str> = path.strip_prefix(VENDOR_NPM_PREFIX)?.split('/').collect(); - let (uuid, scope, leaf, bare) = match segments.as_slice() { - [uuid, leaf, "node_modules", bare] => (*uuid, None, *leaf, *bare), - [uuid, scope, leaf, "node_modules", scope_again, bare] if scope == scope_again => { - (*uuid, Some(*scope), *leaf, *bare) +/// `(name, version)` of a vendored directory leaf below the uuid level, +/// `[@s/]-/node_modules/[@s/]`, with the name read from +/// its `node_modules/` segments. +pub(crate) fn parse_vendored_dir_leaf(leaf: &str) -> Option<(String, String)> { + let segments: Vec<&str> = leaf.split('/').collect(); + let (scope, leaf, bare) = match segments.as_slice() { + [leaf, "node_modules", bare] => (None, *leaf, *bare), + [scope, leaf, "node_modules", scope_again, bare] if scope == scope_again => { + (Some(*scope), *leaf, *bare) } _ => return None, }; @@ -897,13 +898,24 @@ pub(crate) fn parse_vendored_dir_path(path: &str) -> Option { Some(_) => return None, None => bare.to_string(), }; - if !is_canonical_uuid(uuid) || !is_registry_package_name(&name) { + if !is_registry_package_name(&name) { + return None; + } + Some((name, leaf_version(bare, leaf)?.to_string())) +} + +/// A decoded `file` path of the vendored directory shape, with the name +/// read from its `node_modules/` segments. +pub(crate) fn parse_vendored_dir_path(path: &str) -> Option { + let (uuid, leaf) = path.strip_prefix(VENDOR_NPM_PREFIX)?.split_once('/')?; + if !is_canonical_uuid(uuid) { return None; } + let (name, version) = parse_vendored_dir_leaf(leaf)?; Some(VendoredPath { uuid: uuid.to_string(), - version: leaf_version(bare, leaf)?.to_string(), name, + version, shape: VendoredShape::Dir, }) } @@ -941,10 +953,7 @@ pub(crate) fn parse_vendored_path(path: &str, name: &str) -> Option/[@s/]-/node_modules/`. pub(crate) fn vendored_dir_rel(uuid: &str, name: &str, version: &str) -> String { - let leaf = match name.split_once('/') { - Some((scope, bare)) => format!("{scope}/{bare}-{version}"), - None => format!("{name}-{version}"), - }; + let leaf = super::npm_common::pkg_rel_leaf(name, version); format!("{VENDOR_NPM_PREFIX}{uuid}/{leaf}/node_modules/{name}") } diff --git a/crates/socket-patch-core/src/vex/discover/mod.rs b/crates/socket-patch-core/src/vex/discover/mod.rs index fe3090b0..65006b6b 100644 --- a/crates/socket-patch-core/src/vex/discover/mod.rs +++ b/crates/socket-patch-core/src/vex/discover/mod.rs @@ -2904,6 +2904,7 @@ mod tests { "npm-shrinkwrap.json", "package-lock.json", "pnpm-lock.yaml", + "vlt-lock.json", "yarn.lock", ], ), diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias-selfref-peer/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias-selfref-peer/case.json new file mode 100644 index 00000000..18633faa --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias-selfref-peer/case.json @@ -0,0 +1,12 @@ +{ + "project": "alias", + "purl": "pkg:npm/use-sync-external-store@1.2.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [ + [ + " \"file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§use-sync-external-store-1.2.0§node_modules§use-sync-external-store react\": \"peer ^16.8.0 || ^17.0.0 || ^18.0.0 ·npm·react@18.2.0\"", + " \"file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§use-sync-external-store-1.2.0§node_modules§use-sync-external-store react\": \"peer 18.2.0 ·npm·react@18.2.0\"" + ] + ] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias-selfref-peer/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias-selfref-peer/expected/package.json new file mode 100644 index 00000000..48d192ea --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias-selfref-peer/expected/package.json @@ -0,0 +1,9 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "lp": "npm:left-pad@1.3.0", + "react": "18.2.0", + "usx": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/use-sync-external-store-1.2.0/node_modules/use-sync-external-store" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias-selfref-peer/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias-selfref-peer/expected/vlt-lock.json new file mode 100644 index 00000000..b6dcbd0f --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias-selfref-peer/expected/vlt-lock.json @@ -0,0 +1,19 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "·npm·js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "·npm·left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "·npm·loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "·npm·react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§use-sync-external-store-1.2.0§node_modules§use-sync-external-store": [0,"use-sync-external-store",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/use-sync-external-store-1.2.0/node_modules/use-sync-external-store"] + }, + "edges": { + "file·. lp": "prod npm:left-pad@1.3.0 ·npm·left-pad@1.3.0", + "file·. react": "prod 18.2.0 ·npm·react@18.2.0", + "file·. usx": "prod file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/use-sync-external-store-1.2.0/node_modules/use-sync-external-store file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§use-sync-external-store-1.2.0§node_modules§use-sync-external-store", + "·npm·loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ·npm·js-tokens@4.0.0", + "·npm·react@18.2.0 loose-envify": "prod ^1.1.0 ·npm·loose-envify@1.4.0", + "file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§use-sync-external-store-1.2.0§node_modules§use-sync-external-store react": "peer 18.2.0 ·npm·react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias/case.json new file mode 100644 index 00000000..6c9a0f29 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias/case.json @@ -0,0 +1,7 @@ +{ + "project": "alias", + "purl": "pkg:npm/left-pad@1.3.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias/expected/package.json new file mode 100644 index 00000000..2246ce1e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias/expected/package.json @@ -0,0 +1,9 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "lp": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad", + "react": "18.2.0", + "usx": "npm:use-sync-external-store@1.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias/expected/vlt-lock.json new file mode 100644 index 00000000..2ed10c88 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/alias/expected/vlt-lock.json @@ -0,0 +1,19 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "·npm·js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "·npm·loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "·npm·react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "·npm·use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§left-pad-1.3.0§node_modules§left-pad": [0,"left-pad",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad"] + }, + "edges": { + "file·. react": "prod 18.2.0 ·npm·react@18.2.0", + "file·. usx": "prod npm:use-sync-external-store@1.2.0 ·npm·use-sync-external-store@1.2.0", + "file·. lp": "prod file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§left-pad-1.3.0§node_modules§left-pad", + "·npm·loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ·npm·js-tokens@4.0.0", + "·npm·react@18.2.0 loose-envify": "prod ^1.1.0 ·npm·loose-envify@1.4.0", + "·npm·use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ·npm·react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/dev-edge/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/dev-edge/case.json new file mode 100644 index 00000000..dead58d7 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/dev-edge/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/is-number@7.0.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/dev-edge/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/dev-edge/expected/package.json new file mode 100644 index 00000000..b5085327 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/dev-edge/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/is-number-7.0.0/node_modules/is-number" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/dev-edge/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/dev-edge/expected/packages/a/package.json new file mode 100644 index 00000000..90360a71 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/dev-edge/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/dev-edge/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/dev-edge/expected/vlt-lock.json new file mode 100644 index 00000000..ad025b2a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/dev-edge/expected/vlt-lock.json @@ -0,0 +1,42 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "·npm·@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "·npm·debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "·npm·escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",null,null,null,null,{ "engines": { "node": ">=10" }}], + "·npm·has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "·npm·js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "·npm·left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "·npm·loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "·npm·lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "·npm·ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "·npm·ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "·npm·react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "·npm·semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",null,null,null,null,null,{ "semver": "bin/semver.js"}], + "·npm·supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "·npm·use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "·npm·yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§is-number-7.0.0§node_modules§is-number": [2,"is-number",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/is-number-7.0.0/node_modules/is-number"] + }, + "edges": { + "file·. is-number": "dev file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/is-number-7.0.0/node_modules/is-number file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§is-number-7.0.0§node_modules§is-number", + "file·. escape-string-regexp": "optional 4.0.0 ·npm·escape-string-regexp@4.0.0", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ·npm·@isaacs§string-locale-compare@1.1.0", + "file·. left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "file·. ms": "prod 2.1.3 ·npm·ms@2.1.3", + "file·. react": "prod 18.2.0 ·npm·react@18.2.0", + "file·. semver": "prod 7.6.0 ·npm·semver@7.6.0", + "file·. supports-color": "prod 7.2.0 ·npm·supports-color@7.2.0", + "file·. use-sync-external-store": "prod 1.2.0 ·npm·use-sync-external-store@1.2.0", + "workspace·packages§a debug": "prod 4.3.4 ·npm·debug@4.3.4", + "workspace·packages§a left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "·npm·debug@4.3.4 ms": "prod 2.1.2 ·npm·ms@2.1.2", + "·npm·loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ·npm·js-tokens@4.0.0", + "·npm·lru-cache@6.0.0 yallist": "prod ^4.0.0 ·npm·yallist@4.0.0", + "·npm·react@18.2.0 loose-envify": "prod ^1.1.0 ·npm·loose-envify@1.4.0", + "·npm·semver@7.6.0 lru-cache": "prod ^6.0.0 ·npm·lru-cache@6.0.0", + "·npm·supports-color@7.2.0 has-flag": "prod ^4.0.0 ·npm·has-flag@4.0.0", + "·npm·use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ·npm·react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/left-pad/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/left-pad/case.json new file mode 100644 index 00000000..c98fb091 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/left-pad/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/left-pad@1.3.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/left-pad/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/left-pad/expected/package.json new file mode 100644 index 00000000..ee2d0698 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/left-pad/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/left-pad/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/left-pad/expected/packages/a/package.json new file mode 100644 index 00000000..17bfc18d --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/left-pad/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "file:../../.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/left-pad/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/left-pad/expected/vlt-lock.json new file mode 100644 index 00000000..9fe47ca6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/left-pad/expected/vlt-lock.json @@ -0,0 +1,42 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "·npm·@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "·npm·debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "·npm·escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",null,null,null,null,{ "engines": { "node": ">=10" }}], + "·npm·has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "·npm·is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + "·npm·js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "·npm·loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "·npm·lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "·npm·ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "·npm·ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "·npm·react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "·npm·semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",null,null,null,null,null,{ "semver": "bin/semver.js"}], + "·npm·supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "·npm·use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "·npm·yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§left-pad-1.3.0§node_modules§left-pad": [0,"left-pad",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad"] + }, + "edges": { + "file·. is-number": "dev 7.0.0 ·npm·is-number@7.0.0", + "file·. escape-string-regexp": "optional 4.0.0 ·npm·escape-string-regexp@4.0.0", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ·npm·@isaacs§string-locale-compare@1.1.0", + "file·. ms": "prod 2.1.3 ·npm·ms@2.1.3", + "file·. react": "prod 18.2.0 ·npm·react@18.2.0", + "file·. semver": "prod 7.6.0 ·npm·semver@7.6.0", + "file·. supports-color": "prod 7.2.0 ·npm·supports-color@7.2.0", + "file·. use-sync-external-store": "prod 1.2.0 ·npm·use-sync-external-store@1.2.0", + "file·. left-pad": "prod file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§left-pad-1.3.0§node_modules§left-pad", + "workspace·packages§a debug": "prod 4.3.4 ·npm·debug@4.3.4", + "workspace·packages§a left-pad": "prod file:../../.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§left-pad-1.3.0§node_modules§left-pad", + "·npm·debug@4.3.4 ms": "prod 2.1.2 ·npm·ms@2.1.2", + "·npm·loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ·npm·js-tokens@4.0.0", + "·npm·lru-cache@6.0.0 yallist": "prod ^4.0.0 ·npm·yallist@4.0.0", + "·npm·react@18.2.0 loose-envify": "prod ^1.1.0 ·npm·loose-envify@1.4.0", + "·npm·semver@7.6.0 lru-cache": "prod ^6.0.0 ·npm·lru-cache@6.0.0", + "·npm·supports-color@7.2.0 has-flag": "prod ^4.0.0 ·npm·has-flag@4.0.0", + "·npm·use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ·npm·react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/member-only/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/member-only/case.json new file mode 100644 index 00000000..38c6aba5 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/member-only/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/debug@4.3.4", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/member-only/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/member-only/expected/package.json new file mode 100644 index 00000000..8ea00c2b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/member-only/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/member-only/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/member-only/expected/packages/a/package.json new file mode 100644 index 00000000..6506dc3d --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/member-only/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "file:../../.socket/vendor/npm/11111111-2222-4333-8444-555555555555/debug-4.3.4/node_modules/debug" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/member-only/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/member-only/expected/vlt-lock.json new file mode 100644 index 00000000..135d5d40 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/member-only/expected/vlt-lock.json @@ -0,0 +1,42 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "·npm·@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "·npm·escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",null,null,null,null,{ "engines": { "node": ">=10" }}], + "·npm·has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "·npm·is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + "·npm·js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "·npm·left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "·npm·loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "·npm·lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "·npm·ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "·npm·ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "·npm·react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "·npm·semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",null,null,null,null,null,{ "semver": "bin/semver.js"}], + "·npm·supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "·npm·use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "·npm·yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§debug-4.3.4§node_modules§debug": [0,"debug",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/debug-4.3.4/node_modules/debug"] + }, + "edges": { + "file·. is-number": "dev 7.0.0 ·npm·is-number@7.0.0", + "file·. escape-string-regexp": "optional 4.0.0 ·npm·escape-string-regexp@4.0.0", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ·npm·@isaacs§string-locale-compare@1.1.0", + "file·. left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "file·. ms": "prod 2.1.3 ·npm·ms@2.1.3", + "file·. react": "prod 18.2.0 ·npm·react@18.2.0", + "file·. semver": "prod 7.6.0 ·npm·semver@7.6.0", + "file·. supports-color": "prod 7.2.0 ·npm·supports-color@7.2.0", + "file·. use-sync-external-store": "prod 1.2.0 ·npm·use-sync-external-store@1.2.0", + "workspace·packages§a left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "workspace·packages§a debug": "prod file:../../.socket/vendor/npm/11111111-2222-4333-8444-555555555555/debug-4.3.4/node_modules/debug file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§debug-4.3.4§node_modules§debug", + "·npm·loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ·npm·js-tokens@4.0.0", + "·npm·lru-cache@6.0.0 yallist": "prod ^4.0.0 ·npm·yallist@4.0.0", + "·npm·react@18.2.0 loose-envify": "prod ^1.1.0 ·npm·loose-envify@1.4.0", + "·npm·semver@7.6.0 lru-cache": "prod ^6.0.0 ·npm·lru-cache@6.0.0", + "·npm·supports-color@7.2.0 has-flag": "prod ^4.0.0 ·npm·has-flag@4.0.0", + "·npm·use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ·npm·react@18.2.0", + "file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§debug-4.3.4§node_modules§debug ms": "prod 2.1.2 ·npm·ms@2.1.2" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/optional-edge/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/optional-edge/case.json new file mode 100644 index 00000000..38775dc8 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/optional-edge/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/escape-string-regexp@4.0.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/optional-edge/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/optional-edge/expected/package.json new file mode 100644 index 00000000..ef388d4c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/optional-edge/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/escape-string-regexp-4.0.0/node_modules/escape-string-regexp" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/optional-edge/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/optional-edge/expected/packages/a/package.json new file mode 100644 index 00000000..90360a71 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/optional-edge/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/optional-edge/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/optional-edge/expected/vlt-lock.json new file mode 100644 index 00000000..bd8b0641 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/optional-edge/expected/vlt-lock.json @@ -0,0 +1,42 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "·npm·@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "·npm·debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "·npm·has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "·npm·is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + "·npm·js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "·npm·left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "·npm·loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "·npm·lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "·npm·ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "·npm·ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "·npm·react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "·npm·semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",null,null,null,null,null,{ "semver": "bin/semver.js"}], + "·npm·supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "·npm·use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "·npm·yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§escape-string-regexp-4.0.0§node_modules§escape-string-regexp": [1,"escape-string-regexp",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/escape-string-regexp-4.0.0/node_modules/escape-string-regexp",null,null,null,{ "engines": { "node": ">=10" }}] + }, + "edges": { + "file·. is-number": "dev 7.0.0 ·npm·is-number@7.0.0", + "file·. escape-string-regexp": "optional file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/escape-string-regexp-4.0.0/node_modules/escape-string-regexp file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§escape-string-regexp-4.0.0§node_modules§escape-string-regexp", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ·npm·@isaacs§string-locale-compare@1.1.0", + "file·. left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "file·. ms": "prod 2.1.3 ·npm·ms@2.1.3", + "file·. react": "prod 18.2.0 ·npm·react@18.2.0", + "file·. semver": "prod 7.6.0 ·npm·semver@7.6.0", + "file·. supports-color": "prod 7.2.0 ·npm·supports-color@7.2.0", + "file·. use-sync-external-store": "prod 1.2.0 ·npm·use-sync-external-store@1.2.0", + "workspace·packages§a debug": "prod 4.3.4 ·npm·debug@4.3.4", + "workspace·packages§a left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "·npm·debug@4.3.4 ms": "prod 2.1.2 ·npm·ms@2.1.2", + "·npm·loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ·npm·js-tokens@4.0.0", + "·npm·lru-cache@6.0.0 yallist": "prod ^4.0.0 ·npm·yallist@4.0.0", + "·npm·react@18.2.0 loose-envify": "prod ^1.1.0 ·npm·loose-envify@1.4.0", + "·npm·semver@7.6.0 lru-cache": "prod ^6.0.0 ·npm·lru-cache@6.0.0", + "·npm·supports-color@7.2.0 has-flag": "prod ^4.0.0 ·npm·has-flag@4.0.0", + "·npm·use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ·npm·react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/peer/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/peer/case.json new file mode 100644 index 00000000..5bd8da3a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/peer/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/use-sync-external-store@1.2.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/peer/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/peer/expected/package.json new file mode 100644 index 00000000..601ef7bf --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/peer/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/use-sync-external-store-1.2.0/node_modules/use-sync-external-store" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/peer/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/peer/expected/packages/a/package.json new file mode 100644 index 00000000..90360a71 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/peer/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/peer/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/peer/expected/vlt-lock.json new file mode 100644 index 00000000..456ae0e5 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/peer/expected/vlt-lock.json @@ -0,0 +1,42 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "·npm·@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "·npm·debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "·npm·escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",null,null,null,null,{ "engines": { "node": ">=10" }}], + "·npm·has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "·npm·is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + "·npm·js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "·npm·left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "·npm·loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "·npm·lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "·npm·ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "·npm·ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "·npm·react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "·npm·semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",null,null,null,null,null,{ "semver": "bin/semver.js"}], + "·npm·supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "·npm·yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§use-sync-external-store-1.2.0§node_modules§use-sync-external-store": [0,"use-sync-external-store",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/use-sync-external-store-1.2.0/node_modules/use-sync-external-store"] + }, + "edges": { + "file·. is-number": "dev 7.0.0 ·npm·is-number@7.0.0", + "file·. escape-string-regexp": "optional 4.0.0 ·npm·escape-string-regexp@4.0.0", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ·npm·@isaacs§string-locale-compare@1.1.0", + "file·. left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "file·. ms": "prod 2.1.3 ·npm·ms@2.1.3", + "file·. react": "prod 18.2.0 ·npm·react@18.2.0", + "file·. semver": "prod 7.6.0 ·npm·semver@7.6.0", + "file·. supports-color": "prod 7.2.0 ·npm·supports-color@7.2.0", + "file·. use-sync-external-store": "prod file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/use-sync-external-store-1.2.0/node_modules/use-sync-external-store file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§use-sync-external-store-1.2.0§node_modules§use-sync-external-store", + "workspace·packages§a debug": "prod 4.3.4 ·npm·debug@4.3.4", + "workspace·packages§a left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "·npm·debug@4.3.4 ms": "prod 2.1.2 ·npm·ms@2.1.2", + "·npm·loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ·npm·js-tokens@4.0.0", + "·npm·lru-cache@6.0.0 yallist": "prod ^4.0.0 ·npm·yallist@4.0.0", + "·npm·react@18.2.0 loose-envify": "prod ^1.1.0 ·npm·loose-envify@1.4.0", + "·npm·semver@7.6.0 lru-cache": "prod ^6.0.0 ·npm·lru-cache@6.0.0", + "·npm·supports-color@7.2.0 has-flag": "prod ^4.0.0 ·npm·has-flag@4.0.0", + "file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§use-sync-external-store-1.2.0§node_modules§use-sync-external-store react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ·npm·react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/scoped/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/scoped/case.json new file mode 100644 index 00000000..7358fac5 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/scoped/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/@isaacs/string-locale-compare@1.1.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/scoped/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/scoped/expected/package.json new file mode 100644 index 00000000..cb3d0b8c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/scoped/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/@isaacs/string-locale-compare-1.1.0/node_modules/@isaacs/string-locale-compare", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/scoped/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/scoped/expected/packages/a/package.json new file mode 100644 index 00000000..90360a71 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/scoped/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/scoped/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/scoped/expected/vlt-lock.json new file mode 100644 index 00000000..5a78cf34 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/scoped/expected/vlt-lock.json @@ -0,0 +1,42 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "·npm·debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "·npm·escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",null,null,null,null,{ "engines": { "node": ">=10" }}], + "·npm·has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "·npm·is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + "·npm·js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "·npm·left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "·npm·loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "·npm·lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "·npm·ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "·npm·ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "·npm·react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "·npm·semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",null,null,null,null,null,{ "semver": "bin/semver.js"}], + "·npm·supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "·npm·use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "·npm·yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§@isaacs§string-locale-compare-1.1.0§node_modules§@isaacs§string-locale-compare": [0,"@isaacs/string-locale-compare",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/@isaacs/string-locale-compare-1.1.0/node_modules/@isaacs/string-locale-compare"] + }, + "edges": { + "file·. is-number": "dev 7.0.0 ·npm·is-number@7.0.0", + "file·. escape-string-regexp": "optional 4.0.0 ·npm·escape-string-regexp@4.0.0", + "file·. left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "file·. ms": "prod 2.1.3 ·npm·ms@2.1.3", + "file·. react": "prod 18.2.0 ·npm·react@18.2.0", + "file·. semver": "prod 7.6.0 ·npm·semver@7.6.0", + "file·. supports-color": "prod 7.2.0 ·npm·supports-color@7.2.0", + "file·. use-sync-external-store": "prod 1.2.0 ·npm·use-sync-external-store@1.2.0", + "file·. @isaacs/string-locale-compare": "prod file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/@isaacs/string-locale-compare-1.1.0/node_modules/@isaacs/string-locale-compare file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§@isaacs§string-locale-compare-1.1.0§node_modules§@isaacs§string-locale-compare", + "workspace·packages§a debug": "prod 4.3.4 ·npm·debug@4.3.4", + "workspace·packages§a left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "·npm·debug@4.3.4 ms": "prod 2.1.2 ·npm·ms@2.1.2", + "·npm·loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ·npm·js-tokens@4.0.0", + "·npm·lru-cache@6.0.0 yallist": "prod ^4.0.0 ·npm·yallist@4.0.0", + "·npm·react@18.2.0 loose-envify": "prod ^1.1.0 ·npm·loose-envify@1.4.0", + "·npm·semver@7.6.0 lru-cache": "prod ^6.0.0 ·npm·lru-cache@6.0.0", + "·npm·supports-color@7.2.0 has-flag": "prod ^4.0.0 ·npm·has-flag@4.0.0", + "·npm·use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ·npm·react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/semver/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/semver/case.json new file mode 100644 index 00000000..3d937dc3 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/semver/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/semver@7.6.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/semver/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/semver/expected/package.json new file mode 100644 index 00000000..59ac3005 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/semver/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/semver-7.6.0/node_modules/semver", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/semver/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/semver/expected/packages/a/package.json new file mode 100644 index 00000000..90360a71 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/semver/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/semver/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/semver/expected/vlt-lock.json new file mode 100644 index 00000000..c9f0e842 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/semver/expected/vlt-lock.json @@ -0,0 +1,42 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "·npm·@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "·npm·debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "·npm·escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",null,null,null,null,{ "engines": { "node": ">=10" }}], + "·npm·has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "·npm·is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + "·npm·js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "·npm·left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "·npm·loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "·npm·lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "·npm·ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "·npm·ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "·npm·react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "·npm·supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "·npm·use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "·npm·yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§semver-7.6.0§node_modules§semver": [0,"semver",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/semver-7.6.0/node_modules/semver",null,null,null,null,{ "semver": "bin/semver.js"}] + }, + "edges": { + "file·. is-number": "dev 7.0.0 ·npm·is-number@7.0.0", + "file·. escape-string-regexp": "optional 4.0.0 ·npm·escape-string-regexp@4.0.0", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ·npm·@isaacs§string-locale-compare@1.1.0", + "file·. left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "file·. ms": "prod 2.1.3 ·npm·ms@2.1.3", + "file·. react": "prod 18.2.0 ·npm·react@18.2.0", + "file·. supports-color": "prod 7.2.0 ·npm·supports-color@7.2.0", + "file·. use-sync-external-store": "prod 1.2.0 ·npm·use-sync-external-store@1.2.0", + "file·. semver": "prod file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/semver-7.6.0/node_modules/semver file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§semver-7.6.0§node_modules§semver", + "workspace·packages§a debug": "prod 4.3.4 ·npm·debug@4.3.4", + "workspace·packages§a left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "·npm·debug@4.3.4 ms": "prod 2.1.2 ·npm·ms@2.1.2", + "·npm·loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ·npm·js-tokens@4.0.0", + "·npm·lru-cache@6.0.0 yallist": "prod ^4.0.0 ·npm·yallist@4.0.0", + "·npm·react@18.2.0 loose-envify": "prod ^1.1.0 ·npm·loose-envify@1.4.0", + "·npm·supports-color@7.2.0 has-flag": "prod ^4.0.0 ·npm·has-flag@4.0.0", + "·npm·use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ·npm·react@18.2.0", + "file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§semver-7.6.0§node_modules§semver lru-cache": "prod ^6.0.0 ·npm·lru-cache@6.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/supports-color/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/supports-color/case.json new file mode 100644 index 00000000..a0f4f351 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/supports-color/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/supports-color@7.2.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/supports-color/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/supports-color/expected/package.json new file mode 100644 index 00000000..6796bab6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/supports-color/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/supports-color-7.2.0/node_modules/supports-color", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/supports-color/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/supports-color/expected/packages/a/package.json new file mode 100644 index 00000000..90360a71 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/supports-color/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/supports-color/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/supports-color/expected/vlt-lock.json new file mode 100644 index 00000000..adf35d53 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/supports-color/expected/vlt-lock.json @@ -0,0 +1,42 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "·npm·@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "·npm·debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "·npm·escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",null,null,null,null,{ "engines": { "node": ">=10" }}], + "·npm·has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "·npm·is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + "·npm·js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "·npm·left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "·npm·loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "·npm·lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "·npm·ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "·npm·ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "·npm·react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "·npm·semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",null,null,null,null,null,{ "semver": "bin/semver.js"}], + "·npm·use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "·npm·yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§supports-color-7.2.0§node_modules§supports-color": [0,"supports-color",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/supports-color-7.2.0/node_modules/supports-color"] + }, + "edges": { + "file·. is-number": "dev 7.0.0 ·npm·is-number@7.0.0", + "file·. escape-string-regexp": "optional 4.0.0 ·npm·escape-string-regexp@4.0.0", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ·npm·@isaacs§string-locale-compare@1.1.0", + "file·. left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "file·. ms": "prod 2.1.3 ·npm·ms@2.1.3", + "file·. react": "prod 18.2.0 ·npm·react@18.2.0", + "file·. semver": "prod 7.6.0 ·npm·semver@7.6.0", + "file·. use-sync-external-store": "prod 1.2.0 ·npm·use-sync-external-store@1.2.0", + "file·. supports-color": "prod file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/supports-color-7.2.0/node_modules/supports-color file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§supports-color-7.2.0§node_modules§supports-color", + "workspace·packages§a debug": "prod 4.3.4 ·npm·debug@4.3.4", + "workspace·packages§a left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "·npm·debug@4.3.4 ms": "prod 2.1.2 ·npm·ms@2.1.2", + "·npm·loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ·npm·js-tokens@4.0.0", + "·npm·lru-cache@6.0.0 yallist": "prod ^4.0.0 ·npm·yallist@4.0.0", + "·npm·react@18.2.0 loose-envify": "prod ^1.1.0 ·npm·loose-envify@1.4.0", + "·npm·semver@7.6.0 lru-cache": "prod ^6.0.0 ·npm·lru-cache@6.0.0", + "·npm·use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ·npm·react@18.2.0", + "file·.socket§vendor§npm§11111111-2222-4333-8444-555555555555§supports-color-7.2.0§node_modules§supports-color has-flag": "prod ^4.0.0 ·npm·has-flag@4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/transitive/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/transitive/case.json new file mode 100644 index 00000000..6b2b1999 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/cases/transitive/case.json @@ -0,0 +1,6 @@ +{ + "project": "workspace", + "purl": "pkg:npm/has-flag@4.0.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": "vendor_vlt_transitive_unsupported" +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/alias/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/alias/package.json new file mode 100644 index 00000000..25865836 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/alias/package.json @@ -0,0 +1,9 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "lp": "npm:left-pad@1.3.0", + "react": "18.2.0", + "usx": "npm:use-sync-external-store@1.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/alias/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/alias/vlt-lock.json new file mode 100644 index 00000000..b7777a5f --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/alias/vlt-lock.json @@ -0,0 +1,19 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "·npm·js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "·npm·left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "·npm·loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "·npm·react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "·npm·use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="] + }, + "edges": { + "file·. lp": "prod npm:left-pad@1.3.0 ·npm·left-pad@1.3.0", + "file·. react": "prod 18.2.0 ·npm·react@18.2.0", + "file·. usx": "prod npm:use-sync-external-store@1.2.0 ·npm·use-sync-external-store@1.2.0", + "·npm·loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ·npm·js-tokens@4.0.0", + "·npm·react@18.2.0 loose-envify": "prod ^1.1.0 ·npm·loose-envify@1.4.0", + "·npm·use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ·npm·react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/alias/vlt.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/alias/vlt.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/alias/vlt.json @@ -0,0 +1 @@ +{} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/workspace/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/workspace/package.json new file mode 100644 index 00000000..8ea00c2b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/workspace/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/workspace/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/workspace/packages/a/package.json new file mode 100644 index 00000000..90360a71 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/workspace/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/workspace/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/workspace/vlt-lock.json new file mode 100644 index 00000000..c6096c93 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/workspace/vlt-lock.json @@ -0,0 +1,42 @@ +{ + "lockfileVersion": 0, + "options": {}, + "nodes": { + "·npm·@isaacs§string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="], + "·npm·debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="], + "·npm·escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",null,null,null,null,{ "engines": { "node": ">=10" }}], + "·npm·has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "·npm·is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + "·npm·js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "·npm·left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA=="], + "·npm·loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",null,null,null,null,null,{ "loose-envify": "cli.js"}], + "·npm·lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "·npm·ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "·npm·ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "·npm·react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], + "·npm·semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",null,null,null,null,null,{ "semver": "bin/semver.js"}], + "·npm·supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "·npm·use-sync-external-store@1.2.0": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="], + "·npm·yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="] + }, + "edges": { + "file·. is-number": "dev 7.0.0 ·npm·is-number@7.0.0", + "file·. escape-string-regexp": "optional 4.0.0 ·npm·escape-string-regexp@4.0.0", + "file·. @isaacs/string-locale-compare": "prod 1.1.0 ·npm·@isaacs§string-locale-compare@1.1.0", + "file·. left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "file·. ms": "prod 2.1.3 ·npm·ms@2.1.3", + "file·. react": "prod 18.2.0 ·npm·react@18.2.0", + "file·. semver": "prod 7.6.0 ·npm·semver@7.6.0", + "file·. supports-color": "prod 7.2.0 ·npm·supports-color@7.2.0", + "file·. use-sync-external-store": "prod 1.2.0 ·npm·use-sync-external-store@1.2.0", + "workspace·packages§a debug": "prod 4.3.4 ·npm·debug@4.3.4", + "workspace·packages§a left-pad": "prod 1.3.0 ·npm·left-pad@1.3.0", + "·npm·debug@4.3.4 ms": "prod 2.1.2 ·npm·ms@2.1.2", + "·npm·loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ·npm·js-tokens@4.0.0", + "·npm·lru-cache@6.0.0 yallist": "prod ^4.0.0 ·npm·yallist@4.0.0", + "·npm·react@18.2.0 loose-envify": "prod ^1.1.0 ·npm·loose-envify@1.4.0", + "·npm·semver@7.6.0 lru-cache": "prod ^6.0.0 ·npm·lru-cache@6.0.0", + "·npm·supports-color@7.2.0 has-flag": "prod ^4.0.0 ·npm·has-flag@4.0.0", + "·npm·use-sync-external-store@1.2.0 react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ·npm·react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/workspace/vlt.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/workspace/vlt.json new file mode 100644 index 00000000..d6fc9d95 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.0-rc.14/projects/workspace/vlt.json @@ -0,0 +1,3 @@ +{ + "workspaces": "packages/*" +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/alias-selfref-peer/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/alias-selfref-peer/case.json new file mode 100644 index 00000000..0d80425a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/alias-selfref-peer/case.json @@ -0,0 +1,6 @@ +{ + "project": "alias", + "purl": "pkg:npm/use-sync-external-store@1.2.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": "vendor_lock_entry_unsupported" +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/alias/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/alias/case.json new file mode 100644 index 00000000..6c9a0f29 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/alias/case.json @@ -0,0 +1,7 @@ +{ + "project": "alias", + "purl": "pkg:npm/left-pad@1.3.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/alias/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/alias/expected/package.json new file mode 100644 index 00000000..2246ce1e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/alias/expected/package.json @@ -0,0 +1,9 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "lp": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad", + "react": "18.2.0", + "usx": "npm:use-sync-external-store@1.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/alias/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/alias/expected/vlt-lock.json new file mode 100644 index 00000000..416505cf --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/alias/expected/vlt-lock.json @@ -0,0 +1,23 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+left-pad-1.3.0+node__modules+left-pad": [0,"left-pad",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad"] + }, + "edges": { + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d usx": "prod npm:use-sync-external-store@1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "file~_d lp": "prod file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+left-pad-1.3.0+node__modules+left-pad", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/dev-edge/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/dev-edge/case.json new file mode 100644 index 00000000..dead58d7 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/dev-edge/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/is-number@7.0.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/dev-edge/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/dev-edge/expected/package.json new file mode 100644 index 00000000..b5085327 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/dev-edge/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/is-number-7.0.0/node_modules/is-number" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/dev-edge/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/dev-edge/expected/packages/a/package.json new file mode 100644 index 00000000..90360a71 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/dev-edge/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/dev-edge/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/dev-edge/expected/vlt-lock.json new file mode 100644 index 00000000..199e129a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/dev-edge/expected/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==","https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",null,null,null,{ "engines": { "node": ">=10" }}], + "~npm~has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==","https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==","https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+is-number-7.0.0+node__modules+is-number": [2,"is-number",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/is-number-7.0.0/node_modules/is-number"] + }, + "edges": { + "file~_d is-number": "dev file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/is-number-7.0.0/node_modules/is-number file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+is-number-7.0.0+node__modules+is-number", + "file~_d escape-string-regexp": "optional 4.0.0 ~npm~escape-string-regexp@4.0.0", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d supports-color": "prod 7.2.0 ~npm~supports-color@7.2.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "workspace~packages+a debug": "prod 4.3.4 ~npm~debug@4.3.4", + "workspace~packages+a left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "~npm~debug@4.3.4 ms": "prod 2.1.2 ~npm~ms@2.1.2", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~supports-color@7.2.0 has-flag": "prod ^4.0.0 ~npm~has-flag@4.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/left-pad/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/left-pad/case.json new file mode 100644 index 00000000..c98fb091 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/left-pad/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/left-pad@1.3.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/left-pad/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/left-pad/expected/package.json new file mode 100644 index 00000000..ee2d0698 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/left-pad/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/left-pad/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/left-pad/expected/packages/a/package.json new file mode 100644 index 00000000..17bfc18d --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/left-pad/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "file:../../.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/left-pad/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/left-pad/expected/vlt-lock.json new file mode 100644 index 00000000..60206682 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/left-pad/expected/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==","https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",null,null,null,{ "engines": { "node": ">=10" }}], + "~npm~has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==","https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"], + "~npm~is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==","https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==","https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+left-pad-1.3.0+node__modules+left-pad": [0,"left-pad",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad"] + }, + "edges": { + "file~_d is-number": "dev 7.0.0 ~npm~is-number@7.0.0", + "file~_d escape-string-regexp": "optional 4.0.0 ~npm~escape-string-regexp@4.0.0", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d supports-color": "prod 7.2.0 ~npm~supports-color@7.2.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "file~_d left-pad": "prod file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+left-pad-1.3.0+node__modules+left-pad", + "workspace~packages+a debug": "prod 4.3.4 ~npm~debug@4.3.4", + "workspace~packages+a left-pad": "prod file:../../.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+left-pad-1.3.0+node__modules+left-pad", + "~npm~debug@4.3.4 ms": "prod 2.1.2 ~npm~ms@2.1.2", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~supports-color@7.2.0 has-flag": "prod ^4.0.0 ~npm~has-flag@4.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/member-only/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/member-only/case.json new file mode 100644 index 00000000..38c6aba5 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/member-only/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/debug@4.3.4", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/member-only/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/member-only/expected/package.json new file mode 100644 index 00000000..8ea00c2b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/member-only/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/member-only/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/member-only/expected/packages/a/package.json new file mode 100644 index 00000000..6506dc3d --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/member-only/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "file:../../.socket/vendor/npm/11111111-2222-4333-8444-555555555555/debug-4.3.4/node_modules/debug" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/member-only/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/member-only/expected/vlt-lock.json new file mode 100644 index 00000000..8ea8f754 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/member-only/expected/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==","https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",null,null,null,{ "engines": { "node": ">=10" }}], + "~npm~has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==","https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"], + "~npm~is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==","https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==","https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+debug-4.3.4+node__modules+debug": [0,"debug",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/debug-4.3.4/node_modules/debug"] + }, + "edges": { + "file~_d is-number": "dev 7.0.0 ~npm~is-number@7.0.0", + "file~_d escape-string-regexp": "optional 4.0.0 ~npm~escape-string-regexp@4.0.0", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d supports-color": "prod 7.2.0 ~npm~supports-color@7.2.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "workspace~packages+a left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "workspace~packages+a debug": "prod file:../../.socket/vendor/npm/11111111-2222-4333-8444-555555555555/debug-4.3.4/node_modules/debug file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+debug-4.3.4+node__modules+debug", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~supports-color@7.2.0 has-flag": "prod ^4.0.0 ~npm~has-flag@4.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0", + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+debug-4.3.4+node__modules+debug ms": "prod 2.1.2 ~npm~ms@2.1.2" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/optional-edge/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/optional-edge/case.json new file mode 100644 index 00000000..38775dc8 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/optional-edge/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/escape-string-regexp@4.0.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/optional-edge/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/optional-edge/expected/package.json new file mode 100644 index 00000000..ef388d4c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/optional-edge/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/escape-string-regexp-4.0.0/node_modules/escape-string-regexp" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/optional-edge/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/optional-edge/expected/packages/a/package.json new file mode 100644 index 00000000..90360a71 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/optional-edge/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/optional-edge/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/optional-edge/expected/vlt-lock.json new file mode 100644 index 00000000..f505e21c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/optional-edge/expected/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==","https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"], + "~npm~is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==","https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==","https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+escape-string-regexp-4.0.0+node__modules+escape-string-regexp": [1,"escape-string-regexp",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/escape-string-regexp-4.0.0/node_modules/escape-string-regexp",null,null,null,{ "engines": { "node": ">=10" }}] + }, + "edges": { + "file~_d is-number": "dev 7.0.0 ~npm~is-number@7.0.0", + "file~_d escape-string-regexp": "optional file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/escape-string-regexp-4.0.0/node_modules/escape-string-regexp file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+escape-string-regexp-4.0.0+node__modules+escape-string-regexp", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d supports-color": "prod 7.2.0 ~npm~supports-color@7.2.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "workspace~packages+a debug": "prod 4.3.4 ~npm~debug@4.3.4", + "workspace~packages+a left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "~npm~debug@4.3.4 ms": "prod 2.1.2 ~npm~ms@2.1.2", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~supports-color@7.2.0 has-flag": "prod ^4.0.0 ~npm~has-flag@4.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/peer/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/peer/case.json new file mode 100644 index 00000000..57104202 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/peer/case.json @@ -0,0 +1,6 @@ +{ + "project": "workspace", + "purl": "pkg:npm/use-sync-external-store@1.2.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": "vendor_lock_entry_unsupported" +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/scoped/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/scoped/case.json new file mode 100644 index 00000000..7358fac5 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/scoped/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/@isaacs/string-locale-compare@1.1.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/scoped/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/scoped/expected/package.json new file mode 100644 index 00000000..cb3d0b8c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/scoped/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/@isaacs/string-locale-compare-1.1.0/node_modules/@isaacs/string-locale-compare", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/scoped/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/scoped/expected/packages/a/package.json new file mode 100644 index 00000000..90360a71 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/scoped/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/scoped/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/scoped/expected/vlt-lock.json new file mode 100644 index 00000000..29b490cf --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/scoped/expected/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==","https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",null,null,null,{ "engines": { "node": ">=10" }}], + "~npm~has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==","https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"], + "~npm~is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==","https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==","https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+@isaacs+string-locale-compare-1.1.0+node__modules+@isaacs+string-locale-compare": [0,"@isaacs/string-locale-compare",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/@isaacs/string-locale-compare-1.1.0/node_modules/@isaacs/string-locale-compare"] + }, + "edges": { + "file~_d is-number": "dev 7.0.0 ~npm~is-number@7.0.0", + "file~_d escape-string-regexp": "optional 4.0.0 ~npm~escape-string-regexp@4.0.0", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d supports-color": "prod 7.2.0 ~npm~supports-color@7.2.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "file~_d @isaacs/string-locale-compare": "prod file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/@isaacs/string-locale-compare-1.1.0/node_modules/@isaacs/string-locale-compare file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+@isaacs+string-locale-compare-1.1.0+node__modules+@isaacs+string-locale-compare", + "workspace~packages+a debug": "prod 4.3.4 ~npm~debug@4.3.4", + "workspace~packages+a left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "~npm~debug@4.3.4 ms": "prod 2.1.2 ~npm~ms@2.1.2", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~supports-color@7.2.0 has-flag": "prod ^4.0.0 ~npm~has-flag@4.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/semver/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/semver/case.json new file mode 100644 index 00000000..3d937dc3 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/semver/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/semver@7.6.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/semver/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/semver/expected/package.json new file mode 100644 index 00000000..59ac3005 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/semver/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/semver-7.6.0/node_modules/semver", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/semver/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/semver/expected/packages/a/package.json new file mode 100644 index 00000000..90360a71 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/semver/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/semver/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/semver/expected/vlt-lock.json new file mode 100644 index 00000000..41939d03 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/semver/expected/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==","https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",null,null,null,{ "engines": { "node": ">=10" }}], + "~npm~has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==","https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"], + "~npm~is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==","https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==","https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+semver-7.6.0+node__modules+semver": [0,"semver",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/semver-7.6.0/node_modules/semver",null,null,null,null,{ "semver": "bin/semver.js"}] + }, + "edges": { + "file~_d is-number": "dev 7.0.0 ~npm~is-number@7.0.0", + "file~_d escape-string-regexp": "optional 4.0.0 ~npm~escape-string-regexp@4.0.0", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d supports-color": "prod 7.2.0 ~npm~supports-color@7.2.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "file~_d semver": "prod file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/semver-7.6.0/node_modules/semver file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+semver-7.6.0+node__modules+semver", + "workspace~packages+a debug": "prod 4.3.4 ~npm~debug@4.3.4", + "workspace~packages+a left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "~npm~debug@4.3.4 ms": "prod 2.1.2 ~npm~ms@2.1.2", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~supports-color@7.2.0 has-flag": "prod ^4.0.0 ~npm~has-flag@4.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0", + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+semver-7.6.0+node__modules+semver lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/supports-color/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/supports-color/case.json new file mode 100644 index 00000000..a0f4f351 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/supports-color/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/supports-color@7.2.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/supports-color/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/supports-color/expected/package.json new file mode 100644 index 00000000..6796bab6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/supports-color/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/supports-color-7.2.0/node_modules/supports-color", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/supports-color/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/supports-color/expected/packages/a/package.json new file mode 100644 index 00000000..90360a71 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/supports-color/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/supports-color/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/supports-color/expected/vlt-lock.json new file mode 100644 index 00000000..aca30652 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/supports-color/expected/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==","https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",null,null,null,{ "engines": { "node": ">=10" }}], + "~npm~has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==","https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"], + "~npm~is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==","https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+supports-color-7.2.0+node__modules+supports-color": [0,"supports-color",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/supports-color-7.2.0/node_modules/supports-color"] + }, + "edges": { + "file~_d is-number": "dev 7.0.0 ~npm~is-number@7.0.0", + "file~_d escape-string-regexp": "optional 4.0.0 ~npm~escape-string-regexp@4.0.0", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "file~_d supports-color": "prod file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/supports-color-7.2.0/node_modules/supports-color file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+supports-color-7.2.0+node__modules+supports-color", + "workspace~packages+a debug": "prod 4.3.4 ~npm~debug@4.3.4", + "workspace~packages+a left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "~npm~debug@4.3.4 ms": "prod 2.1.2 ~npm~ms@2.1.2", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0", + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+supports-color-7.2.0+node__modules+supports-color has-flag": "prod ^4.0.0 ~npm~has-flag@4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/transitive/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/transitive/case.json new file mode 100644 index 00000000..6b2b1999 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/cases/transitive/case.json @@ -0,0 +1,6 @@ +{ + "project": "workspace", + "purl": "pkg:npm/has-flag@4.0.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": "vendor_vlt_transitive_unsupported" +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/alias/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/alias/package.json new file mode 100644 index 00000000..25865836 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/alias/package.json @@ -0,0 +1,9 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "lp": "npm:left-pad@1.3.0", + "react": "18.2.0", + "usx": "npm:use-sync-external-store@1.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/alias/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/alias/vlt-lock.json new file mode 100644 index 00000000..7c3ca797 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/alias/vlt-lock.json @@ -0,0 +1,23 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"] + }, + "edges": { + "file~_d lp": "prod npm:left-pad@1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d usx": "prod npm:use-sync-external-store@1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/alias/vlt.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/alias/vlt.json new file mode 100644 index 00000000..edaf227f --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/alias/vlt.json @@ -0,0 +1,7 @@ +{ + "config": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/workspace/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/workspace/package.json new file mode 100644 index 00000000..8ea00c2b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/workspace/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/workspace/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/workspace/packages/a/package.json new file mode 100644 index 00000000..90360a71 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/workspace/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/workspace/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/workspace/vlt-lock.json new file mode 100644 index 00000000..b786105f --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/workspace/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==","https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",null,null,null,{ "engines": { "node": ">=10" }}], + "~npm~has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==","https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"], + "~npm~is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==","https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==","https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"] + }, + "edges": { + "file~_d is-number": "dev 7.0.0 ~npm~is-number@7.0.0", + "file~_d escape-string-regexp": "optional 4.0.0 ~npm~escape-string-regexp@4.0.0", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d supports-color": "prod 7.2.0 ~npm~supports-color@7.2.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "workspace~packages+a debug": "prod 4.3.4 ~npm~debug@4.3.4", + "workspace~packages+a left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "~npm~debug@4.3.4 ms": "prod 2.1.2 ~npm~ms@2.1.2", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~supports-color@7.2.0 has-flag": "prod ^4.0.0 ~npm~has-flag@4.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/workspace/vlt.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/workspace/vlt.json new file mode 100644 index 00000000..c34a23cf --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.0.10/projects/workspace/vlt.json @@ -0,0 +1,8 @@ +{ + "config": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "workspaces": "packages/*" +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/alias-selfref-peer/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/alias-selfref-peer/case.json new file mode 100644 index 00000000..0d80425a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/alias-selfref-peer/case.json @@ -0,0 +1,6 @@ +{ + "project": "alias", + "purl": "pkg:npm/use-sync-external-store@1.2.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": "vendor_lock_entry_unsupported" +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/alias/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/alias/case.json new file mode 100644 index 00000000..6c9a0f29 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/alias/case.json @@ -0,0 +1,7 @@ +{ + "project": "alias", + "purl": "pkg:npm/left-pad@1.3.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/alias/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/alias/expected/package.json new file mode 100644 index 00000000..2246ce1e --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/alias/expected/package.json @@ -0,0 +1,9 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "lp": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad", + "react": "18.2.0", + "usx": "npm:use-sync-external-store@1.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/alias/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/alias/expected/vlt-lock.json new file mode 100644 index 00000000..416505cf --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/alias/expected/vlt-lock.json @@ -0,0 +1,23 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+left-pad-1.3.0+node__modules+left-pad": [0,"left-pad",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad"] + }, + "edges": { + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d usx": "prod npm:use-sync-external-store@1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "file~_d lp": "prod file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+left-pad-1.3.0+node__modules+left-pad", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/dev-edge/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/dev-edge/case.json new file mode 100644 index 00000000..dead58d7 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/dev-edge/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/is-number@7.0.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/dev-edge/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/dev-edge/expected/package.json new file mode 100644 index 00000000..b5085327 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/dev-edge/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/is-number-7.0.0/node_modules/is-number" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/dev-edge/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/dev-edge/expected/packages/a/package.json new file mode 100644 index 00000000..90360a71 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/dev-edge/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/dev-edge/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/dev-edge/expected/vlt-lock.json new file mode 100644 index 00000000..199e129a --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/dev-edge/expected/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==","https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",null,null,null,{ "engines": { "node": ">=10" }}], + "~npm~has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==","https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==","https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+is-number-7.0.0+node__modules+is-number": [2,"is-number",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/is-number-7.0.0/node_modules/is-number"] + }, + "edges": { + "file~_d is-number": "dev file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/is-number-7.0.0/node_modules/is-number file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+is-number-7.0.0+node__modules+is-number", + "file~_d escape-string-regexp": "optional 4.0.0 ~npm~escape-string-regexp@4.0.0", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d supports-color": "prod 7.2.0 ~npm~supports-color@7.2.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "workspace~packages+a debug": "prod 4.3.4 ~npm~debug@4.3.4", + "workspace~packages+a left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "~npm~debug@4.3.4 ms": "prod 2.1.2 ~npm~ms@2.1.2", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~supports-color@7.2.0 has-flag": "prod ^4.0.0 ~npm~has-flag@4.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/left-pad/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/left-pad/case.json new file mode 100644 index 00000000..c98fb091 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/left-pad/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/left-pad@1.3.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/left-pad/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/left-pad/expected/package.json new file mode 100644 index 00000000..ee2d0698 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/left-pad/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/left-pad/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/left-pad/expected/packages/a/package.json new file mode 100644 index 00000000..17bfc18d --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/left-pad/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "file:../../.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/left-pad/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/left-pad/expected/vlt-lock.json new file mode 100644 index 00000000..60206682 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/left-pad/expected/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==","https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",null,null,null,{ "engines": { "node": ">=10" }}], + "~npm~has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==","https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"], + "~npm~is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==","https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==","https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+left-pad-1.3.0+node__modules+left-pad": [0,"left-pad",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad"] + }, + "edges": { + "file~_d is-number": "dev 7.0.0 ~npm~is-number@7.0.0", + "file~_d escape-string-regexp": "optional 4.0.0 ~npm~escape-string-regexp@4.0.0", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d supports-color": "prod 7.2.0 ~npm~supports-color@7.2.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "file~_d left-pad": "prod file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+left-pad-1.3.0+node__modules+left-pad", + "workspace~packages+a debug": "prod 4.3.4 ~npm~debug@4.3.4", + "workspace~packages+a left-pad": "prod file:../../.socket/vendor/npm/11111111-2222-4333-8444-555555555555/left-pad-1.3.0/node_modules/left-pad file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+left-pad-1.3.0+node__modules+left-pad", + "~npm~debug@4.3.4 ms": "prod 2.1.2 ~npm~ms@2.1.2", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~supports-color@7.2.0 has-flag": "prod ^4.0.0 ~npm~has-flag@4.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/member-only/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/member-only/case.json new file mode 100644 index 00000000..38c6aba5 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/member-only/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/debug@4.3.4", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/member-only/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/member-only/expected/package.json new file mode 100644 index 00000000..8ea00c2b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/member-only/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/member-only/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/member-only/expected/packages/a/package.json new file mode 100644 index 00000000..6506dc3d --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/member-only/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "file:../../.socket/vendor/npm/11111111-2222-4333-8444-555555555555/debug-4.3.4/node_modules/debug" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/member-only/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/member-only/expected/vlt-lock.json new file mode 100644 index 00000000..8ea8f754 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/member-only/expected/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==","https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",null,null,null,{ "engines": { "node": ">=10" }}], + "~npm~has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==","https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"], + "~npm~is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==","https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==","https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+debug-4.3.4+node__modules+debug": [0,"debug",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/debug-4.3.4/node_modules/debug"] + }, + "edges": { + "file~_d is-number": "dev 7.0.0 ~npm~is-number@7.0.0", + "file~_d escape-string-regexp": "optional 4.0.0 ~npm~escape-string-regexp@4.0.0", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d supports-color": "prod 7.2.0 ~npm~supports-color@7.2.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "workspace~packages+a left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "workspace~packages+a debug": "prod file:../../.socket/vendor/npm/11111111-2222-4333-8444-555555555555/debug-4.3.4/node_modules/debug file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+debug-4.3.4+node__modules+debug", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~supports-color@7.2.0 has-flag": "prod ^4.0.0 ~npm~has-flag@4.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0", + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+debug-4.3.4+node__modules+debug ms": "prod 2.1.2 ~npm~ms@2.1.2" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/optional-edge/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/optional-edge/case.json new file mode 100644 index 00000000..38775dc8 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/optional-edge/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/escape-string-regexp@4.0.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/optional-edge/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/optional-edge/expected/package.json new file mode 100644 index 00000000..ef388d4c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/optional-edge/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/escape-string-regexp-4.0.0/node_modules/escape-string-regexp" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/optional-edge/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/optional-edge/expected/packages/a/package.json new file mode 100644 index 00000000..90360a71 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/optional-edge/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/optional-edge/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/optional-edge/expected/vlt-lock.json new file mode 100644 index 00000000..f505e21c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/optional-edge/expected/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==","https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"], + "~npm~is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==","https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==","https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+escape-string-regexp-4.0.0+node__modules+escape-string-regexp": [1,"escape-string-regexp",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/escape-string-regexp-4.0.0/node_modules/escape-string-regexp",null,null,null,{ "engines": { "node": ">=10" }}] + }, + "edges": { + "file~_d is-number": "dev 7.0.0 ~npm~is-number@7.0.0", + "file~_d escape-string-regexp": "optional file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/escape-string-regexp-4.0.0/node_modules/escape-string-regexp file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+escape-string-regexp-4.0.0+node__modules+escape-string-regexp", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d supports-color": "prod 7.2.0 ~npm~supports-color@7.2.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "workspace~packages+a debug": "prod 4.3.4 ~npm~debug@4.3.4", + "workspace~packages+a left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "~npm~debug@4.3.4 ms": "prod 2.1.2 ~npm~ms@2.1.2", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~supports-color@7.2.0 has-flag": "prod ^4.0.0 ~npm~has-flag@4.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/peer/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/peer/case.json new file mode 100644 index 00000000..57104202 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/peer/case.json @@ -0,0 +1,6 @@ +{ + "project": "workspace", + "purl": "pkg:npm/use-sync-external-store@1.2.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": "vendor_lock_entry_unsupported" +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/scoped/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/scoped/case.json new file mode 100644 index 00000000..7358fac5 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/scoped/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/@isaacs/string-locale-compare@1.1.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/scoped/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/scoped/expected/package.json new file mode 100644 index 00000000..cb3d0b8c --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/scoped/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/@isaacs/string-locale-compare-1.1.0/node_modules/@isaacs/string-locale-compare", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/scoped/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/scoped/expected/packages/a/package.json new file mode 100644 index 00000000..90360a71 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/scoped/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/scoped/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/scoped/expected/vlt-lock.json new file mode 100644 index 00000000..29b490cf --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/scoped/expected/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==","https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",null,null,null,{ "engines": { "node": ">=10" }}], + "~npm~has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==","https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"], + "~npm~is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==","https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==","https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+@isaacs+string-locale-compare-1.1.0+node__modules+@isaacs+string-locale-compare": [0,"@isaacs/string-locale-compare",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/@isaacs/string-locale-compare-1.1.0/node_modules/@isaacs/string-locale-compare"] + }, + "edges": { + "file~_d is-number": "dev 7.0.0 ~npm~is-number@7.0.0", + "file~_d escape-string-regexp": "optional 4.0.0 ~npm~escape-string-regexp@4.0.0", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d supports-color": "prod 7.2.0 ~npm~supports-color@7.2.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "file~_d @isaacs/string-locale-compare": "prod file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/@isaacs/string-locale-compare-1.1.0/node_modules/@isaacs/string-locale-compare file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+@isaacs+string-locale-compare-1.1.0+node__modules+@isaacs+string-locale-compare", + "workspace~packages+a debug": "prod 4.3.4 ~npm~debug@4.3.4", + "workspace~packages+a left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "~npm~debug@4.3.4 ms": "prod 2.1.2 ~npm~ms@2.1.2", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~supports-color@7.2.0 has-flag": "prod ^4.0.0 ~npm~has-flag@4.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/semver/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/semver/case.json new file mode 100644 index 00000000..3d937dc3 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/semver/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/semver@7.6.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/semver/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/semver/expected/package.json new file mode 100644 index 00000000..59ac3005 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/semver/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/semver-7.6.0/node_modules/semver", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/semver/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/semver/expected/packages/a/package.json new file mode 100644 index 00000000..90360a71 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/semver/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/semver/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/semver/expected/vlt-lock.json new file mode 100644 index 00000000..41939d03 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/semver/expected/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==","https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",null,null,null,{ "engines": { "node": ">=10" }}], + "~npm~has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==","https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"], + "~npm~is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==","https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==","https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+semver-7.6.0+node__modules+semver": [0,"semver",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/semver-7.6.0/node_modules/semver",null,null,null,null,{ "semver": "bin/semver.js"}] + }, + "edges": { + "file~_d is-number": "dev 7.0.0 ~npm~is-number@7.0.0", + "file~_d escape-string-regexp": "optional 4.0.0 ~npm~escape-string-regexp@4.0.0", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d supports-color": "prod 7.2.0 ~npm~supports-color@7.2.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "file~_d semver": "prod file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/semver-7.6.0/node_modules/semver file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+semver-7.6.0+node__modules+semver", + "workspace~packages+a debug": "prod 4.3.4 ~npm~debug@4.3.4", + "workspace~packages+a left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "~npm~debug@4.3.4 ms": "prod 2.1.2 ~npm~ms@2.1.2", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~supports-color@7.2.0 has-flag": "prod ^4.0.0 ~npm~has-flag@4.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0", + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+semver-7.6.0+node__modules+semver lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/supports-color/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/supports-color/case.json new file mode 100644 index 00000000..a0f4f351 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/supports-color/case.json @@ -0,0 +1,7 @@ +{ + "project": "workspace", + "purl": "pkg:npm/supports-color@7.2.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": null, + "ciChurn": [] +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/supports-color/expected/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/supports-color/expected/package.json new file mode 100644 index 00000000..6796bab6 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/supports-color/expected/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/supports-color-7.2.0/node_modules/supports-color", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/supports-color/expected/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/supports-color/expected/packages/a/package.json new file mode 100644 index 00000000..90360a71 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/supports-color/expected/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/supports-color/expected/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/supports-color/expected/vlt-lock.json new file mode 100644 index 00000000..aca30652 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/supports-color/expected/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==","https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",null,null,null,{ "engines": { "node": ">=10" }}], + "~npm~has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==","https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"], + "~npm~is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==","https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"], + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+supports-color-7.2.0+node__modules+supports-color": [0,"supports-color",null,".socket/vendor/npm/11111111-2222-4333-8444-555555555555/supports-color-7.2.0/node_modules/supports-color"] + }, + "edges": { + "file~_d is-number": "dev 7.0.0 ~npm~is-number@7.0.0", + "file~_d escape-string-regexp": "optional 4.0.0 ~npm~escape-string-regexp@4.0.0", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "file~_d supports-color": "prod file:./.socket/vendor/npm/11111111-2222-4333-8444-555555555555/supports-color-7.2.0/node_modules/supports-color file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+supports-color-7.2.0+node__modules+supports-color", + "workspace~packages+a debug": "prod 4.3.4 ~npm~debug@4.3.4", + "workspace~packages+a left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "~npm~debug@4.3.4 ms": "prod 2.1.2 ~npm~ms@2.1.2", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0", + "file~.socket+vendor+npm+11111111-2222-4333-8444-555555555555+supports-color-7.2.0+node__modules+supports-color has-flag": "prod ^4.0.0 ~npm~has-flag@4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/transitive/case.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/transitive/case.json new file mode 100644 index 00000000..6b2b1999 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/cases/transitive/case.json @@ -0,0 +1,6 @@ +{ + "project": "workspace", + "purl": "pkg:npm/has-flag@4.0.0", + "uuid": "11111111-2222-4333-8444-555555555555", + "refusal": "vendor_vlt_transitive_unsupported" +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/alias/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/alias/package.json new file mode 100644 index 00000000..25865836 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/alias/package.json @@ -0,0 +1,9 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "lp": "npm:left-pad@1.3.0", + "react": "18.2.0", + "usx": "npm:use-sync-external-store@1.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/alias/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/alias/vlt-lock.json new file mode 100644 index 00000000..7c3ca797 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/alias/vlt-lock.json @@ -0,0 +1,23 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"] + }, + "edges": { + "file~_d lp": "prod npm:left-pad@1.3.0 ~npm~left-pad@1.3.0", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d usx": "prod npm:use-sync-external-store@1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/alias/vlt.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/alias/vlt.json new file mode 100644 index 00000000..edaf227f --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/alias/vlt.json @@ -0,0 +1,7 @@ +{ + "config": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/workspace/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/workspace/package.json new file mode 100644 index 00000000..8ea00c2b --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/workspace/package.json @@ -0,0 +1,19 @@ +{ + "name": "root", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "ms": "2.1.3", + "supports-color": "7.2.0", + "@isaacs/string-locale-compare": "1.1.0", + "semver": "7.6.0", + "react": "18.2.0", + "use-sync-external-store": "1.2.0" + }, + "devDependencies": { + "is-number": "7.0.0" + }, + "optionalDependencies": { + "escape-string-regexp": "4.0.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/workspace/packages/a/package.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/workspace/packages/a/package.json new file mode 100644 index 00000000..90360a71 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/workspace/packages/a/package.json @@ -0,0 +1,8 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0", + "debug": "4.3.4" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/workspace/vlt-lock.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/workspace/vlt-lock.json new file mode 100644 index 00000000..b786105f --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/workspace/vlt-lock.json @@ -0,0 +1,46 @@ +{ + "lockfileVersion": 1, + "options": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "nodes": { + "~npm~@isaacs+string-locale-compare@1.1.0": [0,"@isaacs/string-locale-compare","sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==","https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz"], + "~npm~debug@4.3.4": [0,"debug","sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==","https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"], + "~npm~escape-string-regexp@4.0.0": [1,"escape-string-regexp","sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==","https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",null,null,null,{ "engines": { "node": ">=10" }}], + "~npm~has-flag@4.0.0": [0,"has-flag","sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==","https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"], + "~npm~is-number@7.0.0": [2,"is-number","sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==","https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"], + "~npm~js-tokens@4.0.0": [0,"js-tokens","sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==","https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"], + "~npm~left-pad@1.3.0": [0,"left-pad","sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==","https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz"], + "~npm~loose-envify@1.4.0": [0,"loose-envify","sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==","https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",null,null,null,null,{ "loose-envify": "cli.js"}], + "~npm~lru-cache@6.0.0": [0,"lru-cache","sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==","https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"], + "~npm~ms@2.1.2": [0,"ms","sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==","https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"], + "~npm~ms@2.1.3": [0,"ms","sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==","https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"], + "~npm~react@18.2.0": [0,"react","sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==","https://registry.npmjs.org/react/-/react-18.2.0.tgz"], + "~npm~semver@7.6.0": [0,"semver","sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==","https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",null,null,null,null,{ "semver": "bin/semver.js"}], + "~npm~supports-color@7.2.0": [0,"supports-color","sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==","https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"], + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba": [0,"use-sync-external-store","sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==","https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"], + "~npm~yallist@4.0.0": [0,"yallist","sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==","https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"] + }, + "edges": { + "file~_d is-number": "dev 7.0.0 ~npm~is-number@7.0.0", + "file~_d escape-string-regexp": "optional 4.0.0 ~npm~escape-string-regexp@4.0.0", + "file~_d @isaacs/string-locale-compare": "prod 1.1.0 ~npm~@isaacs+string-locale-compare@1.1.0", + "file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "file~_d ms": "prod 2.1.3 ~npm~ms@2.1.3", + "file~_d react": "prod 18.2.0 ~npm~react@18.2.0", + "file~_d semver": "prod 7.6.0 ~npm~semver@7.6.0", + "file~_d supports-color": "prod 7.2.0 ~npm~supports-color@7.2.0", + "file~_d use-sync-external-store": "prod 1.2.0 ~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba", + "workspace~packages+a debug": "prod 4.3.4 ~npm~debug@4.3.4", + "workspace~packages+a left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0", + "~npm~debug@4.3.4 ms": "prod 2.1.2 ~npm~ms@2.1.2", + "~npm~loose-envify@1.4.0 js-tokens": "prod ^3.0.0 || ^4.0.0 ~npm~js-tokens@4.0.0", + "~npm~lru-cache@6.0.0 yallist": "prod ^4.0.0 ~npm~yallist@4.0.0", + "~npm~react@18.2.0 loose-envify": "prod ^1.1.0 ~npm~loose-envify@1.4.0", + "~npm~semver@7.6.0 lru-cache": "prod ^6.0.0 ~npm~lru-cache@6.0.0", + "~npm~supports-color@7.2.0 has-flag": "prod ^4.0.0 ~npm~has-flag@4.0.0", + "~npm~use-sync-external-store@1.2.0~peer.0df72515a50372ba react": "peer ^16.8.0 || ^17.0.0 || ^18.0.0 ~npm~react@18.2.0" + } +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/workspace/vlt.json b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/workspace/vlt.json new file mode 100644 index 00000000..c34a23cf --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/1.2.0/projects/workspace/vlt.json @@ -0,0 +1,8 @@ +{ + "config": { + "registries": { + "npm": "https://registry.npmjs.org/" + } + }, + "workspaces": "packages/*" +} diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/regenerate.sh b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/regenerate.sh new file mode 100755 index 00000000..ab67f0ad --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/regenerate.sh @@ -0,0 +1,141 @@ +#!/bin/bash +# Rebuild the vendored-wiring fixtures with real vlt (the byte-stability +# oracle of tests/vlt_locks.rs). For each vlt version and target: +# 1. a cold `vlt install` of the project writes input/vlt-lock.json; +# 2. surgery.mjs wires the target to its D19 directory artifact; +# 3. `vlt ci` from a clean node_modules writes expected/vlt-lock.json. +# Inputs live once per project under /projects//; each +# /cases// holds case.json and, unless refused, expected/. +# A lock ci rewrites is recorded in case.json `ciChurn` (pairs of the line +# surgery wrote and the line vlt wrote); the expected lock is then taken +# after a second `ci`, which must leave it unchanged. +# +# usage: VLT_BIN_DIR=/node_modules/vlt/vlt.js> ./regenerate.sh +set -euo pipefail +export LANG=C LC_ALL=C VLT_TELEMETRY=0 DO_NOT_TRACK=1 CI=1 NO_COLOR=1 +unset VLT_STORE_LINKER +HERE=$(cd "$(dirname "$0")" && pwd) +: "${VLT_BIN_DIR:?set VLT_BIN_DIR}" +WORK=$(mktemp -d) +trap 'rm -rf "$WORK"' EXIT +UUID=11111111-2222-4333-8444-555555555555 +VERSIONS=${VERSIONS:-"1.2.0 1.0.10 1.0.0-rc.14"} + +vlt() { + local version=$1 xdg=$2 + shift 2 + mkdir -p "$xdg"/{cache,config,data,state,run} + XDG_CACHE_HOME=$xdg/cache XDG_CONFIG_HOME=$xdg/config XDG_DATA_HOME=$xdg/data \ + XDG_STATE_HOME=$xdg/state XDG_RUNTIME_DIR=$xdg/run VLT_CACHE=$xdg/cache/vlt \ + node --no-warnings "$VLT_BIN_DIR/$version/node_modules/vlt/vlt.js" "$@" "$dir/vlt.json" + json '{"name":"root","version":"1.0.0","dependencies":{"left-pad":"1.3.0","ms":"2.1.3","supports-color":"7.2.0","@isaacs/string-locale-compare":"1.1.0","semver":"7.6.0","react":"18.2.0","use-sync-external-store":"1.2.0"},"devDependencies":{"is-number":"7.0.0"},"optionalDependencies":{"escape-string-regexp":"4.0.0"}}' >"$dir/package.json" + json '{"name":"a","version":"1.0.0","dependencies":{"left-pad":"1.3.0","debug":"4.3.4"}}' >"$dir/packages/a/package.json" + ;; + alias) + json "{${cfg}}" >"$dir/vlt.json" + json '{"name":"root","version":"1.0.0","dependencies":{"lp":"npm:left-pad@1.3.0","react":"18.2.0","usx":"npm:use-sync-external-store@1.2.0"}}' >"$dir/package.json" + ;; + esac +} + +# case name, project, target +CASES="left-pad:workspace:left-pad@1.3.0 +supports-color:workspace:supports-color@7.2.0 +scoped:workspace:@isaacs/string-locale-compare@1.1.0 +semver:workspace:semver@7.6.0 +dev-edge:workspace:is-number@7.0.0 +optional-edge:workspace:escape-string-regexp@4.0.0 +member-only:workspace:debug@4.3.4 +peer:workspace:use-sync-external-store@1.2.0 +transitive:workspace:has-flag@4.0.0 +alias:alias:left-pad@1.3.0 +alias-selfref-peer:alias:use-sync-external-store@1.2.0" + +copy_inputs() { + local from=$1 to=$2 + mkdir -p "$to" + (cd "$from" && find . -name node_modules -prune -o -type f \( -name 'vlt-lock.json' -o -name 'vlt.json' -o -name 'package.json' \) -print) | + while read -r f; do mkdir -p "$to/$(dirname "$f")"; cp "$from/$f" "$to/$f"; done +} + +for version in $VERSIONS; do + for project in workspace alias; do + base=$WORK/$version/$project + write_project "$project" "$version" "$base" + (cd "$base" && vlt "$version" "$WORK/xdg-$version" install >"$WORK/$version-$project-install.log" 2>&1) + rm -rf "$HERE/$version/projects/$project" + copy_inputs "$base" "$HERE/$version/projects/$project" + done + echo "$CASES" | while IFS=: read -r case project target; do + base=$WORK/$version/$project + out=$HERE/$version/cases/$case + rm -rf "$out" + mkdir -p "$out" + run=$WORK/$version/run-$case + copy_inputs "$base" "$run" + verdict=$(node "$HERE/surgery.mjs" "$run" "$target" "$UUID") + refusal=$(node -e 'const v=JSON.parse(process.argv[1]); process.stdout.write(v.refusal ?? "")' "$verdict") + if [ -n "$refusal" ]; then + json "{\"project\":\"$project\",\"purl\":\"pkg:npm/$target\",\"uuid\":\"$UUID\",\"refusal\":\"$refusal\"}" >"$out/case.json" + echo "$version $case: refused $refusal" + continue + fi + rel=$(node -e 'process.stdout.write(JSON.parse(process.argv[1]).rel)' "$verdict") + name=${target%@*} + installed=$(node -e ' + const fs = require("fs"), path = require("path"); + const [store, name, version] = process.argv.slice(1); + for (const id of fs.readdirSync(store)) { + const dir = path.join(store, id, "node_modules", name); + try { + if (JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")).version === version) { + process.stdout.write(dir); + process.exit(0); + } + } catch {} + } + process.exit(1); + ' "$base/node_modules/.vlt" "$name" "${target##*@}") + mkdir -p "$run/$(dirname "$rel")" + cp -R "$installed" "$run/$rel" + rm -rf "$run/$rel/node_modules" + node -e 'const f=process.argv[1]; const d=JSON.parse(require("fs").readFileSync(f,"utf8")); delete d.devDependencies; require("fs").writeFileSync(f, JSON.stringify(d,null,2)+"\n")' "$run/$rel/package.json" + printf '!*\n**/node_modules/*/node_modules/\n**/node_modules/@*/*/node_modules/\n' >"$run/.socket/vendor/npm/$UUID/.gitignore" + printf '* -text\n' >"$run/.socket/vendor/npm/$UUID/.gitattributes" + cp "$run/vlt-lock.json" "$WORK/surgery.json" + (cd "$run" && vlt "$version" "$WORK/xdg-$version" ci >"$WORK/$version-$case-ci.log" 2>&1) + churn='[]' + if ! cmp -s "$WORK/surgery.json" "$run/vlt-lock.json"; then + churn=$(node -e ' + const fs = require("fs"); + const a = fs.readFileSync(process.argv[1], "utf8").split("\n"); + const b = fs.readFileSync(process.argv[2], "utf8").split("\n"); + if (a.length !== b.length) { console.error("line count changed"); process.exit(1) } + const pairs = a.map((l, i) => [l, b[i]]).filter(([x, y]) => x !== y); + process.stdout.write(JSON.stringify(pairs)); + ' "$WORK/surgery.json" "$run/vlt-lock.json") + cp "$run/vlt-lock.json" "$WORK/ci1.json" + (cd "$run" && rm -rf node_modules packages/a/node_modules && vlt "$version" "$WORK/xdg-$version" ci >"$WORK/$version-$case-ci2.log" 2>&1) + cmp -s "$WORK/ci1.json" "$run/vlt-lock.json" || { echo "$version $case: second ci changed the lock" >&2; exit 1; } + echo "$version $case: ci churn $churn" + else + echo "$version $case: byte-stable" + fi + json "{\"project\":\"$project\",\"purl\":\"pkg:npm/$target\",\"uuid\":\"$UUID\",\"refusal\":null,\"ciChurn\":$churn}" >"$out/case.json" + copy_inputs "$run" "$out/expected" + rm -f "$out/expected/vlt.json" + done +done diff --git a/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/surgery.mjs b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/surgery.mjs new file mode 100644 index 00000000..65e2f9d4 --- /dev/null +++ b/crates/socket-patch-core/tests/fixtures/vendor/npm/vlt/surgery.mjs @@ -0,0 +1,242 @@ +// Independent JS reading of DESIGN §4.5 (vendored vlt wiring of a direct +// target), used only to build the byte-stability fixtures: its output is +// handed to real `vlt ci`, and the lock vlt writes back is the expected +// lock. Orders with Node's own `localeCompare(…, 'en')`, like vlt. +// +// usage: node surgery.mjs +// prints {"refusal": code, "detail": …} or {"rel": …, "fileId": …} +import fs from 'node:fs' +import path from 'node:path' + +const [proj, target, uuid] = process.argv.slice(2) +const at = target.lastIndexOf('@') +const name = target.slice(0, at) +const version = target.slice(at + 1) + +const lockPath = path.join(proj, 'vlt-lock.json') +const text = fs.readFileSync(lockPath, 'utf8') +const lock = JSON.parse(text) +const v1 = lock.lockfileVersion === 1 +const D = v1 ? '~' : '·' +const options = lock.options ?? {} + +const TILDE_ESC = { + _: '__', '+': '_p', '\\': '_b', ':': '_c', '~': '_t', '<': '_l', '>': '_g', + '"': '_q', '|': '_i', '?': '_m', '*': '_a', ' ': '_s', +} +const TILDE_UNESC = Object.fromEntries( + Object.entries(TILDE_ESC).map(([k, v]) => [v, k]), +) +const enc = s => { + if (!v1) { + return encodeURIComponent(s).replaceAll('%40', '@').replaceAll('%2F', '§') + } + const out = [...s].map(c => (c === '/' ? '+' : (TILDE_ESC[c] ?? c))).join('') + return out.endsWith('.') ? out.slice(0, -1) + '_d' : out +} +const dec = s => { + if (!v1) { + return decodeURIComponent(s.replaceAll('@', '%40').replaceAll('§', '%2F')) + } + let out = '' + for (let i = 0; i < s.length; i++) { + if (s[i] === '_') { + const two = s.slice(i, i + 2) + if (two === '_d' && i + 2 === s.length) { + out += '.' + } else if (TILDE_UNESC[two]) { + out += TILDE_UNESC[two] + } else { + throw new Error('undecodable ' + s) + } + i++ + } else { + out += s[i] === '+' ? '/' : s[i] + } + } + return out +} + +const lines = text.split('\n') +const block = header => { + const open = lines.findIndex(l => l.replace(/\r$/, '') === ` "${header}": {`) + let close = open + 1 + while (!/^ },?\r?$/.test(lines[close])) close++ + return [open, close] +} +const ENTRY = /^ ("(?:[^"\\]|\\.)*"): (.*?)(,?)(\r?)$/ +const entries = ([open, close]) => + lines.slice(open + 1, close).map(l => { + const m = ENTRY.exec(l) + return { key: JSON.parse(m[1]), val: m[2], cr: m[4] } + }) +const [nOpen, nClose] = block('nodes') +const [eOpen, eClose] = block('edges') +let nodes = entries([nOpen, nClose]) +let edges = entries([eOpen, eClose]) + +const refuse = (code, detail) => { + console.log(JSON.stringify({ refusal: code, detail })) + process.exit(0) +} + +const registryOf = key => { + const parts = key.split(D) + if (parts[0] !== '' || parts.length < 3 || parts.length > 4) return null + const second = dec(parts[2]) + const i = second.lastIndexOf('@') + return { + segment: dec(parts[1]), + name: second.slice(0, i), + version: second.slice(i + 1), + extra: parts[3], + } +} +const withSlash = u => (u.endsWith('/') ? u : u + '/') +const isDefault = seg => { + const alias = typeof options['default-registry-alias'] === 'string' + ? options['default-registry-alias'] : 'npm' + if (seg === '' || seg === alias) return true + const reg = options.registry + return typeof reg === 'string' && /^https?:/.test(seg) && + withSlash(seg) === withSlash(reg) +} +const instances = nodes + .map(n => ({ n, r: registryOf(n.key) })) + .filter(({ r }) => r && r.name === name && r.version === version) +if (instances.some(({ r }) => !isDefault(r.segment))) { + refuse('vendor_lock_entry_unsupported', "not from vlt's default registry") +} +if (instances.length > 1 || instances.some(({ r }) => r.extra !== undefined)) { + refuse('vendor_lock_entry_unsupported', 'peer/modifier variants; use --mode hosted') +} +if (instances.length === 0) refuse('vendor_lock_entry_not_found', `${target}`) +const reg = instances[0].n + +const isImporter = id => + id === 'file~_d' || id === 'file·.' || + (id.startsWith('workspace~') && id.length > 10) || + (id.startsWith('workspace·') && id.length > 10) +const parseEdge = e => { + const from = e.key.slice(0, e.key.indexOf(' ')) + const dep = e.key.slice(e.key.indexOf(' ') + 1) + const v = JSON.parse(e.val) + const type = v.slice(0, v.indexOf(' ')) + const to = v.slice(v.lastIndexOf(' ') + 1) + const spec = v.slice(v.indexOf(' ') + 1, v.lastIndexOf(' ')) + return { from, dep, type, spec, to } +} +const inbound = edges.filter(e => parseEdge(e).to === reg.key) +for (const e of inbound) { + const p = parseEdge(e) + if (!isImporter(p.from)) refuse('vendor_vlt_transitive_unsupported', e.key) + if (!['prod', 'dev', 'optional'].includes(p.type)) { + refuse('vendor_lock_entry_unsupported', 'peer edge') + } +} + +const scope = name.startsWith('@') ? name.slice(0, name.indexOf('/') + 1) : '' +const bare = name.slice(scope.length) +const rel = `.socket/vendor/npm/${uuid}/${scope}${bare}-${version}/node_modules/${name}` +const fileId = 'file' + D + enc(rel) + +const tuple = JSON.parse(reg.val) +const tail = reg.val.slice(1, -1) +const elems = [] +{ + let depth = 0, inStr = false, start = 0 + for (let i = 0; i < tail.length; i++) { + const c = tail[i] + if (inStr) { + if (c === '\\') i++ + else if (c === '"') inStr = false + } else if (c === '"') inStr = true + else if (c === '[' || c === '{') depth++ + else if (c === ']' || c === '}') depth-- + else if (c === ',' && depth === 0) { + elems.push(tail.slice(start, i)) + start = i + 1 + } + } + elems.push(tail.slice(start)) +} +if (elems.length !== tuple.length) throw new Error('tuple split') +const fileElems = [elems[0], elems[1], 'null', JSON.stringify(rel), ...elems.slice(4)] +const newNode = { key: fileId, val: `[${fileElems.join(',')}]`, cr: reg.cr } + +const pkgs = new Map() +const readPkg = dir => { + if (!pkgs.has(dir)) { + const file = path.join(proj, dir, 'package.json') + pkgs.set(dir, { file, json: JSON.parse(fs.readFileSync(file, 'utf8')) }) + } + return pkgs.get(dir) +} +const FIELD = { prod: 'dependencies', dev: 'devDependencies', optional: 'optionalDependencies' } +const pkgEdits = [] +const importerEdges = [] +for (const e of inbound) { + const p = parseEdge(e) + const dir = p.from.startsWith('workspace') ? dec(p.from.slice(10)) : '' + const r = path.posix.relative(dir || '.', rel) + const spec = 'file:' + (r.startsWith('../') ? r : './' + r) + const pkg = readPkg(dir) + const declared = ['dependencies', 'devDependencies', 'optionalDependencies'] + .filter(f => pkg.json[f] && Object.hasOwn(pkg.json[f], p.dep)) + if (declared.length > 1) { + refuse('vendor_lock_entry_unsupported', 'declared in multiple dependency fields') + } + const field = FIELD[p.type] + if (pkg.json[field]?.[p.dep] !== p.spec) refuse('vendor_vlt_lock_out_of_sync', e.key) + pkgEdits.push({ dir, field, dep: p.dep, spec, pkg }) + importerEdges.push({ + key: e.key, + val: JSON.stringify(`${p.type} ${spec} ${fileId}`), + cr: e.cr, + }) +} +pkgEdits.sort((a, b) => + a.dir < b.dir ? -1 : a.dir > b.dir ? 1 + : a.field < b.field ? -1 : a.field > b.field ? 1 + : a.dep < b.dep ? -1 : a.dep > b.dep ? 1 : 0) +const outgoing = edges + .filter(e => parseEdge(e).from === reg.key) + .map(e => ({ key: fileId + e.key.slice(reg.key.length), val: e.val, cr: e.cr })) + +const touchedEdgeKeys = new Set([...inbound, ...edges.filter(e => parseEdge(e).from === reg.key)] + .map(e => e.key)) +nodes = nodes.filter(n => n.key !== reg.key) +edges = edges.filter(e => !touchedEdgeKeys.has(e.key)) + +const coll = (a, b) => a.localeCompare(b, 'en') +const edgeCmp = (a, b) => { + const A = parseEdge(a) + const B = parseEdge(b) + const toA = A.to === 'MISSING' ? '' : A.to + const toB = B.to === 'MISSING' ? '' : B.to + return (Number(isImporter(B.from)) - Number(isImporter(A.from))) || + coll(A.from, B.from) || coll(A.type, B.type) || coll(toA, toB) +} +const insert = (list, items, cmp) => { + for (const it of items) { + let i = list.findIndex(x => cmp(it, x) < 0) + if (i < 0) i = list.length + list.splice(i, 0, it) + } + return list +} +nodes = insert(nodes, [newNode], (a, b) => coll(a.key, b.key)) +edges = insert(edges, [...importerEdges, ...outgoing], edgeCmp) +const render = list => + list.map((x, i) => ` ${JSON.stringify(x.key)}: ${x.val}${i < list.length - 1 ? ',' : ''}${x.cr}`) +const out = [ + ...lines.slice(0, nOpen + 1), ...render(nodes), + ...lines.slice(nClose, eOpen + 1), ...render(edges), + ...lines.slice(eClose), +] +fs.writeFileSync(lockPath, out.join('\n')) +for (const { field, dep, spec, pkg } of pkgEdits) pkg.json[field][dep] = spec +for (const pkg of pkgs.values()) { + fs.writeFileSync(pkg.file, JSON.stringify(pkg.json, null, 2) + '\n') +} +console.log(JSON.stringify({ rel, fileId })) diff --git a/crates/socket-patch-core/tests/vlt_locks.rs b/crates/socket-patch-core/tests/vlt_locks.rs index 151ae319..82fdb17d 100644 --- a/crates/socket-patch-core/tests/vlt_locks.rs +++ b/crates/socket-patch-core/tests/vlt_locks.rs @@ -3,13 +3,26 @@ //! change, and the output stays in vlt's own canonical serialization, so //! vlt's next save leaves it byte-identical. A CRLF checkout of each capture //! gets the same edits, with every line keeping its `\r`. +//! +//! The vendored wiring runs against `tests/fixtures/vendor/npm/vlt/`, whose +//! expected locks real vlt wrote (`regenerate.sh`: an independent surgery, +//! then `vlt ci`), so byte equality proves `vlt ci` keeps the wired lock +//! byte-stable; the revert gives the pre-vendor bytes back. The lock +//! inventory reads the same captures. use std::collections::BTreeMap; use std::fs; use std::path::{Path, PathBuf}; use serde_json::Value; +use socket_patch_core::hash::git_sha256::compute_git_sha256_from_bytes; +use socket_patch_core::manifest::schema::{PatchFileInfo, PatchRecord}; +use socket_patch_core::patch::apply::PatchSources; use socket_patch_core::patch::redirect::{rewrite_registry_redirect, DepOverride}; +use socket_patch_core::vendor::lock_inventory::{inventory_project, LockIntegrity}; +use socket_patch_core::vendor::npm_flavor::{revert_npm_any, vendor_npm_any}; +use socket_patch_core::vendor::VendorOutcome; +use socket_patch_core::vendor::{save_state, VendorEntry, VendorState}; const TOKEN: &str = "11111111-1111-1111-1111-111111111111"; @@ -361,3 +374,496 @@ fn hosted_rewrite_changes_exactly_the_target_slots() { assert_eq!(crlf_result.edits, result.edits, "{version}: CRLF"); } } + +// ── vendored wiring ────────────────────────────────────────────────────── + +const ORIGINAL_JS: &[u8] = b"module.exports = 'orig';\n"; +const PATCHED_JS: &[u8] = b"module.exports = 'patched';\n"; + +fn vendor_root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/vendor/npm/vlt") +} + +struct Case { + version: String, + name: String, + dir: PathBuf, + project: PathBuf, + purl: String, + uuid: String, + refusal: Option, + churn: Vec<(String, String)>, +} + +fn cases() -> Vec { + let mut out = Vec::new(); + for version in ["1.2.0", "1.0.10", "1.0.0-rc.14"] { + let root = vendor_root().join(version); + let mut names: Vec = fs::read_dir(root.join("cases")) + .unwrap() + .map(|e| e.unwrap().file_name().to_string_lossy().into_owned()) + .collect(); + names.sort(); + for name in names { + let dir = root.join("cases").join(&name); + let case: Value = + serde_json::from_str(&fs::read_to_string(dir.join("case.json")).unwrap()).unwrap(); + let churn = case["ciChurn"] + .as_array() + .map(|pairs| { + pairs + .iter() + .map(|p| { + ( + p[0].as_str().unwrap().to_string(), + p[1].as_str().unwrap().to_string(), + ) + }) + .collect() + }) + .unwrap_or_default(); + out.push(Case { + version: version.to_string(), + project: root + .join("projects") + .join(case["project"].as_str().unwrap()), + purl: case["purl"].as_str().unwrap().to_string(), + uuid: case["uuid"].as_str().unwrap().to_string(), + refusal: case["refusal"].as_str().map(str::to_string), + churn, + name, + dir, + }); + } + } + out +} + +/// Every file under `dir`, relative, forward-slashed. +fn tree(dir: &Path) -> BTreeMap> { + let mut out = BTreeMap::new(); + let mut stack = vec![dir.to_path_buf()]; + while let Some(d) = stack.pop() { + for entry in fs::read_dir(&d).unwrap() { + let path = entry.unwrap().path(); + if path.is_dir() { + stack.push(path); + } else { + let rel = path + .strip_prefix(dir) + .unwrap() + .to_string_lossy() + .replace('\\', "/"); + out.insert(rel, fs::read(&path).unwrap()); + } + } + } + out +} + +fn write_tree(root: &Path, files: &BTreeMap>) { + for (rel, bytes) in files { + let path = root.join(rel); + fs::create_dir_all(path.parent().unwrap()).unwrap(); + fs::write(path, bytes).unwrap(); + } +} + +fn name_version(purl: &str) -> (String, String) { + let rest = purl.strip_prefix("pkg:npm/").unwrap(); + let at = rest.rfind('@').unwrap(); + (rest[..at].to_string(), rest[at + 1..].to_string()) +} + +fn patch_record(uuid: &str) -> PatchRecord { + let mut files = std::collections::HashMap::new(); + files.insert( + "package/index.js".to_string(), + PatchFileInfo { + before_hash: compute_git_sha256_from_bytes(ORIGINAL_JS), + after_hash: compute_git_sha256_from_bytes(PATCHED_JS), + }, + ); + PatchRecord { + uuid: uuid.to_string(), + exported_at: String::new(), + files, + vulnerabilities: std::collections::HashMap::new(), + description: String::new(), + license: String::new(), + tier: String::new(), + } +} + +/// A project staged from the case's inputs, an installed copy of the target +/// (with devDependencies, which the artifact must drop) and the patch blob. +struct Staged { + _tmp: tempfile::TempDir, + root: PathBuf, + installed: PathBuf, + blobs: PathBuf, +} + +fn stage(case: &Case, crlf: bool) -> Staged { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path().join("project"); + let mut inputs = tree(&case.project); + if crlf { + let lock = String::from_utf8(inputs["vlt-lock.json"].clone()).unwrap(); + inputs.insert( + "vlt-lock.json".into(), + lock.replace('\n', "\r\n").into_bytes(), + ); + } + write_tree(&root, &inputs); + let (name, version) = name_version(&case.purl); + let installed = tmp.path().join("installed").join(&name); + fs::create_dir_all(&installed).unwrap(); + fs::write( + installed.join("package.json"), + format!( + "{{\n \"name\": \"{name}\",\n \"version\": \"{version}\",\n \"devDependencies\": {{\n \"tap\": \"1.0.0\"\n }},\n \"main\": \"index.js\"\n}}\n" + ), + ) + .unwrap(); + fs::write(installed.join("index.js"), ORIGINAL_JS).unwrap(); + let blobs = tmp.path().join("blobs"); + fs::create_dir_all(&blobs).unwrap(); + fs::write( + blobs.join(compute_git_sha256_from_bytes(PATCHED_JS)), + PATCHED_JS, + ) + .unwrap(); + Staged { + _tmp: tmp, + root, + installed, + blobs, + } +} + +async fn vendor(case: &Case, staged: &Staged) -> VendorOutcome { + let sources = PatchSources { + blobs_path: &staged.blobs, + packages_path: None, + diffs_path: None, + mem_blobs: None, + }; + vendor_npm_any( + &case.purl, + &staged.installed, + &staged.root, + &patch_record(&case.uuid), + &sources, + "2026-09-26T00:00:00Z", + false, + false, + None, + ) + .await +} + +fn apply_churn(lock: &str, churn: &[(String, String)]) -> String { + let mut lines: Vec = lock.split('\n').map(str::to_string).collect(); + for (from, to) in churn { + let at = lines + .iter() + .position(|l| l.trim_end_matches(',') == from.trim_end_matches(',')) + .unwrap_or_else(|| panic!("no churned line {from}")); + let comma = if lines[at].ends_with(',') { "," } else { "" }; + lines[at] = format!("{}{comma}", to.trim_end_matches(',')); + } + lines.join("\n") +} + +fn expected_files(case: &Case) -> BTreeMap> { + let mut want = tree(&case.project); + want.extend(tree(&case.dir.join("expected"))); + want +} + +fn project_files(root: &Path) -> BTreeMap> { + tree(root) + .into_iter() + .filter(|(rel, _)| !rel.starts_with(".socket/")) + .collect() +} + +fn expect_done(outcome: VendorOutcome, label: &str) -> VendorEntry { + let VendorOutcome::Done { + result, + entry, + warnings, + } = outcome + else { + panic!("{label}: expected Done, got {outcome:?}"); + }; + assert!(result.success, "{label}: {:?}", result.error); + assert!( + warnings + .iter() + .all(|w| w.code != "vendor_multiple_lockfiles"), + "{label}: {warnings:?}" + ); + entry.unwrap_or_else(|| panic!("{label}: no ledger entry")) +} + +#[test] +fn every_vendored_case_is_a_regenerated_fixture() { + let all = cases(); + assert_eq!(all.len(), 33, "11 cases on each of 3 vlt versions"); + for case in &all { + assert_eq!( + case.refusal.is_none(), + case.dir.join("expected/vlt-lock.json").is_file(), + "{} {}", + case.version, + case.name + ); + } +} + +#[tokio::test] +async fn vendored_wiring_matches_what_vlt_ci_writes_and_reverts_byte_exact() { + for case in cases() { + let label = format!("{} {}", case.version, case.name); + let staged = stage(&case, false); + let before = project_files(&staged.root); + let outcome = vendor(&case, &staged).await; + if let Some(code) = &case.refusal { + let VendorOutcome::Refused { code: got, detail } = outcome else { + panic!("{label}: expected {code}, got {outcome:?}"); + }; + assert_eq!(got, code.as_str(), "{label}: {detail}"); + assert_eq!( + project_files(&staged.root), + before, + "{label}: refusal writes nothing" + ); + assert!(!staged.root.join(".socket").exists(), "{label}"); + continue; + } + let entry = expect_done(outcome, &label); + let mut got = project_files(&staged.root); + let lock = String::from_utf8(got["vlt-lock.json"].clone()).unwrap(); + got.insert( + "vlt-lock.json".into(), + apply_churn(&lock, &case.churn).into_bytes(), + ); + let want = expected_files(&case); + for (rel, bytes) in &want { + assert_eq!( + String::from_utf8_lossy(&got[rel]), + String::from_utf8_lossy(bytes), + "{label}: {rel}" + ); + } + assert_eq!(got.len(), want.len(), "{label}"); + + let (name, version) = name_version(&case.purl); + let rel_dir = entry.artifact.path.clone(); + assert!( + rel_dir.ends_with(&format!("/node_modules/{name}")), + "{label}: {rel_dir}" + ); + assert_eq!(entry.flavor.as_deref(), Some("vlt"), "{label}"); + let inventory = entry.artifact.file_inventory.clone().expect("inventory"); + assert_eq!( + inventory.keys().cloned().collect::>(), + ["index.js", "package.json"], + "{label}" + ); + let uuid_dir = staged + .root + .join(format!(".socket/vendor/npm/{}", case.uuid)); + assert_eq!( + fs::read_to_string(uuid_dir.join(".gitignore")).unwrap(), + "!*\n**/node_modules/*/node_modules/\n**/node_modules/@*/*/node_modules/\n" + ); + assert_eq!( + fs::read_to_string(uuid_dir.join(".gitattributes")).unwrap(), + "* -text\n" + ); + let artifact = staged.root.join(&rel_dir); + assert_eq!( + fs::read(artifact.join("index.js")).unwrap(), + PATCHED_JS, + "{label}" + ); + assert_eq!( + fs::read_to_string(artifact.join("package.json")).unwrap(), + format!( + "{{\n \"name\": \"{name}\",\n \"version\": \"{version}\",\n \"main\": \"index.js\"\n}}\n" + ), + "{label}: devDependencies cut out as a span" + ); + + let mut state = VendorState::new(); + state.entries.insert(case.purl.clone(), entry.clone()); + save_state(&staged.root, &state).await.unwrap(); + let wired = project_files(&staged.root); + match vendor(&case, &staged).await { + VendorOutcome::Done { + result, + entry: None, + .. + } => assert!(result.success, "{label}"), + other => panic!("{label}: a rerun is in sync, got {other:?}"), + } + assert_eq!( + project_files(&staged.root), + wired, + "{label}: the rerun writes nothing" + ); + + let reverted = revert_npm_any(&entry, &staged.root, false).await; + assert!(reverted.success, "{label}: {:?}", reverted.error); + assert!( + reverted.warnings.is_empty(), + "{label}: {:?}", + reverted.warnings + ); + let mut after = project_files(&staged.root); + after.remove(".socket/vendor/state.json"); + assert_eq!( + after, before, + "{label}: revert restores the pre-vendor bytes" + ); + assert!(!uuid_dir.exists(), "{label}: the artifact is removed"); + } +} + +#[tokio::test] +async fn revert_of_the_lock_vlt_ci_wrote_keeps_its_rewritten_outgoing_values() { + for case in cases().into_iter().filter(|c| c.refusal.is_none()) { + let label = format!("{} {}", case.version, case.name); + let staged = stage(&case, false); + let before = project_files(&staged.root); + let entry = expect_done(vendor(&case, &staged).await, &label); + write_tree(&staged.root, &tree(&case.dir.join("expected"))); + let reverted = revert_npm_any(&entry, &staged.root, false).await; + assert!(reverted.success, "{label}: {:?}", reverted.error); + assert!( + !reverted.drift_skipped(), + "{label}: {:?}", + reverted.warnings + ); + let got = project_files(&staged.root); + let mut want = before.clone(); + if !case.churn.is_empty() { + let lock = String::from_utf8(want["vlt-lock.json"].clone()).unwrap(); + let file_key = |line: &str| line.trim().split_once("\": ").unwrap().0.to_string(); + let value = |line: &str| { + line.trim() + .trim_end_matches(',') + .split_once("\": ") + .unwrap() + .1 + .to_string() + }; + let mut lines: Vec = lock.split('\n').map(str::to_string).collect(); + for (from, to) in &case.churn { + let dep = file_key(from).rsplit_once(' ').unwrap().1.to_string(); + let at = lines + .iter() + .position(|l| l.contains(&format!(" {dep}\": {}", value(from)))) + .unwrap_or_else(|| panic!("{label}: no pre-vendor twin of {from}")); + lines[at] = lines[at].replace(&value(from), &value(to)); + } + want.insert("vlt-lock.json".into(), lines.join("\n").into_bytes()); + } + assert_eq!(got, want, "{label}"); + } +} + +#[tokio::test] +async fn a_crlf_lock_is_wired_with_every_line_keeping_its_cr() { + for case in cases() + .into_iter() + .filter(|c| c.refusal.is_none() && c.churn.is_empty()) + { + let label = format!("{} {}", case.version, case.name); + let staged = stage(&case, true); + let before = project_files(&staged.root); + let entry = expect_done(vendor(&case, &staged).await, &label); + let want = fs::read_to_string(case.dir.join("expected/vlt-lock.json")) + .unwrap() + .replace('\n', "\r\n"); + assert_eq!( + fs::read_to_string(staged.root.join("vlt-lock.json")).unwrap(), + want, + "{label}" + ); + let reverted = revert_npm_any(&entry, &staged.root, false).await; + assert!(reverted.success, "{label}: {:?}", reverted.error); + assert_eq!(project_files(&staged.root), before, "{label}"); + } +} + +#[tokio::test] +async fn lock_inventory_reads_every_capture_and_drops_vendored_nodes() { + for (version, _, _) in CAPTURES { + let tmp = tempfile::tempdir().unwrap(); + for (name, text) in read_capture(version) { + fs::write(tmp.path().join(name), text).unwrap(); + } + let lock: Value = serde_json::from_str(&read_capture(version)["vlt-lock.json"]).unwrap(); + let registry_nodes = lock["nodes"] + .as_object() + .unwrap() + .keys() + .filter(|k| k.starts_with('~') || k.starts_with('·')) + .count(); + let entries = inventory_project(tmp.path()).await; + assert_eq!(entries.len(), registry_nodes, "{version}"); + for e in &entries { + let url = e.resolved.as_deref().unwrap_or_default(); + let bare = e.name.rsplit('/').next().unwrap(); + assert!( + url.ends_with(&format!("{bare}-{}.tgz", e.version)), + "{version}: {} → {url}", + e.purl + ); + assert!( + matches!(e.integrity, LockIntegrity::Sri(_)), + "{version} {}", + e.purl + ); + } + } + + let hosted = rewrite_registry_redirect(&read_capture("1.2.0"), &overrides()); + let tmp = tempfile::tempdir().unwrap(); + fs::write( + tmp.path().join("vlt-lock.json"), + &hosted.files["vlt-lock.json"], + ) + .unwrap(); + let entries = inventory_project(tmp.path()).await; + let left_pad = entries + .iter() + .find(|e| e.name == "left-pad" && e.version == "1.3.0") + .expect("a hosted entry stays installed-by-lock"); + let uuid = TARGETS.iter().find(|t| t.0 == "left-pad").unwrap().2; + assert_eq!( + left_pad.resolved.as_deref(), + Some(hosted_url("left-pad", "1.3.0", uuid).as_str()) + ); + assert_eq!(left_pad.integrity, LockIntegrity::Sri(patched_sha(uuid))); + + for case in cases().into_iter().filter(|c| c.refusal.is_none()) { + let tmp = tempfile::tempdir().unwrap(); + write_tree(tmp.path(), &expected_files(&case)); + let (name, version) = name_version(&case.purl); + let entries = inventory_project(tmp.path()).await; + assert!( + !entries + .iter() + .any(|e| e.name == name && e.version == version), + "{} {}: the vendored file node is not a registry entry", + case.version, + case.name + ); + assert!(!entries.is_empty(), "{} {}", case.version, case.name); + } +} From efe259cdb7c56e06886f268c5bc08d666afc6362 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Sat, 26 Sep 2026 01:22:58 -0400 Subject: [PATCH 16/46] Harden vlt vendoring revert and rebuild paths `vendor --revert` on a vlt project no longer writes a lock with duplicate keys when `vlt install` has since added a registry copy of the vendored package beside it: the vendored entries fold into the matching registry ones, and a mismatching copy is reported as drift with nothing written and the artifact kept. Re-vendoring a new patch over a dir whose ledger entry lost its pre-vendor wiring now refuses with vendor_wiring_unknown and says how to recover, instead of recording wiring that could never be reverted. A rebuild of the committed package dir replaces the whole version-level directory, so stray files beside the package no longer cause an endless corrupt-then-rebuild loop, and it keeps vlt's dependency links (or says to run `vlt install` when they had to be discarded). Prebuilt service trees are pruned of node_modules and bundling packages refuse, as the local build already did. The git-ignore probe now parses rule sources that contain a colon, such as Windows drive-letter paths. New tests pin every revert inverse's already-reverted branch, the importer allowlist, dry-run revert, the rebuild branch, the reuse-time .gitignore restore, the gitignored refusal, both bundleDependencies refusals, the service layout fallback, local dir artifact staging and the FIFO-safe vlt-lock.json inventory. Assisted-by: Claude Code:claude-opus-5-5 --- .../src/vendor/lock_inventory/tests.rs | 21 +- .../socket-patch-core/src/vendor/npm_dir.rs | 154 ++-- .../src/vendor/registry_fetch.rs | 39 + .../socket-patch-core/src/vendor/vlt_lock.rs | 699 ++++++++++++++++-- 4 files changed, 818 insertions(+), 95 deletions(-) diff --git a/crates/socket-patch-core/src/vendor/lock_inventory/tests.rs b/crates/socket-patch-core/src/vendor/lock_inventory/tests.rs index a7366e5c..de51325f 100644 --- a/crates/socket-patch-core/src/vendor/lock_inventory/tests.rs +++ b/crates/socket-patch-core/src/vendor/lock_inventory/tests.rs @@ -1849,6 +1849,7 @@ async fn fifo_lockfiles_fail_fast_instead_of_wedging() { "yarn.lock", "bun.lock", "shrinkwrap.yaml", + "vlt-lock.json", ]; for name in names { mkfifo(&root.join(name)); @@ -1870,6 +1871,7 @@ async fn fifo_lockfiles_fail_fast_instead_of_wedging() { inventory_yarn_classic(&root).await, inventory_yarn_berry(&root).await, inventory_bun(&root).await, + inventory_vlt(&root).await, inventory_pnpm_lock_at(&root.join("shrinkwrap.yaml")).await, gem_remotes(&root).await, wired_vendor_integrity(&root, ".socket/vendor/npm/x/x.tgz").await, @@ -1885,8 +1887,22 @@ async fn fifo_lockfiles_fail_fast_instead_of_wedging() { } panic!("lockfile inventories must fail fast on FIFO lockfiles"); }; - let (cargo, go, composer, gem, pypi, npm, pnpm, yarn_c, yarn_b, bun, legacy, remotes, wired) = - results; + let ( + cargo, + go, + composer, + gem, + pypi, + npm, + pnpm, + yarn_c, + yarn_b, + bun, + vlt, + legacy, + remotes, + wired, + ) = results; for (label, opt) in [ ("cargo", cargo), ("go", go), @@ -1898,6 +1914,7 @@ async fn fifo_lockfiles_fail_fast_instead_of_wedging() { ("yarn classic", yarn_c), ("yarn berry", yarn_b), ("bun", bun), + ("vlt", vlt), ("pnpm legacy", legacy), ] { assert!( diff --git a/crates/socket-patch-core/src/vendor/npm_dir.rs b/crates/socket-patch-core/src/vendor/npm_dir.rs index d847a637..7168edd4 100644 --- a/crates/socket-patch-core/src/vendor/npm_dir.rs +++ b/crates/socket-patch-core/src/vendor/npm_dir.rs @@ -61,6 +61,9 @@ pub(super) struct NpmStagedDir { pub staged_pkg_json: Option, /// The committed dir passed the reuse check; nothing was written. pub reused: bool, + /// A rebuild discarded the old dir's `node_modules/` (it held more + /// than vlt's links), so the package needs `vlt install` to re-link. + pub links_dropped: bool, } // ── package.json spans ─────────────────────────────────────────────────── @@ -431,24 +434,17 @@ pub(crate) async fn gitignored(project_root: &Path, paths: &[String]) -> Option< if inside.trim() != "true" { return None; } - let input: String = paths.iter().map(|p| format!("{p}\n")).collect(); + let input: String = paths.iter().map(|p| format!("{p}\0")).collect(); let (code, out) = git_output( &git, project_root, - &["check-ignore", "-v", "--no-index", "--stdin"], + &["check-ignore", "-v", "-z", "--no-index", "--stdin"], Some(input), ) .await?; - let lines: Vec<&str> = out - .lines() - .filter(|line| { - let rule = line.split('\t').next().unwrap_or_default(); - let pattern = rule.splitn(3, ':').nth(2).unwrap_or_default(); - !pattern.is_empty() && !pattern.starts_with('!') - }) - .collect(); + let lines = ignoring_rules(&out); (code == 0 && !lines.is_empty()).then(|| { - let shown: Vec<&str> = lines.iter().take(3).copied().collect(); + let shown: Vec<&str> = lines.iter().take(3).map(String::as_str).collect(); let more = lines.len().saturating_sub(shown.len()); let mut detail = shown.join("; "); if more > 0 { @@ -458,6 +454,19 @@ pub(crate) async fn gitignored(project_root: &Path, paths: &[String]) -> Option< }) } +/// The non-negated matches of `git check-ignore -v -z` output +/// (` NUL NUL NUL NUL` per path), as +/// `::\t`. A source may hold a colon +/// (a Windows drive letter), so the fields are split on NUL only. +fn ignoring_rules(out: &str) -> Vec { + let fields: Vec<&str> = out.split('\0').collect(); + fields + .chunks_exact(4) + .filter(|f| !f[2].is_empty() && !f[2].starts_with('!')) + .map(|f| format!("{}:{}:{}\t{}", f[0], f[1], f[2], f[3])) + .collect() +} + fn gitignored_refusal(rel_dir: &str, rules: &str) -> VendorOutcome { refused( "vendor_artifact_gitignored", @@ -519,6 +528,7 @@ pub(super) async fn stage_patch_dir( uuid_dir_preexisted: true, staged_pkg_json, reused: true, + links_dropped: false, }), result, )); @@ -561,6 +571,7 @@ pub(super) async fn stage_patch_dir( } let result = match result { Some(result) => { + prune_staged_node_modules(purl, &stage, &coords.name, &coords.version).await?; apply_transforms(&stage, &coords.name, &coords.version).await?; result } @@ -571,24 +582,7 @@ pub(super) async fn stage_patch_dir( format!("cannot stage a copy of the installed package: {e}"), ))); } - if let Err(e) = remove_tree(&stage.join(NODE_MODULES)).await { - return Err(Box::new(done_failure( - purl, - format!("cannot prune staged node_modules: {e}"), - ))); - } - if let Ok(pkg) = read_manifest(&stage).await { - if declares_bundled_deps(&pkg) { - return Err(Box::new(refused( - "vendor_bundled_deps_unsupported", - format!( - "{}@{} declares bundleDependencies; vendoring would drop its \ - bundled node_modules and break installs", - coords.name, coords.version - ), - ))); - } - } + prune_staged_node_modules(purl, &stage, &coords.name, &coords.version).await?; let result = super::force_apply_staged( purl, &stage, @@ -622,11 +616,14 @@ pub(super) async fn stage_patch_dir( uuid_dir_preexisted, ) }; - if let Err(e) = write_into_place(&stage, &uuid_dir, &rel_abs).await { - return Err(Box::new( - unstage(format!("cannot write {rel_dir}: {e}")).await, - )); - } + let links_dropped = match write_into_place(&stage, &uuid_dir, &rel_abs, &coords.name).await { + Ok(dropped) => dropped, + Err(e) => { + return Err(Box::new( + unstage(format!("cannot write {rel_dir}: {e}")).await, + )) + } + }; if let Err(e) = restore_uuid_metadata(&uuid_dir).await { return Err(Box::new( unstage(format!( @@ -667,11 +664,40 @@ pub(super) async fn stage_patch_dir( uuid_dir_preexisted, staged_pkg_json, reused: false, + links_dropped, }), result, )) } +/// DESIGN §4.3 step 5 on any staged tree: prune its `node_modules/` and +/// refuse a package that bundles dependencies. +async fn prune_staged_node_modules( + purl: &str, + stage: &Path, + name: &str, + version: &str, +) -> Result<(), Box> { + if let Err(e) = remove_tree(&stage.join(NODE_MODULES)).await { + return Err(Box::new(done_failure( + purl, + format!("cannot prune staged node_modules: {e}"), + ))); + } + if let Ok(pkg) = read_manifest(stage).await { + if declares_bundled_deps(&pkg) { + return Err(Box::new(refused( + "vendor_bundled_deps_unsupported", + format!( + "{name}@{version} declares bundleDependencies; vendoring would drop its \ + bundled node_modules and break installs" + ), + ))); + } + } + Ok(()) +} + async fn read_manifest(dir: &Path) -> Result { let text = crate::utils::fs::read_regular_to_string(&dir.join("package.json")) .await @@ -680,30 +706,56 @@ async fn read_manifest(dir: &Path) -> Result { .map_err(|e| format!("package.json is not parseable JSON: {e}")) } -/// Copy the stage to `/.tmp-*`, then rename it over `rel_abs`. -async fn write_into_place(stage: &Path, uuid_dir: &Path, rel_abs: &Path) -> std::io::Result<()> { - tokio::fs::create_dir_all(uuid_dir).await?; - let parent: PathBuf = rel_abs +/// Build the whole `` level in `/.tmp-*` and rename it over +/// the old one, so nothing but the package dir survives beside it. The old +/// dir's `node_modules/` moves along when it holds only vlt's links; +/// `Ok(true)` when it held anything else and was discarded. +async fn write_into_place( + stage: &Path, + uuid_dir: &Path, + rel_abs: &Path, + name: &str, +) -> std::io::Result { + let mut leaf_abs = rel_abs.to_path_buf(); + for _ in name.split('/') { + leaf_abs.pop(); + } + leaf_abs.pop(); + let parent: PathBuf = leaf_abs .parent() .map(Path::to_path_buf) .ok_or_else(|| std::io::Error::other("artifact path has no parent"))?; + tokio::fs::create_dir_all(uuid_dir).await?; tokio::fs::create_dir_all(&parent).await?; let tmp = tempfile::Builder::new() .prefix(".tmp-") .tempdir_in(uuid_dir)? .keep(); - if let Err(e) = fresh_copy(stage, &tmp, None).await { + let tmp_pkg = tmp.join(NODE_MODULES).join(name); + if let Err(e) = fresh_copy(stage, &tmp_pkg, None).await { let _ = remove_tree(&tmp).await; return Err(e); } - if tokio::fs::symlink_metadata(rel_abs).await.is_ok() { - remove_tree(rel_abs).await?; + let old_links = rel_abs.join(NODE_MODULES); + let mut dropped = false; + if tokio::fs::symlink_metadata(&old_links).await.is_ok() { + if node_modules_holds_only_links(rel_abs).await { + if let Err(e) = tokio::fs::rename(&old_links, tmp_pkg.join(NODE_MODULES)).await { + let _ = remove_tree(&tmp).await; + return Err(e); + } + } else { + dropped = true; + } + } + if tokio::fs::symlink_metadata(&leaf_abs).await.is_ok() { + remove_tree(&leaf_abs).await?; } - if let Err(e) = tokio::fs::rename(&tmp, rel_abs).await { + if let Err(e) = tokio::fs::rename(&tmp, &leaf_abs).await { let _ = remove_tree(&tmp).await; return Err(e); } - Ok(()) + Ok(dropped) } /// Every record file of the extracted tree hashes to its afterHash (the @@ -989,6 +1041,22 @@ mod tests { } } + #[test] + fn check_ignore_records_split_on_nul_so_a_drive_letter_source_parses() { + let out = "C:/Users/u/.gitignore_global\x003\x00!dist/\x00.socket/p/dist/i.js\x00\ + C:/Users/u/.gitignore_global\x004\x00*.map\x00.socket/p/i.js.map\x00\ + .gitignore\x001\x00.socket/\x00.socket/p/a.js\x00"; + assert_eq!( + ignoring_rules(out), + [ + "C:/Users/u/.gitignore_global:4:*.map\t.socket/p/i.js.map", + ".gitignore:1:.socket/\t.socket/p/a.js", + ] + ); + assert!(ignoring_rules("").is_empty()); + assert!(ignoring_rules("C:/g\x002\x00!x\x00x\x00").is_empty()); + } + #[cfg(unix)] #[tokio::test] async fn the_gitignore_probe_ignores_tracked_state_and_names_the_rule() { diff --git a/crates/socket-patch-core/src/vendor/registry_fetch.rs b/crates/socket-patch-core/src/vendor/registry_fetch.rs index bd53dea5..d5196555 100644 --- a/crates/socket-patch-core/src/vendor/registry_fetch.rs +++ b/crates/socket-patch-core/src/vendor/registry_fetch.rs @@ -1621,6 +1621,45 @@ mod tests { } } + #[tokio::test] + async fn stage_local_dir_artifact_verifies_the_inventory_and_drops_node_modules() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path().join("left-pad"); + std::fs::create_dir_all(dir.join("node_modules/.bin")).unwrap(); + std::fs::write(dir.join("package.json"), b"{}").unwrap(); + std::fs::write(dir.join("index.js"), b"x").unwrap(); + std::fs::write(dir.join("node_modules/.bin/tool"), b"#!/bin/sh\n").unwrap(); + #[cfg(unix)] + std::os::unix::fs::symlink("../../elsewhere", dir.join("node_modules/dep")).unwrap(); + let inventory = super::super::verify::compute_package_dir_inventory(&dir) + .await + .unwrap(); + assert_eq!(inventory.len(), 2, "{inventory:?}"); + + let staged = stage_local_dir_artifact(&dir, Some(&inventory)) + .await + .unwrap(); + assert_eq!(std::fs::read(staged.dir().join("index.js")).unwrap(), b"x"); + assert!(!staged.dir().join("node_modules").exists()); + assert_eq!(staged.url, format!("file:{}", dir.display())); + + std::fs::write(dir.join("planted.js"), b"y").unwrap(); + match stage_local_dir_artifact(&dir, Some(&inventory)).await { + Err(FetchError::Failed(msg)) => assert!(msg.contains("file inventory"), "{msg}"), + other => panic!("a planted file must fail, got {other:?}"), + } + std::fs::remove_file(dir.join("planted.js")).unwrap(); + std::fs::write(dir.join("index.js"), b"modified").unwrap(); + match stage_local_dir_artifact(&dir, Some(&inventory)).await { + Err(FetchError::Failed(msg)) => assert!(msg.contains("file inventory"), "{msg}"), + other => panic!("a modified file must fail, got {other:?}"), + } + match stage_local_dir_artifact(&dir, None).await { + Err(FetchError::Unverifiable(_)) => {} + other => panic!("no inventory is unverifiable, got {other:?}"), + } + } + #[tokio::test] async fn cargo_crate_fetch_verifies_sha256_and_extracts() { // .crate = tar.gz with a {name}-{version}/ top dir. diff --git a/crates/socket-patch-core/src/vendor/vlt_lock.rs b/crates/socket-patch-core/src/vendor/vlt_lock.rs index f1ffaed9..69ff733d 100644 --- a/crates/socket-patch-core/src/vendor/vlt_lock.rs +++ b/crates/socket-patch-core/src/vendor/vlt_lock.rs @@ -706,19 +706,13 @@ struct Wiring { lock: Option, } -fn wiring_record( - file: &str, - kind: &str, - key: &str, - original: Option, - new: String, -) -> WiringRecord { +fn wiring_record(file: &str, kind: &str, key: &str, original: String, new: String) -> WiringRecord { WiringRecord { file: file.to_string(), kind: kind.to_string(), action: WiringAction::Rewritten, key: Some(key.to_string()), - original: original.map(Value::String), + original: Some(Value::String(original)), new: Some(Value::String(new)), } } @@ -743,6 +737,28 @@ fn original_text(rec: Option<&WiringRecord>) -> Option { .map(str::to_string) } +/// A re-vendor over our own dir carries the prior record's key and +/// original forward; without them the new records could never be reverted. +fn carried( + target: &Target, + rec: Option<&WiringRecord>, + what: &str, +) -> Result<(String, String), Refusal> { + match (rec.and_then(|r| r.key.clone()), original_text(rec)) { + (Some(key), Some(original)) => Ok((key, original)), + _ => Err(( + "vendor_wiring_unknown", + format!( + "vlt-lock.json already resolves this package through {}, but the vendor ledger \ + records no pre-vendor {what} to carry forward (it was likely reconstructed by \ + `socket-patch repair`); restore the registry spec in package.json, run `vlt \ + install`, then vendor again", + target.key + ), + )), + } +} + /// DESIGN §4.5.2–§4.5.4: the records and the new surfaces, or `None` when /// every surface already names `rel`. fn plan_wiring( @@ -795,9 +811,10 @@ fn plan_wiring( } let key = format!("{}/{}", edge.field, edge.dep); let original = if target.ours { - original_text(prior_original(prior, &pkg_rel, KIND_PKG_DEP, |k| k == key)) + let rec = prior_original(prior, &pkg_rel, KIND_PKG_DEP, |k| k == key); + carried(target, rec, &format!("{pkg_rel} {key} spec"))?.1 } else { - Some(current) + current }; let edited = replace_dependency_token(&text, edge.field, &edge.dep, &new) .map_err(|_| (OUT_OF_SYNC, format!("cannot edit {pkg_rel}")))?; @@ -808,15 +825,10 @@ fn plan_wiring( let mut node_touch = Vec::new(); if *node != new_node { let (key, original) = if target.ours { - let prior_rec = prior_original(prior, VLT_LOCK, KIND_LOCK_NODE, |_| true); - ( - prior_rec - .and_then(|r| r.key.clone()) - .unwrap_or_else(|| node.key.clone()), - original_text(prior_rec), - ) + let rec = prior_original(prior, VLT_LOCK, KIND_LOCK_NODE, |_| true); + carried(target, rec, "registry node")? } else { - (node.key.clone(), Some(node.text())) + (node.key.clone(), node.text()) }; records.push(wiring_record( VLT_LOCK, @@ -848,11 +860,10 @@ fn plan_wiring( continue; } let original = if target.ours { - original_text(prior_original(prior, VLT_LOCK, KIND_LOCK_EDGE, |k| { - k == current.key - })) + let rec = prior_original(prior, VLT_LOCK, KIND_LOCK_EDGE, |k| k == current.key); + carried(target, rec, &format!("edge `{}`", current.key))?.1 } else { - Some(current.text()) + current.text() }; records.push(wiring_record( VLT_LOCK, @@ -874,18 +885,13 @@ fn plan_wiring( }; let (key, original) = if target.ours { let dep = current.edge_dep().to_string(); - let prior_rec = prior_original(prior, VLT_LOCK, KIND_LOCK_EDGE, |k| { + let rec = prior_original(prior, VLT_LOCK, KIND_LOCK_EDGE, |k| { k.split_once(' ') .is_some_and(|(from, d)| d == dep && !is_importer_dep_id(from)) }); - ( - prior_rec - .and_then(|r| r.key.clone()) - .unwrap_or_else(|| current.key.clone()), - original_text(prior_rec), - ) + carried(target, rec, &format!("edge `{}`", current.key))? } else { - (current.key.clone(), Some(current.text())) + (current.key.clone(), current.text()) }; records.push(wiring_record( VLT_LOCK, @@ -1078,11 +1084,17 @@ pub(crate) async fn vendor_vlt( warnings, ); } + let relink = if staged.links_dropped { + "; its node_modules/ held more than vlt's dependency links and was discarded, so \ + run `vlt install` to re-link its dependencies" + } else { + "" + }; warnings.push(VendorWarning::new( "vendor_artifact_rebuilt", format!( "the committed vendored dir for {name}@{version} was missing or stale; rebuilt \ - at {} (vlt-lock.json and package.json untouched)", + at {} (vlt-lock.json and package.json untouched){relink}", staged.rel_dir ), )); @@ -1234,6 +1246,9 @@ struct Staged { edges: Vec, touched_nodes: Vec, touched_edges: Vec, + /// Vendored entries dropped in favor of a live registry twin. + merged_nodes: BTreeSet, + merged_edges: BTreeSet, } enum Step { @@ -1247,6 +1262,11 @@ fn slot_value(raw: Option<&str>) -> Option { .and_then(|s| serde_json::from_str::(s).ok()) } +fn registry_slots(entry: &Entry) -> Option<(Option, Option)> { + let text = entry.text(); + parse_node_entry_text(&text).map(|n| (slot_value(n.slot(2)), slot_value(n.slot(3)))) +} + fn revert_node(staged: &mut Staged, rec: &WiringRecord) -> Step { let (Some(new), Some(original)) = (fragment(&rec.new), fragment(&rec.original)) else { return Step::Drift("the vlt_lock_node record has no pre-vendor original".into()); @@ -1264,6 +1284,17 @@ fn revert_node(staged: &mut Staged, rec: &WiringRecord) -> Step { if live.slot(2) != Some("null") || slot_value(live.slot(3)) != slot_value(new.slot(3)) { return Step::Drift(format!("{} drifted from the vendored wiring", new.key)); } + let wanted = (slot_value(original.slot(2)), slot_value(original.slot(3))); + if let Some(twin) = staged.nodes.iter().find(|e| e.key == original.key) { + if registry_slots(twin) != Some(wanted) { + return Step::Drift(format!( + "vlt-lock.json holds both {} and a different {}", + new.key, original.key + )); + } + staged.merged_nodes.insert(i); + return Step::Applied; + } let tuple = render_tuple_with_slots( &live.elems, original.slot(2).filter(|s| *s != "null"), @@ -1281,11 +1312,7 @@ fn revert_node(staged: &mut Staged, rec: &WiringRecord) -> Step { .nodes .iter() .find(|e| e.key == original.key) - .and_then(|e| { - let text = e.text(); - parse_node_entry_text(&text) - .map(|live| (slot_value(live.slot(2)), slot_value(live.slot(3)))) - }); + .and_then(registry_slots); if restored == Some((slot_value(original.slot(2)), slot_value(original.slot(3)))) { Step::AlreadyReverted } else { @@ -1324,7 +1351,15 @@ fn revert_edge(staged: &mut Staged, rec: &WiringRecord) -> Step { }; } match (find(&new.key), find(&original.key)) { - (Some(i), _) => { + (Some(i), Some(j)) if staged.edges[i].value == staged.edges[j].value => { + staged.merged_edges.insert(i); + Step::Applied + } + (Some(_), Some(_)) => Step::Drift(format!( + "vlt-lock.json holds both `{}` and a different `{}`", + new.key, original.key + )), + (Some(i), None) => { staged.edges[i].key = original.key.clone(); staged.touched_edges.push(i); Step::Applied @@ -1489,6 +1524,8 @@ pub async fn revert_vlt_opts( edges: d.edges.entries.clone(), touched_nodes: Vec::new(), touched_edges: Vec::new(), + merged_nodes: BTreeSet::new(), + merged_edges: BTreeSet::new(), }); let mut new_pkgs = pkgs.clone(); let mut drift: Option = None; @@ -1549,24 +1586,39 @@ pub async fn revert_vlt_opts( outcome } -/// The lock with the restored entries re-placed (§4.5.4), in the forward -/// record order. +/// A block without its merged entries, the restored ones re-placed +/// (§4.5.4) in the forward record order. +fn restored_block( + entries: &[Entry], + touched: &[usize], + merged: &BTreeSet, + cmp: fn(&Entry, &Entry) -> Option, +) -> Vec { + let kept: Vec = entries + .iter() + .enumerate() + .filter(|(i, _)| !merged.contains(i)) + .map(|(_, e)| e.clone()) + .collect(); + let touched: Vec<(usize, Entry)> = touched + .iter() + .rev() + .map(|&i| (i - merged.range(..i).count(), entries[i].clone())) + .collect(); + place(&kept, &touched, cmp) +} + fn render_restored(doc: &LockDoc, staged: Staged) -> String { - let touched = |entries: &[Entry], order: &[usize]| -> Vec<(usize, Entry)> { - order - .iter() - .rev() - .map(|&i| (i, entries[i].clone())) - .collect() - }; - let nodes = place( + let nodes = restored_block( &staged.nodes, - &touched(&staged.nodes, &staged.touched_nodes), + &staged.touched_nodes, + &staged.merged_nodes, node_cmp, ); - let edges = place( + let edges = restored_block( &staged.edges, - &touched(&staged.edges, &staged.touched_edges), + &staged.touched_edges, + &staged.merged_edges, edge_cmp, ); LockDoc { @@ -2396,4 +2448,551 @@ mod tests { } assert!(!fx.root.join(".socket/vendor").exists()); } + + fn file_id() -> String { + format!("file~.socket+vendor+npm+{UUID}+left-pad-1.3.0+node__modules+left-pad") + } + + fn uuid_dir(fx: &Fx, uuid: &str) -> std::path::PathBuf { + fx.root.join(format!(".socket/vendor/npm/{uuid}")) + } + + type DoneParts = ( + (bool, Option), + Option, + Vec, + ); + + fn done_parts(outcome: VendorOutcome) -> DoneParts { + match outcome { + VendorOutcome::Done { + result, + entry, + warnings, + } => ((result.success, result.error), entry, warnings), + other => panic!("expected Done, got {other:?}"), + } + } + + fn codes(warnings: &[VendorWarning]) -> Vec<&str> { + warnings.iter().map(|w| w.code).collect() + } + + #[tokio::test] + async fn revert_beside_a_registry_twin_merges_into_it_or_drifts() { + let rel = format!(".socket/vendor/npm/{UUID}/left-pad-1.3.0/node_modules/left-pad"); + let file_id = file_id(); + let file_node = format!(r#""{file_id}": [0,"left-pad",null,"{rel}"]"#); + let file_edge = format!(r#""file~_d left-pad": "prod file:./{rel} {file_id}""#); + let file_out = format!(r#""{file_id} z": "prod ^1.0.0 ~npm~z@1.0.0""#); + let ws_edge = r#""workspace~packages+a left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0""#; + let a_node = r#""~npm~a@1.0.0": [0,"a","sha512-A=="]"#; + let z_node = r#""~npm~z@1.0.0": [0,"z","sha512-Z=="]"#; + let a_edge = r#""file~_d a": "prod 1.0.0 ~npm~a@1.0.0""#; + let reg_out = r#""~npm~left-pad@1.3.0 z": "prod ^1.0.0 ~npm~z@1.0.0""#; + let cases = [ + (REG_NODE.to_string(), reg_out.to_string(), true), + ( + REG_NODE.replace("sha512-REG==", "sha512-OTHER=="), + reg_out.to_string(), + false, + ), + ( + REG_NODE.to_string(), + reg_out.replace("~npm~z@1.0.0", "~npm~z@1.0.1"), + false, + ), + ]; + for (twin_node, twin_out, merges) in cases { + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (entry, _) = entry_of(run(&fx, UUID, false).await); + let twin = render( + 1, + &[a_node, &twin_node, z_node, &file_node], + &[a_edge, &file_edge, ws_edge, &file_out, &twin_out], + ); + tokio::fs::write(fx.root.join(VLT_LOCK), &twin) + .await + .unwrap(); + let pkg = read(&fx, PACKAGE_JSON).await; + let out = revert_vlt_opts(&entry, &fx.root, RevertOpts::new(false)).await; + if merges { + assert!(out.success && out.warnings.is_empty(), "{out:?}"); + assert_eq!( + read(&fx, VLT_LOCK).await, + render( + 1, + &[a_node, REG_NODE, z_node], + &[ + a_edge, + r#""file~_d left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0""#, + ws_edge, + reg_out, + ], + ) + ); + assert_eq!(read(&fx, PACKAGE_JSON).await, ROOT_PKG); + assert!(!uuid_dir(&fx, UUID).exists()); + assert!(parse_doc(&read(&fx, VLT_LOCK).await).is_ok()); + } else { + assert!( + out.success && out.drift_skipped() && out.kept_artifact, + "{out:?}" + ); + assert!( + out.warnings[0].detail.contains("holds both"), + "{:?}", + out.warnings + ); + assert_eq!(read(&fx, VLT_LOCK).await, twin, "a drift writes nothing"); + assert_eq!(read(&fx, PACKAGE_JSON).await, pkg); + assert!(fx.root.join(&entry.artifact.path).exists()); + } + } + } + + #[tokio::test] + async fn revert_finishes_a_partial_revert_record_by_record() { + let file_id = file_id(); + let rel = format!(".socket/vendor/npm/{UUID}/left-pad-1.3.0/node_modules/left-pad"); + let wired_pkg = ROOT_PKG.replace( + "\"left-pad\": \"1.3.0\"", + &format!("\"left-pad\": \"file:./{rel}\""), + ); + type Undo = fn(&str, &str) -> (Option, bool); + let undos: [(&str, Undo); 3] = [ + ("the lock was already restored", |_, _| { + (Some(basic_lock()), false) + }), + ( + "only the outgoing edge was re-keyed back", + |wired, file_id| { + ( + Some( + wired.replace(&format!("\"{file_id} z\""), "\"~npm~left-pad@1.3.0 z\""), + ), + false, + ) + }, + ), + ("package.json was already restored", |_, _| (None, true)), + ]; + for (label, undo) in undos { + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (entry, _) = entry_of(run(&fx, UUID, false).await); + assert_eq!(read(&fx, PACKAGE_JSON).await, wired_pkg); + let wired = read(&fx, VLT_LOCK).await; + let (lock, pkg_restored) = undo(&wired, &file_id); + if let Some(lock) = lock { + tokio::fs::write(fx.root.join(VLT_LOCK), lock) + .await + .unwrap(); + } + if pkg_restored { + tokio::fs::write(fx.root.join(PACKAGE_JSON), ROOT_PKG) + .await + .unwrap(); + } + let out = revert_vlt_opts(&entry, &fx.root, RevertOpts::new(false)).await; + assert!(out.success && out.warnings.is_empty(), "{label}: {out:?}"); + assert_eq!(read(&fx, VLT_LOCK).await, basic_lock(), "{label}"); + assert_eq!(read(&fx, PACKAGE_JSON).await, ROOT_PKG, "{label}"); + assert!(!uuid_dir(&fx, UUID).exists(), "{label}"); + } + } + + #[tokio::test] + async fn revert_never_follows_a_record_outside_the_importer_allowlist() { + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (mut entry, _) = entry_of(run(&fx, UUID, false).await); + let pkg_rec = entry.wiring[0].clone(); + let new = fragment(&pkg_rec.new).unwrap().to_string(); + let planted = format!("{{\"dependencies\":{{\"left-pad\":{new}}}}}"); + let outside = ["vendor/x/package.json", "../escape/package.json"]; + for rel in outside { + let path = fx.root.join(rel); + tokio::fs::create_dir_all(path.parent().unwrap()) + .await + .unwrap(); + tokio::fs::write(&path, &planted).await.unwrap(); + entry.wiring.insert( + 0, + WiringRecord { + file: rel.to_string(), + ..pkg_rec.clone() + }, + ); + } + let lock = read(&fx, VLT_LOCK).await; + let pkg = read(&fx, PACKAGE_JSON).await; + let out = revert_vlt_opts(&entry, &fx.root, RevertOpts::new(false)).await; + assert!(out.drift_skipped() && out.kept_artifact, "{out:?}"); + for rel in outside { + assert!( + out.warnings + .iter() + .any(|w| w.code == "vendor_lock_entry_drifted" + && w.detail.contains("non-allowlisted") + && w.detail.contains(rel)), + "{rel}: {:?}", + out.warnings + ); + assert_eq!(read(&fx, rel).await, planted, "{rel} is never written"); + } + assert_eq!(read(&fx, VLT_LOCK).await, lock); + assert_eq!(read(&fx, PACKAGE_JSON).await, pkg); + assert!(fx.root.join(&entry.artifact.path).exists()); + } + + #[tokio::test] + async fn a_dry_run_revert_writes_nothing() { + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (entry, _) = entry_of(run(&fx, UUID, false).await); + let lock = read(&fx, VLT_LOCK).await; + let pkg = read(&fx, PACKAGE_JSON).await; + let inventory = crate::vendor::verify::compute_package_dir_inventory( + &fx.root.join(&entry.artifact.path), + ) + .await + .unwrap(); + let out = revert_vlt_opts( + &entry, + &fx.root, + RevertOpts { + dry_run: true, + keep_artifact: false, + }, + ) + .await; + assert!(out.success && out.warnings.is_empty(), "{out:?}"); + assert_eq!(read(&fx, VLT_LOCK).await, lock); + assert_eq!(read(&fx, PACKAGE_JSON).await, pkg); + assert_eq!( + crate::vendor::verify::compute_package_dir_inventory( + &fx.root.join(&entry.artifact.path) + ) + .await + .unwrap(), + inventory + ); + assert!(uuid_dir(&fx, UUID).join(".gitignore").is_file()); + } + + #[tokio::test] + async fn a_new_uuid_over_an_unwired_prior_refuses_before_any_write() { + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (mut first, _) = entry_of(run(&fx, UUID, false).await); + first.wiring.clear(); + persist(&fx, &first).await; + let lock = read(&fx, VLT_LOCK).await; + let pkg = read(&fx, PACKAGE_JSON).await; + let (code, detail) = refusal(run(&fx, UUID2, false).await); + assert_eq!(code, "vendor_wiring_unknown", "{detail}"); + assert!( + detail.contains("run `vlt install`") && detail.contains(&file_id()), + "{detail}" + ); + assert_eq!(read(&fx, VLT_LOCK).await, lock); + assert_eq!(read(&fx, PACKAGE_JSON).await, pkg); + assert!(!uuid_dir(&fx, UUID2).exists()); + + let fx = self::fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (mut partial, _) = entry_of(run(&fx, UUID, false).await); + partial.wiring[1].original = None; + persist(&fx, &partial).await; + let (code, _) = refusal(run(&fx, UUID2, false).await); + assert_eq!(code, "vendor_wiring_unknown"); + assert!(!uuid_dir(&fx, UUID2).exists()); + } + + #[tokio::test] + async fn a_stale_committed_dir_is_rebuilt_under_in_sync_wiring() { + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (first, _) = entry_of(run(&fx, UUID, false).await); + persist(&fx, &first).await; + let lock = read(&fx, VLT_LOCK).await; + let pkg = read(&fx, PACKAGE_JSON).await; + let index = fx.root.join(&first.artifact.path).join("index.js"); + tokio::fs::remove_file(&index).await.unwrap(); + + let (result, entry, warnings) = done_parts(run(&fx, UUID, false).await); + assert!(result.0, "{result:?}"); + let mut entry = entry.expect("a rebuild returns its entry"); + assert_eq!(codes(&warnings), ["vendor_artifact_rebuilt"]); + assert!(!warnings[0].detail.contains("vlt install"), "{warnings:?}"); + assert_eq!(tokio::fs::read(&index).await.unwrap(), PATCHED); + assert_eq!(read(&fx, VLT_LOCK).await, lock); + assert_eq!(read(&fx, PACKAGE_JSON).await, pkg); + assert_eq!(entry.artifact.path, first.artifact.path); + assert_eq!(entry.artifact.file_inventory, first.artifact.file_inventory); + assert!(entry.wiring.is_empty()); + crate::vendor::state::carry_forward_wiring(&first, &mut entry); + assert_eq!(entry.wiring, first.wiring); + } + + #[tokio::test] + async fn a_rebuild_clears_strays_beside_the_package_dir_and_ends_healthy() { + use crate::vendor::verify::{check_vendored_artifact, ArtifactHealth}; + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (first, _) = entry_of(run(&fx, UUID, false).await); + persist(&fx, &first).await; + let rec = record(UUID); + let leaf = uuid_dir(&fx, UUID).join("left-pad-1.3.0"); + tokio::fs::write(leaf.join("node_modules/.DS_Store"), b"x") + .await + .unwrap(); + tokio::fs::write(leaf.join("stray.txt"), b"x") + .await + .unwrap(); + assert_eq!( + check_vendored_artifact(&fx.root, &first, &rec).await, + ArtifactHealth::Corrupt { + reason: "vendor_inventory_mismatch".into() + } + ); + let (result, entry, warnings) = done_parts(run(&fx, UUID, false).await); + assert!(result.0, "{result:?}"); + assert_eq!(codes(&warnings), ["vendor_artifact_rebuilt"]); + let mut entry = entry.unwrap(); + crate::vendor::state::carry_forward_wiring(&first, &mut entry); + persist(&fx, &entry).await; + assert_eq!( + check_vendored_artifact(&fx.root, &entry, &rec).await, + ArtifactHealth::Healthy + ); + assert!(!leaf.join("node_modules/.DS_Store").exists()); + assert!(!leaf.join("stray.txt").exists()); + let (result, entry, warnings) = done_parts(run(&fx, UUID, false).await); + assert!( + result.0 && entry.is_none() && warnings.is_empty(), + "{warnings:?}" + ); + } + + #[cfg(unix)] + #[tokio::test] + async fn a_rebuild_keeps_vlt_links_and_says_to_reinstall_when_it_cannot() { + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (first, _) = entry_of(run(&fx, UUID, false).await); + persist(&fx, &first).await; + let rel_abs = fx.root.join(&first.artifact.path); + let links = rel_abs.join("node_modules"); + tokio::fs::create_dir_all(links.join(".bin")).await.unwrap(); + std::os::unix::fs::symlink( + "../../../../../../../../node_modules/.vlt/z", + links.join("z"), + ) + .unwrap(); + tokio::fs::write(links.join(".bin/tool"), b"#!/bin/sh\n") + .await + .unwrap(); + tokio::fs::remove_file(rel_abs.join("index.js")) + .await + .unwrap(); + + let (_, entry, warnings) = done_parts(run(&fx, UUID, false).await); + assert!(entry.is_some()); + assert_eq!(codes(&warnings), ["vendor_artifact_rebuilt"]); + assert!(!warnings[0].detail.contains("vlt install"), "{warnings:?}"); + assert_eq!( + std::fs::read_link(links.join("z")).unwrap(), + std::path::Path::new("../../../../../../../../node_modules/.vlt/z") + ); + assert!(links.join(".bin/tool").is_file()); + assert_eq!( + tokio::fs::read(rel_abs.join("index.js")).await.unwrap(), + PATCHED + ); + + tokio::fs::write(links.join("planted.js"), b"x") + .await + .unwrap(); + let (_, entry, warnings) = done_parts(run(&fx, UUID, false).await); + assert!(entry.is_some()); + assert_eq!(codes(&warnings), ["vendor_artifact_rebuilt"]); + assert!( + warnings[0].detail.contains("run `vlt install`"), + "{warnings:?}" + ); + assert!(!links.exists()); + } + + #[tokio::test] + async fn an_in_sync_rerun_restores_the_uuid_metadata() { + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let (first, _) = entry_of(run(&fx, UUID, false).await); + persist(&fx, &first).await; + let dir = uuid_dir(&fx, UUID); + tokio::fs::write(dir.join(".gitignore"), b"*\n") + .await + .unwrap(); + tokio::fs::remove_file(dir.join(".gitattributes")) + .await + .unwrap(); + let (result, entry, warnings) = done_parts(run(&fx, UUID, false).await); + assert!( + result.0 && entry.is_none() && warnings.is_empty(), + "{warnings:?}" + ); + assert_eq!( + tokio::fs::read_to_string(dir.join(".gitignore")) + .await + .unwrap(), + super::super::npm_dir::UUID_GITIGNORE + ); + assert_eq!( + tokio::fs::read_to_string(dir.join(".gitattributes")) + .await + .unwrap(), + super::super::npm_dir::UUID_GITATTRIBUTES + ); + } + + #[cfg(unix)] + #[tokio::test] + async fn a_gitignored_payload_refuses_and_unwinds() { + let Some(git) = crate::utils::process::resolve_tool("git") else { + return; + }; + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let status = std::process::Command::new(&git) + .args(["init", "-q"]) + .current_dir(&fx.root) + .status() + .unwrap(); + assert!(status.success()); + tokio::fs::write(fx.root.join(".gitignore"), ".socket/\n") + .await + .unwrap(); + let (code, detail) = refusal(run(&fx, UUID, false).await); + assert_eq!(code, "vendor_artifact_gitignored", "{detail}"); + assert!(detail.contains(".gitignore:1:.socket/"), "{detail}"); + assert!(!uuid_dir(&fx, UUID).exists()); + assert_eq!(read(&fx, VLT_LOCK).await, basic_lock()); + assert_eq!(read(&fx, PACKAGE_JSON).await, ROOT_PKG); + } + + #[tokio::test] + async fn a_bundling_package_refuses_on_the_local_build() { + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + tokio::fs::write( + fx.installed.join(PACKAGE_JSON), + "{\"name\":\"left-pad\",\"version\":\"1.3.0\",\"bundleDependencies\":[\"x\"]}", + ) + .await + .unwrap(); + let (code, _) = refusal(run(&fx, UUID, false).await); + assert_eq!(code, "vendor_bundled_deps_unsupported"); + assert!(!fx.root.join(".socket").exists()); + assert_eq!(read(&fx, VLT_LOCK).await, basic_lock()); + } + + #[tokio::test] + async fn the_service_tree_is_pruned_and_a_bundling_one_refuses() { + use crate::vendor::test_support::{mount_granted, service_cfg}; + use crate::vendor::verify::{check_vendored_artifact, ArtifactHealth}; + use crate::vendor::VendorSource; + let server = wiremock::MockServer::start().await; + let tgz = service_tgz(&[ + ( + "package/package.json", + tar::EntryType::Regular, + b"{\"name\":\"left-pad\",\"version\":\"1.3.0\"}", + ), + ("package/index.js", tar::EntryType::Regular, PATCHED), + ( + "package/node_modules/x/index.js", + tar::EntryType::Regular, + b"bundled", + ), + ]); + mount_granted(&server, UUID, "left-pad-1.3.0.tgz", &tgz).await; + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let cfg = service_cfg(&server.uri(), VendorSource::Auto, false); + let (entry, warnings) = entry_of(run_with(&fx, &cfg).await); + assert!( + codes(&warnings).contains(&"vendor_prebuilt_downloaded"), + "{warnings:?}" + ); + assert!(!fx + .root + .join(&entry.artifact.path) + .join("node_modules") + .exists()); + assert_eq!( + check_vendored_artifact(&fx.root, &entry, &record(UUID)).await, + ArtifactHealth::Healthy + ); + + let server = wiremock::MockServer::start().await; + let tgz = service_tgz(&[ + ( + "package/package.json", + tar::EntryType::Regular, + b"{\"name\":\"left-pad\",\"version\":\"1.3.0\",\"bundleDependencies\":[\"x\"]}", + ), + ("package/index.js", tar::EntryType::Regular, PATCHED), + ( + "package/node_modules/x/index.js", + tar::EntryType::Regular, + b"bundled", + ), + ]); + mount_granted(&server, UUID, "left-pad-1.3.0.tgz", &tgz).await; + let fx = self::fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let cfg = service_cfg(&server.uri(), VendorSource::Auto, false); + let (code, _) = refusal(run_with(&fx, &cfg).await); + assert_eq!(code, "vendor_bundled_deps_unsupported"); + assert!(!fx.root.join(".socket/vendor").exists()); + assert_eq!(read(&fx, VLT_LOCK).await, basic_lock()); + } + + #[tokio::test] + async fn a_service_tree_without_the_patched_files_falls_back_or_fails_closed() { + use crate::vendor::test_support::{mount_granted, service_cfg}; + use crate::vendor::VendorSource; + let server = wiremock::MockServer::start().await; + let tgz = service_tgz(&[ + ( + "package/package.json", + tar::EntryType::Regular, + b"{\"name\":\"left-pad\",\"version\":\"1.3.0\"}", + ), + ( + "package/index.js", + tar::EntryType::Regular, + b"not the patch", + ), + ]); + mount_granted(&server, UUID, "left-pad-1.3.0.tgz", &tgz).await; + + let fx = fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let cfg = service_cfg(&server.uri(), VendorSource::Auto, false); + let (entry, warnings) = entry_of(run_with(&fx, &cfg).await); + assert!( + codes(&warnings).contains(&"vendor_prebuilt_layout_mismatch"), + "{warnings:?}" + ); + assert_eq!( + tokio::fs::read(fx.root.join(&entry.artifact.path).join("index.js")) + .await + .unwrap(), + PATCHED, + "the local build replaced the service tree" + ); + + let fx = self::fx(&basic_lock(), &[(PACKAGE_JSON, ROOT_PKG)]).await; + let cfg = service_cfg(&server.uri(), VendorSource::Service, false); + let (result, entry, _) = done_parts(run_with(&fx, &cfg).await); + assert!(!result.0 && entry.is_none(), "{result:?}"); + assert!( + result + .1 + .unwrap() + .contains("does not carry the patched files"), + "fails closed" + ); + assert_eq!(read(&fx, VLT_LOCK).await, basic_lock()); + assert!(!fx.root.join(".socket/vendor").exists()); + } } From 0ffa6effc5fd3037f740158a83d96e2079393745 Mon Sep 17 00:00:00 2001 From: Mikola Lysenko Date: Sat, 26 Sep 2026 02:01:48 -0400 Subject: [PATCH 17/46] Attest vlt locks in manifest-less VEX `socket-patch vex` and every embedded `--vex` now read vlt-lock.json: a Socket-hosted registry node (patch URL plus sha512, any DepID era) and a vendored vlt package directory become attestation inputs, so a vlt project attests its patches without .socket/manifest.json or the ledgers, and a redirect or vendor ledger stays live only while the vlt lock still wires it. A lock vlt cannot read (BOM, unknown version) wires nothing. Hosted npm packages are now judged by every store variant of their installed copies (pnpm and vlt peer, modifier and registry-alias instances), and a vlt package that another registry also resolves at the same version no longer attests before install. Vendored vlt directories verify with the package.json exemption, including the out-of-sync check of the installed link, and setup.manual accepts `vlt`. Assisted-by: Claude Code:claude-opus-5-5 --- CHANGELOG.md | 11 + crates/socket-patch-cli/CLI_CONTRACT.md | 1 + .../socket-patch-cli/src/commands/scan/mod.rs | 46 + crates/socket-patch-cli/src/commands/vex.rs | 3 +- .../src/commands/vex_consumed.rs | 68 +- .../src/commands/vex_sources.rs | 38 +- .../tests/e2e_embedded_vex.rs | 95 + .../tests/e2e_vex_lockfile/main.rs | 1 + .../tests/e2e_vex_lockfile/vlt.rs | 296 + .../tests/e2e_vex_redirect.rs | 131 +- .../tests/vex_e2e_common/vlt.rs | 357 ++ .../src/patch/redirect/mod.rs | 2 +- .../src/vendor/lock_inventory/vlt.rs | 21 +- crates/socket-patch-core/src/vendor/verify.rs | 43 + .../socket-patch-core/src/vex/discover/mod.rs | 110 +- .../socket-patch-core/src/vex/discover/vlt.rs | 764 +++ crates/socket-patch-core/src/vex/verify.rs | 96 +- .../vex-discover-golden/redirect-npm.json | 4787 ++++++++++++++++- .../vex-discover-golden/vlt-locks.json | 716 ++- crates/socket-patch-core/tests/vlt_locks.rs | 95 +- 20 files changed, 7354 insertions(+), 327 deletions(-) create mode 100644 crates/socket-patch-cli/tests/e2e_vex_lockfile/vlt.rs create mode 100644 crates/socket-patch-cli/tests/vex_e2e_common/vlt.rs create mode 100644 crates/socket-patch-core/src/vex/discover/vlt.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a5aa36ae..f102e8df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -332,6 +332,17 @@ into the new version's section — see docs/releasing.md. slots and outgoing edge values vlt rewrote since) or keeps everything on drift. Lock inventory reads `vlt-lock.json` too, Socket-hosted pins included. +- **`vex` reads `vlt-lock.json`.** Manifest-less VEX (and the ledger + liveness gates behind `vex`, `scan`'s takeovers and the + `hosted_wiring_retained` advisory) discovers hosted vlt nodes (a Socket + URL and sha512 on a registry node, every DepID era) and vendored vlt + package directories, and verifies a vendored directory with the vlt + `package.json` exemption, including the out-of-sync check of the + installed link. A lock vlt cannot read (BOM, other `lockfileVersion`) + wires nothing. A hosted npm package is now judged by every store variant + of its installed copies (pnpm and vlt peer, modifier and registry-alias + instances), and a same-version instance on another registry keeps a vlt + hosted pin from attesting before install. `setup.manual` accepts `vlt`. - **`redirect_yarn_berry_mixed_line_endings` and `vendor_yarn_berry_mixed_line_endings`.** A `yarn.lock` (or, vendored, a root `package.json`) that mixes CRLF and LF line endings — or holds a bare diff --git a/crates/socket-patch-cli/CLI_CONTRACT.md b/crates/socket-patch-cli/CLI_CONTRACT.md index 1947b076..9c7fecf6 100644 --- a/crates/socket-patch-cli/CLI_CONTRACT.md +++ b/crates/socket-patch-cli/CLI_CONTRACT.md @@ -199,6 +199,7 @@ Discovery is read-only, never touches the network, and never fails the run: a ma | pnpm | `pnpm-lock.yaml` (every `lockfileVersion`); `shrinkwrap.yaml` only when there is no `pnpm-lock.yaml`; with `rush.json`, `common/config/rush/pnpm-lock.yaml` + `common/config/subspaces/*/pnpm-lock.yaml` | `packages:` `resolution.tarball` on the patch host | `file:.socket/vendor/npm/…` tarball + key | `integrity`, required | | yarn | `yarn.lock` (classic and berry) | classic `resolved`; berry `resolution: …::__archiveUrl=` | classic `resolved "file:./.socket/vendor/npm/…#"`; berry `file:` entry **plus** a root `package.json` `resolutions` mapping onto the same artifact (without it the entry is orphaned: diagnosed, no ref) | classic `integrity` / `#sha1`, berry `checksum`, required | | bun | `bun.lock`; `bun.lockb` only when there is no `bun.lock` (bun reads exactly one) | URL tuple / binary remote-tarball resolution; version from the URL leaf | `.socket/vendor/npm//-.tgz` tuple / local-tarball resolution | `sha512-…`, required. A 2-tuple that Bun < 1.3.10 re-saved without its digest is still a reference, but it attests only from an installed tree. | +| vlt | `vlt-lock.json` (lockfileVersion absent, `0` or `1`; a BOM-prefixed, non-object or other-version lock is unreadable to vlt: diagnosed, no ref). `vlt.json` and `node_modules/.vlt-lock.json` are never wiring. | a registry node (any segment) whose slot [3] is on the patch host with the leaf `-.tgz` of its DepID's `name@version` and slot [1] == name; version from the DepID | a `file` node `.socket/vendor/npm//-/node_modules/` (or a user-installed `-.tgz`) with slot [1] == name; version from the path. A same-`name@version` registry node beside it is diagnosed, no ref. | slot [2] `sha512-…`, required (a hosted node without one is no reference). A same-`name@version` node on another registry keeps the reference but withholds the lockfile basis: only an installed tree whose every store copy verifies attests. | | cargo | `Cargo.lock`, `Cargo.toml`, `.cargo/config` (else `.cargo/config.toml`) | `Cargo.lock` `source = "sparse+…//index/"`, confirmed by `Cargo.toml`: a crate the root manifest declares must pin `registry = "socket-patch-"`. A reverted pin is diagnosed, no ref. | `[patch.] = { path = ".socket/vendor/cargo//-" }` — primarily the root `Cargo.toml` (v5 `vendor`; key-agnostic: `` is `package` when renamed, else the key, so `-socket-` keys count), also the project config (pre-v5 wiring), live only while the lock holds a sourceless entry for it that is not in `[[patch.unused]]`; a manifest entry cargo ignores — the project config redefines its key with another path, or a `[patch."https://github.com/rust-lang/crates.io-index"]` table replaces `[patch.crates-io]` — is diagnosed (`patched_ref_invalid`), no ref | `checksum` (v1: `[metadata]`), required | | golang | `go.mod`, `go.work`, `go.sum`, `go.work.sum` | `replace M v => patch.socket.dev/gopatch/ ` | `replace M v => ./.socket/vendor/golang//M@v` | both go.sum lines, required. A replace that `require` no longer selects (`require M v'`) is inert: diagnosed, no ref. | | pypi | `uv.lock` (confirmed by `pyproject.toml` `[tool.uv.sources]` when present), PEP 723 `