Skip to content

Commit b29d415

Browse files
committed
Find global package managers on Windows
Global mode asked npm, yarn, pnpm, bun, gem and composer where their global installs live by spawning the bare tool name. On Windows those tools are .cmd/.bat shims that a bare spawn never finds, so scan -g silently reported nothing to patch. The yarn probe also ran inside the scanned project, where Yarn Berry runs the project's own "global" script and its output picked the directory scanned as global. Probes now resolve the tool through PATHEXT (and never from a relative PATH entry), npm-family global probes run from the home directory, and Composer's home falls back to %APPDATA%\Composer and $XDG_CONFIG_HOME/composer like Composer does. Fixes #421, #434, #438, #440. Assisted-by: Claude Code:claude-opus-5-5
1 parent 00664aa commit b29d415

131 files changed

Lines changed: 2551 additions & 791 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CHANGELOG.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ limits, and required install commands.
102102

103103
### Fixed
104104

105+
- Global mode (`-g`) finds npm, yarn, pnpm, bun, RubyGems and Composer on
106+
Windows, where they install as `.cmd` / `.bat` shims, instead of reporting
107+
an empty scan. The yarn and npm-family global lookups no longer run from the
108+
scanned project, so a Yarn Berry project's `global` script can't run or pick
109+
the directory treated as the global install. Composer's global home also
110+
falls back to `%APPDATA%\Composer` and `$XDG_CONFIG_HOME/composer`.
105111
- Patch application, reversal, and cleanup handle missing files, release variants,
106112
corrupt state, newer ledger formats, and unsafe manifest paths without silently
107113
dropping protection. File ownership restoration failures produce warnings.

‎crates/socket-patch-cli/src/commands/apply.rs‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ use clap::Args;
22
use socket_patch_core::api::blob_fetcher::get_missing_blobs;
33
use socket_patch_core::api::client::{get_api_client_with_overrides, ApiClient};
44
use socket_patch_core::crawlers::ruby_crawler::config_path_ignored_warning;
5-
use socket_patch_core::crawlers::{
6-
detect_npm_pkg_manager, Ecosystem, NpmPkgManager, RubyCrawler,
7-
};
5+
use socket_patch_core::crawlers::{detect_npm_pkg_manager, Ecosystem, NpmPkgManager, RubyCrawler};
86
use socket_patch_core::manifest::operations::read_manifest;
97
use socket_patch_core::manifest::schema::{PatchFileInfo, PatchManifest, PatchRecord};
108
use socket_patch_core::patch::apply::{

‎crates/socket-patch-cli/src/commands/list.rs‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,10 @@ pub async fn run(args: ListArgs) -> i32 {
431431
detail: detail.clone(),
432432
});
433433
} else if !args.common.silent {
434-
eprintln!("Warning: {}", crate::commands::rollback::capitalize_first(detail));
434+
eprintln!(
435+
"Warning: {}",
436+
crate::commands::rollback::capitalize_first(detail)
437+
);
435438
}
436439
}
437440
let vendor_state = crate::commands::vendor_state_lenient(&loaded.vendor, args.common.silent);
@@ -773,12 +776,18 @@ mod tests {
773776
let listings = HostedListing::from_pins(
774777
&[
775778
pin("pkg:npm/minimist@1.2.2", &record.uuid),
776-
pin("pkg:npm/other@1.0.0", "33333333-3333-4333-8333-333333333333"),
779+
pin(
780+
"pkg:npm/other@1.0.0",
781+
"33333333-3333-4333-8333-333333333333",
782+
),
777783
],
778784
Some(&legacy),
779785
);
780786
assert_eq!(listings[0].record, record);
781-
assert_eq!(listings[1].record.uuid, "33333333-3333-4333-8333-333333333333");
787+
assert_eq!(
788+
listings[1].record.uuid,
789+
"33333333-3333-4333-8333-333333333333"
790+
);
782791
assert!(listings[1].record.vulnerabilities.is_empty());
783792
assert_eq!(listings[1].lockfiles, vec!["yarn.lock".to_string()]);
784793
}

‎crates/socket-patch-cli/src/commands/mod.rs‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
pub mod apply;
22
pub(crate) mod bun_preflight;
3-
pub(crate) mod context;
43
pub(crate) mod composer_hints;
4+
pub(crate) mod context;
55
pub(crate) mod fetch_stage;
66
pub mod get;
77
pub mod hosted_bundle;
88
pub mod list;
99
pub(crate) mod lock_cli;
1010
pub mod remove;
1111
pub mod repair;
12-
pub(crate) mod vendored_backend;
1312
pub mod rollback;
1413
pub mod scan;
1514
pub mod update;
1615
pub mod vendor;
16+
pub(crate) mod vendored_backend;
1717
pub mod vex;
1818
pub(crate) mod vex_consumed;
1919
pub(crate) mod vex_sources;
@@ -97,9 +97,11 @@ pub(crate) async fn hosted_state_from_lockfiles(
9797
common: &crate::args::GlobalArgs,
9898
root: &Path,
9999
) -> socket_patch_core::patch::redirect::RedirectState {
100-
hosted_state_from_pins(&socket_patch_core::patch::redirect::upstream::HostedPin::all(
101-
&discover_wiring(common, root).await,
102-
))
100+
hosted_state_from_pins(
101+
&socket_patch_core::patch::redirect::upstream::HostedPin::all(
102+
&discover_wiring(common, root).await,
103+
),
104+
)
103105
}
104106

105107
/// [`hosted_state_from_lockfiles`] over already-discovered pins. A purl
@@ -109,18 +111,17 @@ pub(crate) fn hosted_state_from_pins(
109111
) -> socket_patch_core::patch::redirect::RedirectState {
110112
let mut state = socket_patch_core::patch::redirect::RedirectState::new();
111113
for pin in pins {
112-
state
113-
.records
114-
.entry(pin.purl.clone())
115-
.or_insert_with(|| socket_patch_core::manifest::schema::PatchRecord {
114+
state.records.entry(pin.purl.clone()).or_insert_with(|| {
115+
socket_patch_core::manifest::schema::PatchRecord {
116116
uuid: pin.uuid.clone(),
117117
exported_at: String::new(),
118118
files: Default::default(),
119119
vulnerabilities: Default::default(),
120120
description: String::new(),
121121
license: String::new(),
122122
tier: String::new(),
123-
});
123+
}
124+
});
124125
}
125126
state
126127
}
@@ -147,4 +148,3 @@ pub(crate) fn vendor_state_lenient(
147148
}
148149
}
149150
}
150-

‎crates/socket-patch-cli/src/commands/remove.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ use super::rollback::{
1717
pin_before_hash_blobs, rollback_patches_inner, run_hosted_leg, sweep_failure,
1818
sweep_unused_artifacts, HostedLegOutcome, InnerSelection,
1919
};
20-
use crate::commands::vendored_backend::{RevertedEntry, VendorRevertStep, VendoredBackend};
2120
use crate::args::{apply_env_toggles, GlobalArgs};
2221
use crate::commands::lock_cli::acquire_or_emit;
22+
use crate::commands::vendored_backend::{RevertedEntry, VendorRevertStep, VendoredBackend};
2323
use crate::json_envelope::{Command, Envelope, EnvelopeError, PatchAction, PatchEvent, Status};
2424
use crate::ui::plural;
2525

‎crates/socket-patch-cli/src/commands/rollback.rs‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ use socket_patch_core::manifest::operations::{
1010
};
1111
use socket_patch_core::manifest::schema::{PatchFileInfo, PatchManifest, PatchRecord};
1212
use socket_patch_core::patch::apply::select_installed_variants;
13+
use socket_patch_core::patch::redirect::upstream::HostedPin;
1314
use socket_patch_core::patch::rollback::{
1415
cannot_rollback_error, rollback_package_patch, verify_file_rollback, RollbackResult,
1516
VerifyRollbackResult, VerifyRollbackStatus,
1617
};
1718
use socket_patch_core::telemetry::{track_patch_rollback_failed, track_patch_rolled_back};
1819
use socket_patch_core::utils::purl::{patch_matches, strip_purl_qualifiers};
19-
use socket_patch_core::patch::redirect::upstream::HostedPin;
2020
use socket_patch_core::vendor::{purl_keys_cover, RevertOpts, VendorState};
2121
use std::collections::{HashMap, HashSet};
2222
use std::path::{Path, PathBuf};
@@ -1026,7 +1026,8 @@ pub(crate) async fn run_hosted_leg(common: &GlobalArgs, pins: &[HostedPin]) -> H
10261026
.iter()
10271027
.map(|(code, detail)| (code.to_string(), detail.clone())),
10281028
);
1029-
out.edited_files.extend(outcome.reverted_files.iter().cloned());
1029+
out.edited_files
1030+
.extend(outcome.reverted_files.iter().cloned());
10301031
let unwound: Vec<_> = vlt_targets
10311032
.into_iter()
10321033
.filter(|t| out.reverted.iter().any(|p| p == &t.purl))
@@ -1157,7 +1158,11 @@ pub async fn run(args: RollbackArgs) -> i32 {
11571158
} else if !args.common.silent {
11581159
println!(
11591160
"{} the pre-v5 hosted ledger {}: no lockfile pins a hosted patch.",
1160-
if args.common.dry_run { "Would remove" } else { "Removed" },
1161+
if args.common.dry_run {
1162+
"Would remove"
1163+
} else {
1164+
"Removed"
1165+
},
11611166
socket_patch_core::patch::redirect::REDIRECT_STATE_REL
11621167
);
11631168
}

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

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,8 @@ pub(crate) async fn run_redirect_selected(
934934
socket_patch_core::utils::fs::read_regular_to_string_sync(path).ok()
935935
})
936936
};
937-
let rewrite_options = || RewriteOptions {
937+
let rewrite_options = || {
938+
RewriteOptions {
938939
dry_run: common.dry_run,
939940
targets_pipenv_lock,
940941
pipenv_major,
@@ -946,6 +947,7 @@ pub(crate) async fn run_redirect_selected(
946947
npm_allow_remote_config: !common.no_npm_allow_remote_config,
947948
npm_outer: &npm_outer,
948949
blocking: true,
950+
}
949951
};
950952
// The rollout gate plans again without its deferred rows: keep what
951953
// the second pass needs.
@@ -2171,13 +2173,19 @@ fn join_names(names: &[String], max: usize) -> String {
21712173
/// artifacts, then verify with `vex`. After a vendored→hosted takeover
21722174
/// (`vendored_removed`) the commit also has to carry the deleted vendored
21732175
/// ledger entries and artifacts.
2174-
fn format_next_steps(files: &[String], edits: &[socket_patch_core::patch::redirect::FileEdit], vendored_removed: bool) -> Vec<String> {
2176+
fn format_next_steps(
2177+
files: &[String],
2178+
edits: &[socket_patch_core::patch::redirect::FileEdit],
2179+
vendored_removed: bool,
2180+
) -> Vec<String> {
21752181
if files.is_empty() && !vendored_removed {
21762182
return Vec::new();
21772183
}
21782184
let mut commit: Vec<String> = Vec::new();
21792185
if vendored_removed {
2180-
commit.push(".socket/vendor/ (the removed vendored ledger entries and artifacts)".to_string());
2186+
commit.push(
2187+
".socket/vendor/ (the removed vendored ledger entries and artifacts)".to_string(),
2188+
);
21812189
}
21822190
commit.extend(files.iter().cloned());
21832191
let npm = files
@@ -4095,19 +4103,43 @@ mod tests {
40954103
use super::npm_allow_remote_one_line;
40964104
let hosts = ["patch.socket.dev"];
40974105
let cases = [
4098-
(npm_allow_remote_configured_detail(&hosts, true, false), "Note: set"),
4099-
(npm_allow_remote_configured_detail(&hosts, false, false), "Note: set"),
4100-
(npm_allow_remote_configured_detail(&hosts, true, true), "Note: would set"),
4101-
(npm_allow_remote_already_detail(&hosts), "Note: .npmrc already"),
4102-
(npm_allow_remote_user_set_detail(&hosts, "none"), "Warning: npm >=12"),
4103-
(npm_allow_remote_env_set_detail(&hosts, "npm_config_allow_remote", "none"), "Warning: npm >=12"),
4106+
(
4107+
npm_allow_remote_configured_detail(&hosts, true, false),
4108+
"Note: set",
4109+
),
4110+
(
4111+
npm_allow_remote_configured_detail(&hosts, false, false),
4112+
"Note: set",
4113+
),
4114+
(
4115+
npm_allow_remote_configured_detail(&hosts, true, true),
4116+
"Note: would set",
4117+
),
4118+
(
4119+
npm_allow_remote_already_detail(&hosts),
4120+
"Note: .npmrc already",
4121+
),
4122+
(
4123+
npm_allow_remote_user_set_detail(&hosts, "none"),
4124+
"Warning: npm >=12",
4125+
),
4126+
(
4127+
npm_allow_remote_env_set_detail(&hosts, "npm_config_allow_remote", "none"),
4128+
"Warning: npm >=12",
4129+
),
41044130
(npm_allow_remote_manual_detail(&hosts), "Warning: npm >=12"),
4105-
(npm_allow_remote_unreadable_detail(&hosts, "is a symlink"), "Warning: npm >=12"),
4131+
(
4132+
npm_allow_remote_unreadable_detail(&hosts, "is a symlink"),
4133+
"Warning: npm >=12",
4134+
),
41064135
];
41074136
for (detail, start) in cases {
41084137
let line = npm_allow_remote_one_line(&detail);
41094138
assert!(line.starts_with(start), "{line}");
4110-
assert!(!line.contains('\n') && line.ends_with("(details: --verbose)."), "{line}");
4139+
assert!(
4140+
!line.contains('\n') && line.ends_with("(details: --verbose)."),
4141+
"{line}"
4142+
);
41114143
}
41124144
}
41134145
}

0 commit comments

Comments
 (0)