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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 11 additions & 26 deletions crates/socket-patch-cli/src/commands/vendor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -520,34 +520,19 @@ pub(crate) async fn persist_vendor_entry(
let candidate = candidate.to_string();
entry.detached = detached;
entry.record = detached.then(|| record.clone());
// A re-vendor run re-derives the entry from current
// disk state, where the takeover already happened —
// preserve the prior flag or the revert-time
// "takeover_not_restored" hint is lost.
// A re-vendor run re-derives the entry from current disk state, where
// the takeover / earlier wiring already happened. Reconcile the fresh
// entry with the one it replaces so `--revert` still knows how to undo
// every surface any earlier vendoring touched: carry forward the true
// pre-vendor originals (a re-vendor records `original: None` for its own
// stale `.socket/vendor/` pointer), the wiring records for surfaces this
// run left in sync (e.g. package.json + pnpm-lock.yaml when only the new
// pnpm-workspace.yaml override was added on a pnpm >= 11 upgrade), the
// pnpm created-surface bookkeeping, and the takeover flag. See
// [`vendor::carry_forward_wiring`].
let prev = state.entries.get(&candidate).cloned();
if let Some(prev) = &prev {
entry.took_over_go_patches = entry.took_over_go_patches || prev.took_over_go_patches;
// A re-vendor (new patch uuid) rewrites our own
// stale wiring, so the backend records
// `original: None` (it must never record a
// dangling `.socket/vendor/` pointer as the
// pre-vendor fragment). The TRUE pre-vendor
// original lives in the entry being replaced —
// carry it forward by wiring identity, or a
// later `--revert` can only shrug
// (`vendor_lock_entry_drifted`) instead of
// restoring the registry fragment.
for rec in &mut entry.wiring {
if rec.action == vendor::state::WiringAction::Rewritten && rec.original.is_none() {
if let Some(prev_rec) = prev
.wiring
.iter()
.find(|p| p.file == rec.file && p.kind == rec.kind && p.key == rec.key)
{
rec.original = prev_rec.original.clone();
}
}
}
vendor::carry_forward_wiring(prev, &mut entry);
}
let new_uuid = entry.uuid.clone();
state.entries.insert(candidate.clone(), entry);
Expand Down
42 changes: 37 additions & 5 deletions crates/socket-patch-cli/tests/e2e_vendor_pnpm_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
//! bytes (a marker comment prepended to `index.js`).
//! 3. `socket-patch vendor --json --offline` — assert the deterministic
//! tarball lands at `.socket/vendor/npm/<uuid>/…`, the root package.json
//! gains `pnpm.overrides`, and pnpm-lock.yaml carries the file:
//! resolution (spike P1: importer specifier+version rewritten, packages
//! entry rekeyed with the recomputed integrity).
//! gains `pnpm.overrides`, a `pnpm-workspace.yaml` is created carrying
//! the same `overrides:` (where pnpm >= 11 reads them) plus a root-only
//! `packages:` list, and pnpm-lock.yaml carries the file: resolution
//! (spike P1: importer specifier+version rewritten, packages entry
//! rekeyed with the recomputed integrity).
//! 4. **Fresh-checkout proof**: copy ONLY the committable files
//! (package.json + pnpm-lock.yaml + .socket/) to a new dir, an EMPTY
//! `--store-dir`, and run the spike's strictest invocation
Expand Down Expand Up @@ -368,6 +370,24 @@ fn run_pnpm_capstone(pm: &str) {
),
"the inherited registry integrity must NOT survive the rewrite:\n{lock_after}"
);

// pnpm >= 11 reads `overrides` only from pnpm-workspace.yaml, so vendoring
// mirrors the same versioned selector there. When the project had none
// (this fixture), it is CREATED with a root-only `packages:` list — pnpm 9
// refuses a workspace file whose `packages` field is missing/empty, and
// `.` cannot glob a stray subtree into the workspace the way `packages/*`
// could. That makes the committable set install on pnpm 9/10/11 alike.
let ws_path = proj.join("pnpm-workspace.yaml");
let ws_after =
std::fs::read_to_string(&ws_path).expect("vendoring must create pnpm-workspace.yaml");
assert!(
ws_after.contains(&format!("{DEP}@{DEP_VERSION}: file:{tgz_rel}")),
"pnpm-workspace.yaml `overrides:` must point at the vendored tarball; got:\n{ws_after}"
);
assert!(
ws_after.contains("packages:") && ws_after.contains("- '.'"),
"created pnpm-workspace.yaml must carry a root-only packages list; got:\n{ws_after}"
);
eprintln!("VENDOR OK ({pm})");

// 4. FRESH-CHECKOUT PROOF: committable files only, EMPTY store,
Expand All @@ -376,6 +396,7 @@ fn run_pnpm_capstone(pm: &str) {
std::fs::create_dir_all(&fresh).unwrap();
std::fs::copy(&pkg_path, fresh.join("package.json")).unwrap();
std::fs::copy(&lock_path, fresh.join("pnpm-lock.yaml")).unwrap();
std::fs::copy(&ws_path, fresh.join("pnpm-workspace.yaml")).unwrap();
copy_dir_recursive(&proj.join(".socket"), &fresh.join(".socket"));

let fresh_store = tmp.path().join("fresh-pnpm-store");
Expand Down Expand Up @@ -410,9 +431,10 @@ fn run_pnpm_capstone(pm: &str) {
);
eprintln!("FRESH INSTALL OK ({pm})");

// 5. Idempotency: a re-run exits 0 and leaves BOTH files byte-stable.
// 5. Idempotency: a re-run exits 0 and leaves ALL THREE files byte-stable.
let lock_wired = std::fs::read(&lock_path).unwrap();
let pkg_wired = std::fs::read(&pkg_path).unwrap();
let ws_wired = std::fs::read(&ws_path).unwrap();
let (code, stdout, stderr) = run_socket(
&proj,
&[
Expand All @@ -439,8 +461,14 @@ fn run_pnpm_capstone(pm: &str) {
pkg_wired,
"re-vendor must leave package.json byte-identical"
);
assert_eq!(
std::fs::read(&ws_path).unwrap(),
ws_wired,
"re-vendor must leave pnpm-workspace.yaml byte-identical"
);

// 6. REVERT PROOF: package.json AND pnpm-lock.yaml restored byte-for-byte.
// 6. REVERT PROOF: package.json AND pnpm-lock.yaml restored byte-for-byte,
// and the pnpm-workspace.yaml vendoring created is deleted.
let (code, stdout, stderr) = run_socket(
&proj,
&[
Expand Down Expand Up @@ -473,5 +501,9 @@ fn run_pnpm_capstone(pm: &str) {
!proj.join(".socket/vendor").exists(),
".socket/vendor must be fully removed after revert"
);
assert!(
!ws_path.exists(),
"revert must delete the pnpm-workspace.yaml vendoring created"
);
eprintln!("REVERT OK ({pm})");
}
105 changes: 37 additions & 68 deletions crates/socket-patch-cli/tests/e2e_vendored_production.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,23 +514,6 @@ fn minimist_entry(proj: &Path) -> PathBuf {
proj.join("node_modules").join(NPM_NAME).join("index.js")
}

/// Mirror the `pnpm.overrides` the CLI wrote into `package.json` over to a
/// `pnpm-workspace.yaml` `overrides:` block — the location pnpm >= 11 actually
/// reads (see [`pnpm_vendored_install_proof`]). Reads back exactly what the CLI
/// produced rather than hardcoding a value, so it exercises the real wiring.
fn write_pnpm_workspace_overrides(proj: &Path, leg: &str) {
let pkg: serde_json::Value =
serde_json::from_slice(&std::fs::read(proj.join("package.json")).unwrap()).unwrap();
let overrides = pkg["pnpm"]["overrides"].as_object().unwrap_or_else(|| {
panic!("{leg}: package.json carries no `pnpm.overrides` to mirror into pnpm-workspace.yaml")
});
let mut yaml = String::from("overrides:\n");
for (k, v) in overrides {
yaml.push_str(&format!(" '{k}': '{}'\n", v.as_str().unwrap_or_default()));
}
std::fs::write(proj.join("pnpm-workspace.yaml"), yaml).unwrap();
}

/// Locate `site-packages` inside a venv, across platforms and Python minors.
fn site_packages(venv: &Path) -> Option<PathBuf> {
if cfg!(windows) {
Expand Down Expand Up @@ -836,15 +819,28 @@ fn pnpm_vendored_install_proof() {
lock.contains(&tgz_rel),
"{LEG}: pnpm-lock.yaml was not rewired to the vendored tarball:\n{lock}"
);
// pnpm >= 11 reads `overrides` only from pnpm-workspace.yaml, so the CLI
// creates/updates it too. Assert it landed and points at the tarball.
let ws_path = proj.join("pnpm-workspace.yaml");
let ws = read(&ws_path);
assert!(
ws.contains(&tgz_rel),
"{LEG}: pnpm-workspace.yaml `overrides:` was not wired to the vendored tarball:\n{ws}"
);
let lock_wired = std::fs::read(proj.join("pnpm-lock.yaml")).unwrap();
let pkg_wired = std::fs::read(proj.join("package.json")).unwrap();
let ws_wired = std::fs::read(&ws_path).unwrap();

// DELIVERY PROOF: committable files only (pnpm also edits package.json —
// pnpm.overrides), empty store, frozen offline install.
// DELIVERY PROOF: committable files only (pnpm edits package.json's
// `pnpm.overrides` AND creates pnpm-workspace.yaml), empty store, frozen
// offline install. This must now succeed directly on pnpm >= 11 — the
// pnpm-workspace.yaml override is exactly what closes the old
// ERR_PNPM_LOCKFILE_CONFIG_MISMATCH gap.
let fresh = tmp.path().join("fresh");
std::fs::create_dir_all(&fresh).unwrap();
std::fs::copy(proj.join("package.json"), fresh.join("package.json")).unwrap();
std::fs::copy(proj.join("pnpm-lock.yaml"), fresh.join("pnpm-lock.yaml")).unwrap();
std::fs::copy(&ws_path, fresh.join("pnpm-workspace.yaml")).unwrap();
copy_dir_recursive(&proj.join(".socket"), &fresh.join(".socket"));

let fresh_store = tmp.path().join("fresh-pnpm-store").display().to_string();
Expand All @@ -862,55 +858,19 @@ fn pnpm_vendored_install_proof() {
];
let ci = tool(&fresh, "pnpm", &install_args, &fresh_env);
let entry = minimist_entry(&fresh);
if ok(&ci) {
// pnpm <= 10: the package.json `pnpm.overrides` the CLI wrote is honored.
assert_patched(&entry, PATCH_MARKER, LEG);
assert_ne!(
std::fs::read(&entry).unwrap(),
pristine,
"{LEG}: the reinstalled bytes equal the PRISTINE registry bytes"
);
} else {
// KNOWN CLI GAP (pnpm >= 11): pnpm no longer reads `overrides` from
// package.json's `pnpm` field — it moved to `pnpm-workspace.yaml`
// (https://pnpm.io/settings). The CLI still writes package.json
// `pnpm.overrides`, so pnpm 11 ignores it and the frozen install
// refuses with a lockfile/config mismatch even though the vendored
// tarball and lock are correct (the supply-chain policy passes). This
// is a real socket-patch compatibility gap, not a test bug: the pnpm
// vendor rewriter should also emit a `pnpm-workspace.yaml` `overrides`
// block on pnpm >= 11. Until it does, this leg reproduces the documented
// workaround (mirror the override into pnpm-workspace.yaml) to prove the
// vendored artifact IS installable, and fails loudly if the failure is
// anything OTHER than that known gap.
let detail = dump(&ci);
assert!(
detail.contains("ERR_PNPM_LOCKFILE_CONFIG_MISMATCH")
|| detail.contains("no longer read by pnpm"),
"{LEG}: `pnpm install --frozen-lockfile --offline` failed for an UNEXPECTED reason \
(not the known pnpm 11 overrides-field-moved gap). This is a new regression:\n{detail}"
);
println!(
"KNOWN CLI GAP {LEG}: pnpm >= 11 ignores package.json `pnpm.overrides` (moved to \
pnpm-workspace.yaml), so the CLI's vendored wiring does not take effect on a frozen \
install. Retrying with the documented pnpm-workspace.yaml workaround. socket-patch \
should emit that file for pnpm >= 11 during `scan --mode vendored`."
);
write_pnpm_workspace_overrides(&fresh, LEG);
let retry = tool(&fresh, "pnpm", &install_args, &fresh_env);
assert!(
ok(&retry),
"{LEG}: even with the pnpm-workspace.yaml overrides workaround the vendored tarball \
did not install — the artifact itself is not installable:\n{}",
dump(&retry)
);
assert_patched(&entry, PATCH_MARKER, LEG);
assert_ne!(
std::fs::read(&entry).unwrap(),
pristine,
"{LEG}: the reinstalled bytes equal the PRISTINE registry bytes"
);
}
assert!(
ok(&ci),
"{LEG}: `pnpm install --frozen-lockfile --offline` must install the vendored tarball \
from the committable files (no ERR_PNPM_LOCKFILE_CONFIG_MISMATCH — the \
pnpm-workspace.yaml override is what makes pnpm >= 11 honor it):\n{}",
dump(&ci)
);
assert_patched(&entry, PATCH_MARKER, LEG);
assert_ne!(
std::fs::read(&entry).unwrap(),
pristine,
"{LEG}: the reinstalled bytes equal the PRISTINE registry bytes"
);

let env2 = scan_vendored(&proj, &[]);
assert_eq!(
Expand All @@ -928,6 +888,11 @@ fn pnpm_vendored_install_proof() {
pkg_wired,
"{LEG}: re-run must leave package.json byte-identical"
);
assert_eq!(
std::fs::read(&ws_path).unwrap(),
ws_wired,
"{LEG}: re-run must leave pnpm-workspace.yaml byte-identical"
);

assert_eq!(vendor_revert(&proj, LEG), 1, "{LEG}: one entry reverted");
assert_eq!(
Expand All @@ -944,6 +909,10 @@ fn pnpm_vendored_install_proof() {
!proj.join(".socket/vendor").exists(),
"{LEG}: .socket/vendor must be gone after revert"
);
assert!(
!ws_path.exists(),
"{LEG}: revert must delete the pnpm-workspace.yaml vendoring created"
);
}

#[test]
Expand Down
5 changes: 4 additions & 1 deletion crates/socket-patch-core/src/vendor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ mod yarn_classic_lock;
mod yarn_layering_tests;

pub use path::{ecosystem_dir_for_purl, parse_vendor_path};
pub use state::{load_state, lookup_entry, save_state, VendorEntry, VendorState, VENDOR_STATE_REL};
pub use state::{
carry_forward_wiring, load_state, lookup_entry, save_state, VendorEntry, VendorState,
VENDOR_STATE_REL,
};
pub use verify::{check_vendored_artifact, file_sha256_hex, ArtifactHealth};

use std::collections::{HashMap, HashSet};
Expand Down
Loading
Loading