Skip to content

Commit 9159751

Browse files
committed
test(amplify-cli-core): add tests for packageManager field detection
Add tests for the new findPackageManagerFieldInHierarchy() function that detects the packageManager field in package.json hierarchy (corepack convention for monorepos). New test cases: - Detect pnpm from packageManager field in package.json - Detect npm from packageManager field in package.json - Detect yarn from packageManager field in package.json - Detect packageManager field from parent directory in hierarchy - packageManager field takes precedence over lock files - Falls back to lock file detection when packageManager executable not found
1 parent 4c4be52 commit 9159751

19 files changed

Lines changed: 171 additions & 11 deletions

File tree

Lines changed: 121 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
import * as path from 'path';
22
import which from 'which';
33
import { getPackageManager } from '../utils';
4+
import * as shellUtils from '../utils/shell-utils';
45

56
jest.mock('which');
7+
jest.mock('../utils/shell-utils');
68

79
describe('packageManager tests', () => {
810
const baseDirectory = path.join(__dirname, 'testFiles');
911
const which_mock = which as jest.Mocked<typeof which>;
10-
11-
beforeEach(() => jest.clearAllMocks());
12+
const shellUtils_mock = shellUtils as jest.Mocked<typeof shellUtils>;
13+
14+
beforeEach(() => {
15+
jest.resetAllMocks();
16+
// Default to returning undefined (executable not found)
17+
// Tests that need specific executables should set them explicitly
18+
(which_mock.sync as jest.Mock).mockReturnValue(undefined);
19+
// Mock yarn --version to return a valid version
20+
shellUtils_mock.execWithOutputAsString.mockResolvedValue('4.0.0');
21+
});
1222

1323
test('returns null when no package.json found', async () => {
1424
const testDirectory = path.join(baseDirectory, 'packageManager-null');
@@ -19,31 +29,36 @@ describe('packageManager tests', () => {
1929
});
2030

2131
test('detects yarn2 correctly', async () => {
22-
which_mock.sync.mockReturnValue('/path/to/yarn');
32+
(which_mock.sync as jest.Mock).mockImplementation((executable: string) => {
33+
if (executable === 'yarn') return '/path/to/yarn';
34+
return undefined;
35+
});
2336

2437
const testDirectory = path.join(baseDirectory, 'packageManager-yarn2');
2538

2639
const packageManager = await getPackageManager(testDirectory);
2740

28-
expect(which_mock.sync).toBeCalledTimes(1);
2941
expect(packageManager).toBeDefined();
3042
expect(packageManager?.packageManager).toEqual('yarn');
3143
expect(packageManager?.version?.major).toBeGreaterThanOrEqual(2);
3244
});
3345

3446
test('detects yarn correctly', async () => {
35-
which_mock.sync.mockReturnValue('/path/to/yarn');
47+
(which_mock.sync as jest.Mock).mockImplementation((executable: string) => {
48+
if (executable === 'yarn') return '/path/to/yarn';
49+
return undefined;
50+
});
3651

3752
const testDirectory = path.join(baseDirectory, 'packageManager-yarn');
3853

3954
const packageManager = await getPackageManager(testDirectory);
4055

41-
expect(which_mock.sync).toBeCalledTimes(1);
4256
expect(packageManager).toBeDefined();
4357
expect(packageManager!.packageManager).toEqual('yarn');
4458
});
4559

4660
test('detects npm correctly', async () => {
61+
// No executables in PATH - should fall back to npm via lock file
4762
const testDirectory = path.join(baseDirectory, 'packageManager-npm');
4863

4964
const packageManager = await getPackageManager(testDirectory);
@@ -53,26 +68,121 @@ describe('packageManager tests', () => {
5368
});
5469

5570
test('detects yarn fallback correctly when yarn in path', async () => {
56-
which_mock.sync.mockReturnValue('/path/to/yarn');
71+
(which_mock.sync as jest.Mock).mockImplementation((executable: string) => {
72+
if (executable === 'yarn') return '/path/to/yarn';
73+
return undefined;
74+
});
5775

5876
const testDirectory = path.join(baseDirectory, 'packageManager-fallback');
5977

6078
const packageManager = await getPackageManager(testDirectory);
6179

62-
expect(which_mock.sync).toBeCalledTimes(1);
6380
expect(packageManager).toBeDefined();
6481
expect(packageManager!.packageManager).toEqual('yarn');
6582
});
6683

6784
test('detects npm fallback correctly when yarn is not in path', async () => {
68-
(which_mock.sync as any).mockReturnValue(undefined);
69-
85+
// No executables in PATH (already set in beforeEach)
7086
const testDirectory = path.join(baseDirectory, 'packageManager-fallback');
7187

7288
const packageManager = await getPackageManager(testDirectory);
7389

74-
expect(which_mock.sync).toBeCalledTimes(2);
7590
expect(packageManager).toBeDefined();
7691
expect(packageManager!.packageManager).toEqual('npm');
7792
});
93+
94+
describe('packageManager field detection (corepack convention)', () => {
95+
test('detects pnpm from packageManager field in package.json', async () => {
96+
(which_mock.sync as jest.Mock).mockImplementation((executable: string) => {
97+
if (executable === 'pnpm') return '/path/to/pnpm';
98+
return undefined;
99+
});
100+
101+
const testDirectory = path.join(baseDirectory, 'packageManager-pnpm-field');
102+
103+
const packageManager = await getPackageManager(testDirectory);
104+
105+
expect(packageManager).toBeDefined();
106+
expect(packageManager!.packageManager).toEqual('pnpm');
107+
});
108+
109+
test('detects npm from packageManager field in package.json', async () => {
110+
(which_mock.sync as jest.Mock).mockImplementation((executable: string) => {
111+
if (executable === 'npm') return '/path/to/npm';
112+
return undefined;
113+
});
114+
115+
const testDirectory = path.join(baseDirectory, 'packageManager-npm-field');
116+
117+
const packageManager = await getPackageManager(testDirectory);
118+
119+
expect(packageManager).toBeDefined();
120+
expect(packageManager!.packageManager).toEqual('npm');
121+
});
122+
123+
test('detects yarn from packageManager field in package.json', async () => {
124+
(which_mock.sync as jest.Mock).mockImplementation((executable: string) => {
125+
if (executable === 'yarn') return '/path/to/yarn';
126+
return undefined;
127+
});
128+
129+
const testDirectory = path.join(baseDirectory, 'packageManager-yarn-field');
130+
131+
const packageManager = await getPackageManager(testDirectory);
132+
133+
expect(packageManager).toBeDefined();
134+
expect(packageManager!.packageManager).toEqual('yarn');
135+
});
136+
137+
test('detects packageManager field from parent directory in hierarchy', async () => {
138+
(which_mock.sync as jest.Mock).mockImplementation((executable: string) => {
139+
if (executable === 'pnpm') return '/path/to/pnpm';
140+
return undefined;
141+
});
142+
143+
// subdir has package.json but no packageManager field
144+
// parent directory has packageManager: pnpm@10.17.0
145+
const testDirectory = path.join(baseDirectory, 'packageManager-hierarchy', 'subdir');
146+
147+
const packageManager = await getPackageManager(testDirectory);
148+
149+
expect(packageManager).toBeDefined();
150+
expect(packageManager!.packageManager).toEqual('pnpm');
151+
});
152+
153+
test('packageManager field takes precedence over lock files', async () => {
154+
(which_mock.sync as jest.Mock).mockImplementation((executable: string) => {
155+
if (executable === 'pnpm') return '/path/to/pnpm';
156+
if (executable === 'yarn') return '/path/to/yarn';
157+
return undefined;
158+
});
159+
160+
// Directory has yarn.lock but package.json has packageManager: pnpm@10.17.0
161+
const testDirectory = path.join(baseDirectory, 'packageManager-field-with-lockfile');
162+
163+
const packageManager = await getPackageManager(testDirectory);
164+
165+
expect(packageManager).toBeDefined();
166+
expect(packageManager!.packageManager).toEqual('pnpm');
167+
});
168+
169+
test('falls back to lock file detection when packageManager executable not found', async () => {
170+
(which_mock.sync as jest.Mock).mockImplementation((executable: string) => {
171+
// pnpm not in PATH, but yarn is
172+
if (executable === 'pnpm') return undefined;
173+
if (executable === 'yarn') return '/path/to/yarn';
174+
return undefined;
175+
});
176+
177+
// Parent directory has packageManager: pnpm but pnpm not in PATH
178+
// Subdirectory has yarn.lock (no packageManager field to avoid corepack blocking yarn)
179+
// yarn is in PATH, so should fall back to yarn via lock file
180+
const testDirectory = path.join(baseDirectory, 'packageManager-hierarchy-fallback', 'subdir');
181+
182+
const packageManager = await getPackageManager(testDirectory);
183+
184+
expect(packageManager).toBeDefined();
185+
expect(packageManager!.packageManager).toEqual('yarn');
186+
});
187+
});
78188
});

packages/amplify-cli-core/src/__tests__/testFiles/packageManager-field-with-lockfile/.gitkeep

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "packageManager-field-with-lockfile-test",
3+
"version": "1.0.0",
4+
"description": "Test package with packageManager field that differs from lock file",
5+
"packageManager": "pnpm@10.17.0"
6+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2+
# yarn lockfile v1

packages/amplify-cli-core/src/__tests__/testFiles/packageManager-hierarchy-fallback/.gitkeep

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "packageManager-hierarchy-fallback-test",
3+
"version": "1.0.0",
4+
"description": "Root package with packageManager field for fallback test",
5+
"packageManager": "pnpm@10.17.0"
6+
}

packages/amplify-cli-core/src/__tests__/testFiles/packageManager-hierarchy-fallback/subdir/.gitkeep

Whitespace-only changes.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "packageManager-hierarchy-fallback-subdir-test",
3+
"version": "1.0.0",
4+
"description": "Subdirectory with yarn.lock but no packageManager field"
5+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2+
# yarn lockfile v1

packages/amplify-cli-core/src/__tests__/testFiles/packageManager-hierarchy/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)