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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,22 @@ into the new version's section — see docs/releasing.md.

### Fixed

- **Hosted nuget redirects survive a `<clear />` in `nuget.config`.** The
Socket source (and, in an existing `<packageSourceMapping>`, its
mapping) was inserted ahead of the section's `<clear />`, which NuGet
applies to everything read before it: `dotnet restore` then failed
NU1100 / NU1101 for the patched package. Both now land after the last
`<clear />`.

- **nuget redirects and vendoring edit the config NuGet actually reads.**
NuGet reads the first of `nuget.config`, `NuGet.config` and
`NuGet.Config` in a directory. Hosted mode only knew `nuget.config`
and vendored mode missed `NuGet.config`, so on a case-sensitive
filesystem they created a fresh `nuget.config` that shadowed the
project's own file: its sources and mappings vanished and private
packages failed restore. Both modes now edit the existing spelling in
place.

- **`rollback` fetches a before-blob that only a store peer variant
needs.** The before-blob gate now probes every pnpm and vlt store variant
copy the rollback restores, so an online rollback no longer fails
Expand Down
4 changes: 4 additions & 0 deletions crates/socket-patch-cli/src/commands/scan/hosted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ pub(crate) const REDIRECT_CANDIDATE_FILES: &[&str] = &[
// otherwise the `[registries.…]` block lands in a file cargo ignores.
".cargo/config",
"composer.lock",
// Every spelling NuGet probes: the rewriter edits the one NuGet reads
// rather than shadowing it with a fresh `nuget.config`.
"nuget.config",
"NuGet.config",
"NuGet.Config",
"packages.lock.json",
"Gemfile",
"Gemfile.lock",
Expand Down
2 changes: 1 addition & 1 deletion crates/socket-patch-cli/src/hosted_memory/redirect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ fn file_ecosystem(rel: &str) -> Option<&'static str> {
| "pyproject.toml" | "hatch.toml" => "pypi",
"Cargo.toml" | "Cargo.lock" | "config.toml" | "config" => "cargo",
"composer.lock" => "composer",
"nuget.config" | "packages.lock.json" => "nuget",
"nuget.config" | "NuGet.config" | "NuGet.Config" | "packages.lock.json" => "nuget",
"Gemfile" | "Gemfile.lock" | "gems.rb" | "gems.locked" => "gem",
"go.mod" | "go.sum" => "golang",
"pom.xml" | "maven.config" | "checksums.sha256" => "maven",
Expand Down
10 changes: 9 additions & 1 deletion crates/socket-patch-cli/src/hosted_memory/roots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,15 @@ pub(crate) const UNSUPPORTED_MARKERS: [(&str, &[&str]); 2] = [
"settings.gradle.kts",
],
),
("nuget", &["packages.lock.json", "nuget.config"]),
(
"nuget",
&[
"packages.lock.json",
"nuget.config",
"NuGet.config",
"NuGet.Config",
],
),
];

/// Directory names whose subtrees never hold a project root: installed
Expand Down
66 changes: 66 additions & 0 deletions crates/socket-patch-cli/tests/in_process_get_hosted_ecosystems.rs
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,72 @@ async fn nuget_hosted_wires_source_mapping_and_lock_hash() {
.unwrap();
}

/// NuGet reads `NuGet.config` when no `nuget.config` exists: the grant must
/// wire that file in place, not author a `nuget.config` that shadows it.
#[tokio::test]
#[serial]
async fn nuget_hosted_wires_mixed_case_config_in_place() {
const UUID: &str = "c4c4c4c4-c4c4-4c4c-8c4c-c4c4c4c4c4c4";
const PURL: &str = "pkg:nuget/Newtonsoft.Json@13.0.3";
let index_url = format!("http://patch.test/patch-registry/nuget/{TOKEN}/{UUID}/index.json");
let url = format!(
"http://patch.test/patch-registry/nuget/{TOKEN}/{UUID}/flat/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg"
);

let server = MockServer::start().await;
mock_view(&server, UUID, PURL).await;
mock_reference(
&server,
UUID,
PURL,
&url,
serde_json::json!({ "sha512": "sha512-NUGETPATCHED==" }),
serde_json::json!({
"kind": "nuget-v3",
"indexUrl": index_url,
"identifiers": {
"name": "Newtonsoft.Json",
"version": "13.0.3",
"nugetIdLower": "newtonsoft.json",
"nugetVersionNorm": "13.0.3",
}
}),
)
.await;

let tmp = tempfile::tempdir().unwrap();
std::fs::write(
tmp.path().join("NuGet.config"),
r#"<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="corp" value="https://nuget.corp.example/v3/index.json" />
</packageSources>
</configuration>
"#,
)
.unwrap();
let case_sensitive = !tmp.path().join("nuget.config").exists();

let code =
socket_patch_cli::commands::get::run(get_hosted_args(UUID, tmp.path(), server.uri())).await;
assert_eq!(code, 0, "get <uuid> --mode hosted (nuget) should succeed");

let config = std::fs::read_to_string(tmp.path().join("NuGet.config")).unwrap();
assert!(
config.contains(&format!(
r#"<add key="socket-patch-{UUID}" value="{index_url}" />"#
)) && config.contains(r#"<add key="corp""#),
"NuGet.config wired in place, its own source kept; got:\n{config}"
);
if case_sensitive {
assert!(
!tmp.path().join("nuget.config").exists(),
"no shadowing nuget.config authored"
);
}
}

/// The manifest-less VEX steps for a nuget hosted checkout `get` wired
/// (`http://patch.test` is the configured patch-server origin; nothing is
/// installed, so the lock's re-pinned `contentHash` is the evidence):
Expand Down
Loading
Loading