Skip to content

Commit 04171c6

Browse files
Wenxin-Jiangclaude
andcommitted
fix: only add postinstall to root package.json for pnpm monorepos
pnpm runs root lifecycle scripts on `pnpm install`, so adding postinstall scripts to individual workspace package.json files is unnecessary. Worse, it breaks because workspaces may not have `@socketsecurity/socket-patch` as a dependency, and pnpm's strict module isolation prevents `npx` from resolving it. The setup command now detects pnpm monorepos (via pnpm-workspace.yaml) and only updates the root package.json. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9294068 commit 04171c6

File tree

2 files changed

+55
-26
lines changed
  • crates
    • socket-patch-cli/src/commands
    • socket-patch-core/src/package_json

2 files changed

+55
-26
lines changed

crates/socket-patch-cli/src/commands/setup.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clap::Args;
2-
use socket_patch_core::package_json::find::find_package_json_files;
2+
use socket_patch_core::package_json::find::{find_package_json_files, WorkspaceType};
33
use socket_patch_core::package_json::update::{update_package_json, UpdateStatus};
44
use std::io::{self, Write};
55
use std::path::{Path, PathBuf};
@@ -30,7 +30,22 @@ pub async fn run(args: SetupArgs) -> i32 {
3030
println!("Searching for package.json files...");
3131
}
3232

33-
let package_json_files = find_package_json_files(&args.cwd).await;
33+
let find_result = find_package_json_files(&args.cwd).await;
34+
35+
// For pnpm monorepos, only update root package.json.
36+
// pnpm runs root postinstall on `pnpm install`, so workspace-level
37+
// postinstall scripts are unnecessary. Individual workspaces may not
38+
// have `@socketsecurity/socket-patch` as a dependency, causing
39+
// `npx @socketsecurity/socket-patch apply` to fail due to pnpm's
40+
// strict module isolation.
41+
let package_json_files = match find_result.workspace_type {
42+
WorkspaceType::Pnpm => find_result
43+
.files
44+
.into_iter()
45+
.filter(|loc| loc.is_root)
46+
.collect(),
47+
_ => find_result.files,
48+
};
3449

3550
if package_json_files.is_empty() {
3651
if args.json {

crates/socket-patch-core/src/package_json/find.rs

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,17 @@ pub struct PackageJsonLocation {
2525
pub workspace_pattern: Option<String>,
2626
}
2727

28+
/// Result of finding package.json files.
29+
#[derive(Debug)]
30+
pub struct PackageJsonFindResult {
31+
pub files: Vec<PackageJsonLocation>,
32+
pub workspace_type: WorkspaceType,
33+
}
34+
2835
/// Find all package.json files, respecting workspace configurations.
2936
pub async fn find_package_json_files(
3037
start_path: &Path,
31-
) -> Vec<PackageJsonLocation> {
38+
) -> PackageJsonFindResult {
3239
let mut results = Vec::new();
3340
let root_package_json = start_path.join("package.json");
3441

@@ -63,7 +70,10 @@ pub async fn find_package_json_files(
6370
}
6471
}
6572

66-
results
73+
PackageJsonFindResult {
74+
files: results,
75+
workspace_type: workspace_config.ws_type,
76+
}
6777
}
6878

6979
/// Detect workspace configuration from package.json.
@@ -470,8 +480,8 @@ mod tests {
470480
#[tokio::test]
471481
async fn test_find_no_root_package_json() {
472482
let dir = tempfile::tempdir().unwrap();
473-
let results = find_package_json_files(dir.path()).await;
474-
assert!(results.is_empty());
483+
let result = find_package_json_files(dir.path()).await;
484+
assert!(result.files.is_empty());
475485
}
476486

477487
#[tokio::test]
@@ -480,9 +490,9 @@ mod tests {
480490
fs::write(dir.path().join("package.json"), r#"{"name":"root"}"#)
481491
.await
482492
.unwrap();
483-
let results = find_package_json_files(dir.path()).await;
484-
assert_eq!(results.len(), 1);
485-
assert!(results[0].is_root);
493+
let result = find_package_json_files(dir.path()).await;
494+
assert_eq!(result.files.len(), 1);
495+
assert!(result.files[0].is_root);
486496
}
487497

488498
#[tokio::test]
@@ -499,11 +509,12 @@ mod tests {
499509
fs::write(pkg_a.join("package.json"), r#"{"name":"a"}"#)
500510
.await
501511
.unwrap();
502-
let results = find_package_json_files(dir.path()).await;
512+
let result = find_package_json_files(dir.path()).await;
513+
assert!(matches!(result.workspace_type, WorkspaceType::Npm));
503514
// root + workspace member
504-
assert_eq!(results.len(), 2);
505-
assert!(results[0].is_root);
506-
assert!(results[1].is_workspace);
515+
assert_eq!(result.files.len(), 2);
516+
assert!(result.files[0].is_root);
517+
assert!(result.files[1].is_workspace);
507518
}
508519

509520
#[tokio::test]
@@ -523,10 +534,13 @@ mod tests {
523534
fs::write(pkg_a.join("package.json"), r#"{"name":"a"}"#)
524535
.await
525536
.unwrap();
526-
let results = find_package_json_files(dir.path()).await;
527-
assert_eq!(results.len(), 2);
528-
assert!(results[0].is_root);
529-
assert!(results[1].is_workspace);
537+
let result = find_package_json_files(dir.path()).await;
538+
assert!(matches!(result.workspace_type, WorkspaceType::Pnpm));
539+
// find_package_json_files still returns all files;
540+
// filtering for pnpm is done by the caller (setup command)
541+
assert_eq!(result.files.len(), 2);
542+
assert!(result.files[0].is_root);
543+
assert!(result.files[1].is_workspace);
530544
}
531545

532546
#[tokio::test]
@@ -540,10 +554,10 @@ mod tests {
540554
fs::write(nm.join("package.json"), r#"{"name":"lodash"}"#)
541555
.await
542556
.unwrap();
543-
let results = find_package_json_files(dir.path()).await;
557+
let result = find_package_json_files(dir.path()).await;
544558
// Only root, node_modules should be skipped
545-
assert_eq!(results.len(), 1);
546-
assert!(results[0].is_root);
559+
assert_eq!(result.files.len(), 1);
560+
assert!(result.files[0].is_root);
547561
}
548562

549563
#[tokio::test]
@@ -561,9 +575,9 @@ mod tests {
561575
fs::write(deep.join("package.json"), r#"{"name":"deep"}"#)
562576
.await
563577
.unwrap();
564-
let results = find_package_json_files(dir.path()).await;
578+
let result = find_package_json_files(dir.path()).await;
565579
// Only root (the deep one exceeds depth limit)
566-
assert_eq!(results.len(), 1);
580+
assert_eq!(result.files.len(), 1);
567581
}
568582

569583
#[tokio::test]
@@ -580,9 +594,9 @@ mod tests {
580594
fs::write(nested.join("package.json"), r#"{"name":"client"}"#)
581595
.await
582596
.unwrap();
583-
let results = find_package_json_files(dir.path()).await;
597+
let result = find_package_json_files(dir.path()).await;
584598
// root + recursively found workspace member
585-
assert!(results.len() >= 2);
599+
assert!(result.files.len() >= 2);
586600
}
587601

588602
#[tokio::test]
@@ -599,7 +613,7 @@ mod tests {
599613
fs::write(core.join("package.json"), r#"{"name":"core"}"#)
600614
.await
601615
.unwrap();
602-
let results = find_package_json_files(dir.path()).await;
603-
assert_eq!(results.len(), 2);
616+
let result = find_package_json_files(dir.path()).await;
617+
assert_eq!(result.files.len(), 2);
604618
}
605619
}

0 commit comments

Comments
 (0)