@@ -2,17 +2,28 @@ import { spawn } from "child_process";
22import { existsSync } from "fs" ;
33import { join } from "path" ;
44
5+ import { PackageJson } from "./PackageJson" ;
6+
57export enum PackageManagerType {
68 yarn = "yarn" ,
79 npm = "npm" ,
10+ pnpm = "pnpm" ,
811}
912
1013export class PackageManagerService {
1114 static detectPackageManager ( dirPath : string ) : PackageManagerType {
15+ if ( existsSync ( join ( dirPath , "pnpm-lock.yaml" ) ) ) {
16+ return PackageManagerType . pnpm ;
17+ }
1218 if ( existsSync ( join ( dirPath , "yarn.lock" ) ) ) {
1319 return PackageManagerType . yarn ;
1420 }
15- return PackageManagerType . npm ;
21+
22+ if ( existsSync ( join ( dirPath , "package-lock.json" ) ) ) {
23+ return PackageManagerType . npm ;
24+ }
25+
26+ throw new Error ( `Could not detect package manager in directory: ${ dirPath } . No lock file found.` ) ;
1627 }
1728
1829 static async addDevPackage ( packageName : string , dirPath : string ) : Promise < void > {
@@ -37,6 +48,13 @@ export class PackageManagerService {
3748 args . push ( "--no-workspaces" ) ;
3849 }
3950 break ;
51+ case PackageManagerType . pnpm :
52+ args . push ( "add" , "--save-dev" ) ;
53+
54+ if ( isMonorepo ) {
55+ args . push ( "--workspace-root" ) ;
56+ }
57+ break ;
4058 }
4159
4260 args . push ( packageName ) ;
@@ -47,37 +65,69 @@ export class PackageManagerService {
4765 static async isMonorepo ( dirPath : string ) {
4866 const packageManager = PackageManagerService . detectPackageManager ( dirPath ) ;
4967
50- const args : string [ ] = [ packageManager ] ;
51-
5268 switch ( packageManager ) {
53- case PackageManagerType . yarn :
54- args . push ( "workspaces" , "info" ) ;
55- break ;
56-
57- case PackageManagerType . npm :
58- args . push ( "--workspaces" , "list" ) ;
59- break ;
60- }
61-
62- args . push ( "> /dev/null 2>&1 && echo true || echo false;" ) ;
69+ case PackageManagerType . yarn : {
70+ const args = [ packageManager , "workspaces" , "info" ] ;
71+ args . push ( "> /dev/null 2>&1 && echo true || echo false;" ) ;
72+ const output = await PackageManagerService . execCommand ( args , dirPath , true ) ;
73+ return output . trim ( ) === "true" ;
74+ }
6375
64- const output = await PackageManagerService . execCommand ( args , dirPath , true ) ;
76+ case PackageManagerType . npm : {
77+ const args = [ packageManager , "--workspaces" , "list" ] ;
78+ args . push ( "> /dev/null 2>&1 && echo true || echo false;" ) ;
79+ const output = await PackageManagerService . execCommand ( args , dirPath , true ) ;
80+ return output . trim ( ) === "true" ;
81+ }
6582
66- return output . trim ( ) === "true" ;
83+ case PackageManagerType . pnpm : {
84+ // For pnpm, check if pnpm-workspace.yaml exists or if workspaces are defined in package.json
85+ const pnpmWorkspaceFile = join ( dirPath , "pnpm-workspace.yaml" ) ;
86+ if ( existsSync ( pnpmWorkspaceFile ) ) {
87+ return true ;
88+ }
89+ // Check package.json for workspace field (though pnpm prefers pnpm-workspace.yaml)
90+ try {
91+ const packageJson = PackageJson . fromDirPath ( dirPath ) ;
92+ const content = packageJson . getContent ( ) ;
93+ return ! ! ( content . workspaces ) ;
94+ } catch {
95+ return false ;
96+ }
97+ }
98+ }
6799 }
68100
69101 static async isPackageInstalled ( packageName : string , dirPath : string ) : Promise < boolean > {
70102 const packageManager = PackageManagerService . detectPackageManager ( dirPath ) ;
71103
72- const args = [
73- packageManager ,
74- "list" ,
75- "--depth=1" ,
76- "--json" ,
77- "--no-progress" ,
78- `--pattern="${ packageName } "` ,
79- "--non-interactive" ,
80- ] ;
104+ let args : string [ ] ;
105+
106+ switch ( packageManager ) {
107+ case PackageManagerType . yarn :
108+ case PackageManagerType . npm :
109+ args = [
110+ packageManager ,
111+ "list" ,
112+ "--depth=1" ,
113+ "--json" ,
114+ "--no-progress" ,
115+ `--pattern="${ packageName } "` ,
116+ "--non-interactive" ,
117+ ] ;
118+ break ;
119+ case PackageManagerType . pnpm :
120+ args = [
121+ packageManager ,
122+ "list" ,
123+ packageName ,
124+ "--json" ,
125+ "--depth=1" ,
126+ ] ;
127+ break ;
128+ default :
129+ throw new Error ( `Unsupported package manager: ${ packageManager } ` ) ;
130+ }
81131
82132 const output = await PackageManagerService . execCommand ( args , dirPath , true ) ;
83133
@@ -92,6 +142,16 @@ export class PackageManagerService {
92142 return installedPackages . dependencies
93143 ? Object . prototype . hasOwnProperty . call ( installedPackages . dependencies , packageName )
94144 : false ;
145+ case PackageManagerType . pnpm :
146+ // pnpm returns an array of package objects
147+ if ( Array . isArray ( installedPackages ) && installedPackages . length > 0 ) {
148+ const pkg = installedPackages [ 0 ] ;
149+ return ( pkg . dependencies && Object . prototype . hasOwnProperty . call ( pkg . dependencies , packageName ) ) ||
150+ ( pkg . devDependencies && Object . prototype . hasOwnProperty . call ( pkg . devDependencies , packageName ) ) ;
151+ }
152+ return false ;
153+ default :
154+ throw new Error ( `Unsupported package manager: ${ packageManager } ` ) ;
95155 }
96156 }
97157
@@ -112,6 +172,17 @@ export class PackageManagerService {
112172 )
113173 ) . trim ( ) ;
114174 break ;
175+ case PackageManagerType . pnpm :
176+ nodeModulesPath = (
177+ await PackageManagerService . execCommand (
178+ [ packageManager , "root" ] ,
179+ dirPath ,
180+ true
181+ )
182+ ) . trim ( ) ;
183+ break ;
184+ default :
185+ throw new Error ( `Unsupported package manager: ${ packageManager } ` ) ;
115186 }
116187
117188 if ( nodeModulesPath ) {
0 commit comments