Skip to content

Commit b93bbde

Browse files
MaxKlessFrozenPandaz
authored andcommitted
fix(core): fall back to invoking PM in detection (#34691)
## Current Behavior when you run things like `pnpm nx@latest init`, there might not be a pnpm lockfile yet. So nx commands will detect the PM as `npm` by default... even though the fact that the user is invoking the command via `pnpm` is a strong signal that that's the PM they want to use. ## Expected Behavior We fall back to detecting the invoking PM via env var if no lockfile exists. (cherry picked from commit 4377c8b)
1 parent d3e95a5 commit b93bbde

2 files changed

Lines changed: 90 additions & 4 deletions

File tree

packages/nx/src/utils/package-manager.spec.ts

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,73 @@ describe('package-manager', () => {
151151
return jest.requireActual('fs').existsSync(p);
152152
}
153153
});
154-
const packageManager = detectPackageManager();
155-
expect(packageManager).toEqual('npm');
156-
expect(fs.existsSync).toHaveBeenCalledTimes(4);
154+
const originalUserAgent = process.env.npm_config_user_agent;
155+
delete process.env.npm_config_user_agent;
156+
try {
157+
const packageManager = detectPackageManager();
158+
expect(packageManager).toEqual('npm');
159+
expect(fs.existsSync).toHaveBeenCalledTimes(4);
160+
} finally {
161+
process.env.npm_config_user_agent = originalUserAgent;
162+
}
163+
});
164+
165+
it('should detect pnpm from npm_config_user_agent when no lock file exists', () => {
166+
jest.spyOn(configModule, 'readNxJson').mockReturnValueOnce({});
167+
jest.spyOn(fs, 'existsSync').mockReturnValue(false);
168+
const originalUserAgent = process.env.npm_config_user_agent;
169+
process.env.npm_config_user_agent =
170+
'pnpm/8.15.4 npm/? node/v20.11.1 darwin arm64';
171+
try {
172+
expect(detectPackageManager()).toEqual('pnpm');
173+
} finally {
174+
process.env.npm_config_user_agent = originalUserAgent;
175+
}
176+
});
177+
178+
it('should detect yarn from npm_config_user_agent when no lock file exists', () => {
179+
jest.spyOn(configModule, 'readNxJson').mockReturnValueOnce({});
180+
jest.spyOn(fs, 'existsSync').mockReturnValue(false);
181+
const originalUserAgent = process.env.npm_config_user_agent;
182+
process.env.npm_config_user_agent =
183+
'yarn/1.22.21 npm/? node/v20.11.1 darwin arm64';
184+
try {
185+
expect(detectPackageManager()).toEqual('yarn');
186+
} finally {
187+
process.env.npm_config_user_agent = originalUserAgent;
188+
}
189+
});
190+
191+
it('should detect bun from npm_config_user_agent when no lock file exists', () => {
192+
jest.spyOn(configModule, 'readNxJson').mockReturnValueOnce({});
193+
jest.spyOn(fs, 'existsSync').mockReturnValue(false);
194+
const originalUserAgent = process.env.npm_config_user_agent;
195+
process.env.npm_config_user_agent = 'bun/1.0.25';
196+
try {
197+
expect(detectPackageManager()).toEqual('bun');
198+
} finally {
199+
process.env.npm_config_user_agent = originalUserAgent;
200+
}
201+
});
202+
203+
it('should prefer lock file detection over npm_config_user_agent', () => {
204+
jest.spyOn(configModule, 'readNxJson').mockReturnValueOnce({});
205+
jest.spyOn(fs, 'existsSync').mockImplementation((p) => {
206+
switch (p) {
207+
case 'yarn.lock':
208+
return true;
209+
default:
210+
return false;
211+
}
212+
});
213+
const originalUserAgent = process.env.npm_config_user_agent;
214+
process.env.npm_config_user_agent =
215+
'pnpm/8.15.4 npm/? node/v20.11.1 darwin arm64';
216+
try {
217+
expect(detectPackageManager()).toEqual('yarn');
218+
} finally {
219+
process.env.npm_config_user_agent = originalUserAgent;
220+
}
157221
});
158222
});
159223

packages/nx/src/utils/package-manager.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,32 @@ export function detectPackageManager(dir: string = ''): PackageManager {
6969
? 'yarn'
7070
: existsSync(join(dir, 'pnpm-lock.yaml'))
7171
? 'pnpm'
72-
: 'npm')
72+
: detectInvokedPackageManager())
7373
);
7474
}
7575

76+
/**
77+
* Detects which package manager was used to invoke the current command
78+
* based on the npm_config_user_agent environment variable.
79+
*
80+
* Falls back to 'npm' if detection fails.
81+
*/
82+
function detectInvokedPackageManager(): PackageManager {
83+
const userAgent = process.env.npm_config_user_agent;
84+
if (userAgent) {
85+
if (userAgent.startsWith('pnpm/')) {
86+
return 'pnpm';
87+
}
88+
if (userAgent.startsWith('yarn/')) {
89+
return 'yarn';
90+
}
91+
if (userAgent.startsWith('bun/')) {
92+
return 'bun';
93+
}
94+
}
95+
return 'npm';
96+
}
97+
7698
/**
7799
* Returns true if the workspace is using npm workspaces, yarn workspaces, or pnpm workspaces.
78100
* @param packageManager The package manager to use. If not provided, it will be detected based on the lock file.

0 commit comments

Comments
 (0)