Skip to content
4 changes: 2 additions & 2 deletions crates/socket-patch-cli/CLI_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -1506,8 +1506,8 @@ command's integration tests check its output.
| `vendor_dep_manifest_unlocked` | refused | vendor (yarn classic): the patch rewrites the package's own `package.json` to depend on a descriptor (`name@range`) no `yarn.lock` block is keyed by — an added dependency, or an existing one moved to a new range. A descriptor every wired block's sub-map already names is not counted: yarn keys those blocks by the registry packument's ranges, which can differ from the tarball's `package.json` (#1578). yarn 1 builds its install graph from the lock, so the rewired block would name a dependency it never resolves: online frozen installs fetch it unpinned, `--offline` installs fail and every plain `yarn install` re-saves the lock (#591). Refused after staging and before any wiring is written (the staged uuid dir is removed); the detail names the descriptors. Remedy: lock them first (for example `yarn add <descriptor>`), then re-run. A dry run, which stages nothing, does not foresee it. Also `failed` on `repair` (and `vendor_check_failed` on `vendor --check`) for a lock an older release already vendored that way, with a block wired to the entry naming a descriptor no block locks and the recorded pre-vendor block did not (#1379); the lock is left as it is. Remedy: `yarn install` (without `--frozen-lockfile`) to lock them, then commit `yarn.lock`. |
| `redirect_yarn_classic_dep_manifest_unlocked` | `warnings[]` (warning) | scan/get `--mode hosted` (yarn classic): the served tarball's own `package.json` depends on a descriptor no `yarn.lock` block is keyed by and that the sub-maps of the registry blocks being pinned don't already name (yarn keys those by the packument's ranges, #1578). yarn 1 installs only what the lock names, so a pin would install the patched package without that dependency (#591). The dep is not pinned and never confirmed; the lock is left as it was. The served tarball is read on every run, so a pin an earlier release wrote without this check (#1462) gets the same warning and is never confirmed either; it stays in the lock until the dependency is locked or the pin is rolled back. Same remedy as `vendor_dep_manifest_unlocked`. |
| `redirect_yarn_classic_dep_manifest_rewritten` | `warnings[]` (warning) | scan/get `--mode hosted` (yarn classic): the served tarball's own `package.json` declares other dependencies than the pinned block's sub-maps, every descriptor already locked; the block's `dependencies:` / `optionalDependencies:` sub-maps are rewritten to match, in the shape yarn 1 writes for that manifest (#591, #1558). |
| `vendor_artifact_gitignored` | `failed` | vendor (vlt, the npm-family tarball flavors — npm, pnpm, bun, yarn classic, yarn berry — NuGet, and the JVM trees of Maven, Gradle, sbt and scala-cli): inside a git work tree, `git check-ignore --no-index` reports the new artifact's uuid directory (JVM: its tree root, such as `.socket/vendor/maven2`) as ignored by a rule its own `.gitignore` cannot override (such as a root `.socket/` or `vendor/` rule; the detail names the rule). Remedy: drop that rule for `.socket/vendor/`. Refused before any write, dry run included. A file rule such as `*.tgz`, `*.nupkg` or `*.jar` is overridden by the `!*` `.gitignore` vendoring writes into the uuid dir or tree root; if a written tarball, directory payload or nupkg still reads as ignored, the run refuses and removes the uuid dir it created. |
| `vendor_artifact_gitignore_unchecked` | warning | vendor (vlt, the npm-family tarball flavors and NuGet): git is installed but could not answer the ignore check for the written vendored directory (it failed to start, ran past 30 s, or `rev-parse` / `check-ignore` exited with an error); the package is vendored and the detail names what failed. Remedy: make sure no ignore rule covers `.socket/` before committing. Git absent, or a project outside any work tree, raises nothing. |
| `vendor_artifact_gitignored` | `failed` | vendor (vlt, the npm-family tarball flavors — npm, pnpm, bun, yarn classic, yarn berry — NuGet, every PyPI flavor, and the JVM trees of Maven, Gradle, sbt and scala-cli): inside a git work tree, `git check-ignore --no-index` reports the new artifact's uuid directory (JVM: its tree root, such as `.socket/vendor/maven2`) as ignored by a rule its own `.gitignore` cannot override (such as a root `.socket/` or `vendor/` rule; the detail names the rule). Remedy: drop that rule for `.socket/vendor/`. Refused before any write, dry run included. A file rule such as `*.tgz`, `*.nupkg`, `*.whl` or `*.jar` is overridden by the `!*` `.gitignore` vendoring writes into the uuid dir or tree root (PyPI's uuid dir also gets the `.gitattributes` that unsets a `*.whl filter=lfs` rule); if a written tarball, directory payload, nupkg or wheel still reads as ignored, the run refuses and removes the uuid dir it created. |
| `vendor_artifact_gitignore_unchecked` | warning | vendor (vlt, the npm-family tarball flavors, NuGet and every PyPI flavor): git is installed but could not answer the ignore check for the written vendored directory (it failed to start, ran past 30 s, or `rev-parse` / `check-ignore` exited with an error); the package is vendored and the detail names what failed. Remedy: make sure no ignore rule covers `.socket/` before committing. Git absent, or a project outside any work tree, raises nothing. |
| `vendor_ledger_entry_missing` | `failed` | vendor (vlt): the only installed copy is vlt's link to a committed vendored directory, but the vendor ledger has no entry for the package; restore `.socket/vendor/state.json` from version control (v5.0: `repair` no longer re-synthesizes it). Replaces the `package_not_installed` skip. |
| `vendor_variant_ambiguous` | `failed` | vendor / scan / get `--mode vendored` (pypi, gem): the package is not installed and the manifest holds several release variants of it (`?artifact_id=` / `?platform=`), none of which the vendor ledger records, so nothing says which distribution to vendor; install the package or keep one release variant. A variant the ledger records (at any patch uuid) is taken as the wired one and its siblings are left out without an event. |
| `vendor_artifact_missing` | reason | The recorded artifact is missing; repair requires an online exact redownload. |
Expand Down
33 changes: 33 additions & 0 deletions crates/socket-patch-cli/tests/in_process_vendor_pypi_takeover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,3 +412,36 @@ async fn scan_vendored_over_hosted_marker_split_requirements_never_unpatches() {
assert_eq!(now, hosted, "the hosted pin stays byte-for-byte: {env:#}");
}
}

/// #1572: a `.socket/` ignore rule would keep the vendored wheel out of the
/// commit, and the uuid dir's own `.gitignore` can't re-include it. The
/// vendoring refuses, and the takeover keeps the hosted pin, so six stays
/// hosted instead of ending up neither hosted nor vendored.
#[tokio::test(flavor = "multi_thread")]
async fn scan_vendored_over_hosted_with_socket_dir_ignored_keeps_the_hosted_pin() {
let (_tmp, root) = project();
let Ok(status) = std::process::Command::new("git")
.args(["init", "-q"])
.current_dir(&root)
.status()
else {
return;
};
assert!(status.success());
std::fs::write(root.join("requirements.txt"), "six==1.16.0\n").unwrap();
let files: &[&str] = &["requirements.txt"];
std::fs::write(root.join(".gitignore"), ".socket/\n").unwrap();
let (hosted, exit, env) = host_then_vendor(&root, files).await;
assert_eq!(exit, 1, "the refusal fails the run: {env:#}");
assert!(has_code(&env, "vendor_artifact_gitignored"), "{env:#}");
assert!(
!has_code(&env, "vendor_takeover_reverted_redirect"),
"a refused purl must not be reported as restored: {env:#}"
);
assert_eq!(
snapshot(&root, files),
hosted,
"the hosted pin stays byte-for-byte: {env:#}"
);
assert!(!root.join(format!(".socket/vendor/pypi/{UUID}")).exists());
}
10 changes: 6 additions & 4 deletions crates/socket-patch-core/src/vendor/npm_dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,10 +383,12 @@ pub(crate) async fn node_modules_holds_only_links(rel_abs: &Path) -> bool {
/// Rewrite `<uuid>/.gitignore` and `<uuid>/.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),
] {
write_uuid_metadata(uuid_dir, UUID_GITIGNORE).await
}

/// [`restore_uuid_metadata`] with the backend's own `.gitignore` text.
pub(crate) async fn write_uuid_metadata(uuid_dir: &Path, gitignore: &str) -> std::io::Result<()> {
for (name, want) in [(GITIGNORE, 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()) {
Expand Down
Loading
Loading