1+ /// <reference types="node" />
2+ // The above directive tells TypeScript to include Node.js type definitions (i.e. process)
13import { expect } from 'chai' ;
24import {
35 detectActivePackageManager ,
@@ -11,44 +13,166 @@ describe('utils', () => {
1113
1214 describe ( 'detectActivePackageManager' , ( ) => {
1315 let originalNpmExecpath : string | undefined ;
16+ let originalCwd : string ;
17+ let tempDir : string ;
1418
1519 beforeEach ( ( ) => {
1620 originalNpmExecpath = process . env . npm_execpath ;
21+ originalCwd = process . cwd ( ) ;
22+
23+ // Create temporary directory for each test
24+ const fs = require ( 'fs' ) ;
25+ const os = require ( 'os' ) ;
26+ const path = require ( 'path' ) ;
27+ tempDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'pm-test-' ) ) ;
28+ process . chdir ( tempDir ) ;
1729 } ) ;
1830
1931 afterEach ( ( ) => {
32+ // Restore environment
2033 if ( originalNpmExecpath ) {
2134 process . env . npm_execpath = originalNpmExecpath ;
2235 } else {
2336 delete process . env . npm_execpath ;
2437 }
38+
39+ // Restore working directory and cleanup
40+ process . chdir ( originalCwd ) ;
41+ try {
42+ const fs = require ( 'fs' ) ;
43+ fs . rmSync ( tempDir , { recursive : true , force : true } ) ;
44+ } catch ( error ) {
45+ // Ignore cleanup errors
46+ }
2547 } ) ;
2648
27- [
28- [ 'yarn' , '/path/to/yarn.js' ] ,
29- [ 'pnpm' , '/path/to/pnpm.cjs' ] ,
30- [ 'npm' , '/path/to/npx-cli.js' ] ,
31- [ 'npm' , '/path/to/npm-cli.js' ] ,
32- ] . forEach ( ( [ packageManager , npmExecpath ] ) => {
33- it ( `should detect ${ packageManager } when npm_execpath contains ${ npmExecpath } ` , ( ) => {
34- process . env . npm_execpath = npmExecpath ;
35- expect ( detectActivePackageManager ( ) ) . to . equal ( packageManager ) ;
49+ describe ( 'lock file detection' , ( ) => {
50+ it ( 'should detect pnpm from pnpm-lock.yaml' , ( ) => {
51+ const fs = require ( 'fs' ) ;
52+ fs . writeFileSync ( 'pnpm-lock.yaml' , 'lockfileVersion: 5.4' ) ;
53+ expect ( detectActivePackageManager ( ) ) . to . equal ( 'pnpm' ) ;
54+ } ) ;
55+
56+ it ( 'should detect yarn from yarn.lock' , ( ) => {
57+ const fs = require ( 'fs' ) ;
58+ fs . writeFileSync ( 'yarn.lock' , '# yarn lockfile v1' ) ;
59+ expect ( detectActivePackageManager ( ) ) . to . equal ( 'yarn' ) ;
60+ } ) ;
61+
62+ it ( 'should detect npm from package-lock.json' , ( ) => {
63+ const fs = require ( 'fs' ) ;
64+ fs . writeFileSync ( 'package-lock.json' , '{"lockfileVersion": 2}' ) ;
65+ expect ( detectActivePackageManager ( ) ) . to . equal ( 'npm' ) ;
66+ } ) ;
67+
68+ it ( 'should prioritize pnpm-lock.yaml over other lock files' , ( ) => {
69+ const fs = require ( 'fs' ) ;
70+ fs . writeFileSync ( 'pnpm-lock.yaml' , 'lockfileVersion: 5.4' ) ;
71+ fs . writeFileSync ( 'yarn.lock' , '# yarn lockfile v1' ) ;
72+ fs . writeFileSync ( 'package-lock.json' , '{"lockfileVersion": 2}' ) ;
73+ expect ( detectActivePackageManager ( ) ) . to . equal ( 'pnpm' ) ;
74+ } ) ;
75+
76+ it ( 'should prioritize yarn.lock over package-lock.json' , ( ) => {
77+ const fs = require ( 'fs' ) ;
78+ fs . writeFileSync ( 'yarn.lock' , '# yarn lockfile v1' ) ;
79+ fs . writeFileSync ( 'package-lock.json' , '{"lockfileVersion": 2}' ) ;
80+ expect ( detectActivePackageManager ( ) ) . to . equal ( 'yarn' ) ;
3681 } ) ;
3782 } ) ;
3883
39- it ( 'should default to npm when npm_execpath is undefined' , ( ) => {
40- delete process . env . npm_execpath ;
41- expect ( detectActivePackageManager ( ) ) . to . equal ( 'npm' ) ;
84+ describe ( 'package.json packageManager field detection' , ( ) => {
85+ it ( 'should detect pnpm from package.json packageManager field' , ( ) => {
86+ const fs = require ( 'fs' ) ;
87+ const packageJson = { packageManager : 'pnpm@8.6.0' } ;
88+ fs . writeFileSync ( 'package.json' , JSON . stringify ( packageJson ) ) ;
89+ expect ( detectActivePackageManager ( ) ) . to . equal ( 'pnpm' ) ;
90+ } ) ;
91+
92+ it ( 'should detect yarn from package.json packageManager field' , ( ) => {
93+ const fs = require ( 'fs' ) ;
94+ const packageJson = { packageManager : 'yarn@1.22.19' } ;
95+ fs . writeFileSync ( 'package.json' , JSON . stringify ( packageJson ) ) ;
96+ expect ( detectActivePackageManager ( ) ) . to . equal ( 'yarn' ) ;
97+ } ) ;
98+
99+ it ( 'should detect npm from package.json packageManager field' , ( ) => {
100+ const fs = require ( 'fs' ) ;
101+ const packageJson = { packageManager : 'npm@9.8.0' } ;
102+ fs . writeFileSync ( 'package.json' , JSON . stringify ( packageJson ) ) ;
103+ expect ( detectActivePackageManager ( ) ) . to . equal ( 'npm' ) ;
104+ } ) ;
105+
106+ it ( 'should prioritize lock files over package.json packageManager field' , ( ) => {
107+ const fs = require ( 'fs' ) ;
108+ fs . writeFileSync ( 'yarn.lock' , '# yarn lockfile v1' ) ;
109+ const packageJson = { packageManager : 'pnpm@8.6.0' } ;
110+ fs . writeFileSync ( 'package.json' , JSON . stringify ( packageJson ) ) ;
111+ expect ( detectActivePackageManager ( ) ) . to . equal ( 'yarn' ) ;
112+ } ) ;
42113 } ) ;
43114
44- it ( 'should default to npm when npm_execpath is empty string' , ( ) => {
45- process . env . npm_execpath = '' ;
46- expect ( detectActivePackageManager ( ) ) . to . equal ( 'npm' ) ;
115+ describe ( 'npm_execpath fallback detection' , ( ) => {
116+ [
117+ [ 'yarn' , '/path/to/yarn.js' ] ,
118+ [ 'pnpm' , '/path/to/pnpm.cjs' ] ,
119+ [ 'npm' , '/path/to/npx-cli.js' ] ,
120+ [ 'npm' , '/path/to/npm-cli.js' ] ,
121+ ] . forEach ( ( [ packageManager , npmExecpath ] ) => {
122+ it ( `should detect ${ packageManager } when npm_execpath contains ${ npmExecpath } ` , ( ) => {
123+ process . env . npm_execpath = npmExecpath ;
124+ expect ( detectActivePackageManager ( ) ) . to . equal ( packageManager ) ;
125+ } ) ;
126+ } ) ;
127+
128+ it ( 'should default to npm when npm_execpath is undefined' , ( ) => {
129+ delete process . env . npm_execpath ;
130+ expect ( detectActivePackageManager ( ) ) . to . equal ( 'npm' ) ;
131+ } ) ;
132+
133+ it ( 'should default to npm when npm_execpath is empty string' , ( ) => {
134+ process . env . npm_execpath = '' ;
135+ expect ( detectActivePackageManager ( ) ) . to . equal ( 'npm' ) ;
136+ } ) ;
137+
138+ it ( 'should default to npm for unknown execpath' , ( ) => {
139+ process . env . npm_execpath = '/path/to/unknown-package-manager.js' ;
140+ expect ( detectActivePackageManager ( ) ) . to . equal ( 'npm' ) ;
141+ } ) ;
142+
143+ it ( 'should fall back to npm_execpath when package.json is invalid' , ( ) => {
144+ const fs = require ( 'fs' ) ;
145+ fs . writeFileSync ( 'package.json' , 'invalid json' ) ;
146+ process . env . npm_execpath = '/path/to/yarn.js' ;
147+ expect ( detectActivePackageManager ( ) ) . to . equal ( 'yarn' ) ;
148+ } ) ;
149+
150+ it ( 'should fall back to npm_execpath when package.json has no packageManager field' , ( ) => {
151+ const fs = require ( 'fs' ) ;
152+ const packageJson = { name : 'test-app' , version : '1.0.0' } ;
153+ fs . writeFileSync ( 'package.json' , JSON . stringify ( packageJson ) ) ;
154+ process . env . npm_execpath = '/path/to/pnpm.cjs' ;
155+ expect ( detectActivePackageManager ( ) ) . to . equal ( 'pnpm' ) ;
156+ } ) ;
47157 } ) ;
48158
49- it ( 'should default to npm for unknown execpath' , ( ) => {
50- process . env . npm_execpath = '/path/to/unknown-package-manager.js' ;
51- expect ( detectActivePackageManager ( ) ) . to . equal ( 'npm' ) ;
159+ describe ( 'priority and edge cases' , ( ) => {
160+ it ( 'should prioritize lock files over package.json over npm_execpath' , ( ) => {
161+ const fs = require ( 'fs' ) ;
162+ fs . writeFileSync ( 'yarn.lock' , '# yarn lockfile v1' ) ;
163+ const packageJson = { packageManager : 'pnpm@8.6.0' } ;
164+ fs . writeFileSync ( 'package.json' , JSON . stringify ( packageJson ) ) ;
165+ process . env . npm_execpath = '/path/to/npm-cli.js' ;
166+ expect ( detectActivePackageManager ( ) ) . to . equal ( 'yarn' ) ;
167+ } ) ;
168+
169+ it ( 'should prioritize package.json over npm_execpath when no lock files exist' , ( ) => {
170+ const fs = require ( 'fs' ) ;
171+ const packageJson = { packageManager : 'pnpm@8.6.0' } ;
172+ fs . writeFileSync ( 'package.json' , JSON . stringify ( packageJson ) ) ;
173+ process . env . npm_execpath = '/path/to/yarn.js' ;
174+ expect ( detectActivePackageManager ( ) ) . to . equal ( 'pnpm' ) ;
175+ } ) ;
52176 } ) ;
53177 } ) ;
54178
@@ -96,17 +220,37 @@ describe('utils', () => {
96220
97221 describe ( 'normalizeOptions' , ( ) => {
98222 let originalNpmExecpath : string | undefined ;
223+ let originalCwd : string ;
224+ let tempDir : string ;
99225
100226 beforeEach ( ( ) => {
101227 originalNpmExecpath = process . env . npm_execpath ;
228+ originalCwd = process . cwd ( ) ;
229+
230+ // Create temporary directory for each test
231+ const fs = require ( 'fs' ) ;
232+ const os = require ( 'os' ) ;
233+ const path = require ( 'path' ) ;
234+ tempDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'normalize-test-' ) ) ;
235+ process . chdir ( tempDir ) ;
102236 } ) ;
103237
104238 afterEach ( ( ) => {
239+ // Restore environment
105240 if ( originalNpmExecpath ) {
106241 process . env . npm_execpath = originalNpmExecpath ;
107242 } else {
108243 delete process . env . npm_execpath ;
109244 }
245+
246+ // Restore working directory and cleanup
247+ process . chdir ( originalCwd ) ;
248+ try {
249+ const fs = require ( 'fs' ) ;
250+ fs . rmSync ( tempDir , { recursive : true , force : true } ) ;
251+ } catch ( error ) {
252+ // Ignore cleanup errors
253+ }
110254 } ) ;
111255
112256 describe ( 'no package manager options are provided' , ( ) => {
@@ -116,6 +260,7 @@ describe('utils', () => {
116260 [ 'npm' , '/path/to/npx-cli.js' ] ,
117261 ] . forEach ( ( [ activePackageManager , npmExecpath ] ) => {
118262 it ( `falls back to ${ activePackageManager } when that is the active package manager` , ( ) => {
263+ // Set npm_execpath to force detection to use fallback
119264 process . env . npm_execpath = npmExecpath ;
120265 const options : CLIOptions = { } ;
121266 const result = normalizeOptions ( options ) ;
@@ -128,6 +273,31 @@ describe('utils', () => {
128273 } ) ;
129274 } ) ;
130275 } ) ;
276+
277+ it ( 'falls back to detected package manager from lock files' , ( ) => {
278+ const fs = require ( 'fs' ) ;
279+ fs . writeFileSync ( 'yarn.lock' , '# yarn lockfile v1' ) ;
280+
281+ const options : CLIOptions = { } ;
282+ const result = normalizeOptions ( options ) ;
283+
284+ expect ( result . yarn ) . to . be . true ;
285+ expect ( result . npm ) . to . be . undefined ;
286+ expect ( result . pnpm ) . to . be . undefined ;
287+ } ) ;
288+
289+ it ( 'falls back to detected package manager from package.json' , ( ) => {
290+ const fs = require ( 'fs' ) ;
291+ const packageJson = { packageManager : 'pnpm@8.6.0' } ;
292+ fs . writeFileSync ( 'package.json' , JSON . stringify ( packageJson ) ) ;
293+
294+ const options : CLIOptions = { } ;
295+ const result = normalizeOptions ( options ) ;
296+
297+ expect ( result . pnpm ) . to . be . true ;
298+ expect ( result . npm ) . to . be . undefined ;
299+ expect ( result . yarn ) . to . be . undefined ;
300+ } ) ;
131301 } ) ;
132302
133303 describe ( 'one package manager option is provided' , ( ) => {
0 commit comments