Skip to content

Commit 368ee0b

Browse files
Fix Composer vendored to hosted takeover (#536) (#1725)
* Start fix for #536 Assisted-by: Claude Code:claude-opus-5-5 * Test Composer vendored to hosted takeover Regression tests for #536: scan and get in hosted mode over a vendored Composer package must revert the vendored lock entry first, write the same lock a direct hosted scan writes, and stay restorable by rollback. The in-memory engine must refuse the takeover like other ecosystems. Assisted-by: Claude Code:claude-opus-5-5 * Take over vendored Composer packages in hosted scan and get in hosted mode now revert a vendored Composer package's composer.lock entry and committed copy before pinning it to the hosted archive, as cargo, npm, Go and PyPI already do. Before, the hosted rewriter edited the vendored entry in place and kept its patch-uuid dist.reference, so rollback refused and the vendored copy and ledger entry were orphaned. The in-memory hosted engine now refuses such a package instead of rewriting it. Fixes #536 Assisted-by: Claude Code:claude-opus-5-5 * Leave vendored Composer entries out of hosted When a vendored Composer package's ledger entry is lost, the hosted takeover cannot revert it, and the composer.lock rewriter used to repoint socket-patch's own vendored path entry in place, keeping the patch-uuid reference that rollback can never restore. The rewriter now leaves such an entry alone and warns redirect_composer_vendored_entry, in both the disk and in-memory hosted engines. Refs #536 Assisted-by: Claude Code:claude-opus-5-5 * Document the Composer vendored entry warning Assisted-by: Claude Code:claude-opus-5-5 * Give a working fix for the vendored entry warning redirect_composer_vendored_entry fires only when the vendored ledger entry is gone, so its advice to run vendor --revert could not clear it. It now tells the user to restore the registry entry with composer update or from version control. Assisted-by: Claude Code:claude-opus-5-5 * Say what composer update may also change The redirect_composer_vendored_entry advice now puts the version-control restore first and notes that a named composer update may also move the package and its dependents to newer versions, as the other Composer warnings already do. Assisted-by: Claude Code:claude-opus-5-5 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 37f70e7 commit 368ee0b

11 files changed

Lines changed: 526 additions & 3 deletions

File tree

‎crates/socket-patch-cli/CLI_CONTRACT.md‎

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

‎crates/socket-patch-cli/src/commands/scan/hosted/takeover.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
//! a committed copy; for the npm family a `file:./.socket/vendor/…` lock
66
//! resolution (plus a berry `resolutions` pin) and its committed tarball;
77
//! for golang the vendor-owned go.mod `replace` and its module copy; for
8-
//! pypi the vendored lock / requirements source; for a Gradle build the
8+
//! pypi the vendored lock / requirements source; for composer the
9+
//! vendored `composer.lock` path entry and its copy; for a Gradle build the
910
//! vendored JVM wiring — each with its vendored ledger entry. The hosted
1011
//! rewriters cannot pin over that wiring, so the takeover reverts it first
1112
//! (the per-purl machinery `vendor --revert` runs), which also hands the

‎crates/socket-patch-cli/tests/hosted_memory_engine.rs‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,56 @@ async fn vendored_pypi_takeover_is_refused_like_the_disk_flow() {
637637
assert!(output.changed_files.is_empty());
638638
}
639639

640+
/// #536: a vendored composer package is refused as a takeover too, rather
641+
/// than handed to the composer.lock rewriter, which would edit the vendored
642+
/// entry in place and keep its patch-uuid `dist.reference`.
643+
#[tokio::test]
644+
async fn vendored_composer_takeover_is_refused_like_the_disk_flow() {
645+
const COMPOSER_FIXTURE: &str = "redirect/composer/composer-lock/basic";
646+
let server = MockServer::start().await;
647+
let patches = patches_from_overrides(
648+
&fixtures_root()
649+
.join(COMPOSER_FIXTURE)
650+
.join("overrides.json"),
651+
None,
652+
);
653+
mount_api(&server, &patches).await;
654+
let mut files = fixture_files(&fixtures_root().join(COMPOSER_FIXTURE).join("input"));
655+
let uuid = "44444444-4444-4444-4444-444444444444";
656+
files.insert(
657+
".socket/vendor/state.json".into(),
658+
serde_json::to_vec(&serde_json::json!({
659+
"version": 1,
660+
"entries": {
661+
"pkg:composer/monolog/monolog@2.0.0": {
662+
"ecosystem": "composer",
663+
"basePurl": "pkg:composer/monolog/monolog@2.0.0",
664+
"uuid": uuid,
665+
"artifact": {"path": format!(".socket/vendor/composer/{uuid}/monolog/monolog@2.0.0")},
666+
"wiring": []
667+
}
668+
}
669+
}))
670+
.unwrap(),
671+
);
672+
let output = run_engine(&server, build_input(&files, &[], options(false))).await;
673+
let project = &output.projects[0];
674+
assert!(
675+
project
676+
.skipped
677+
.iter()
678+
.any(|s| s.reason == "vendored_takeover_unsupported_in_memory"),
679+
"{:?}",
680+
project.skipped
681+
);
682+
assert!(project.redirected.is_empty());
683+
let warnings = project.redirect["warnings"].as_array().unwrap();
684+
assert!(warnings
685+
.iter()
686+
.any(|w| w["code"] == "vendored_takeover_unsupported_in_memory"));
687+
assert!(output.changed_files.is_empty());
688+
}
689+
640690
/// A pre-v5 redirect ledger (`.socket/vendor/redirect-state.json`) is
641691
/// never read by the v5 engine: a torn one neither fails its project nor
642692
/// changes its plan, and the engine never emits (or rewrites) the file.

0 commit comments

Comments
 (0)