Skip to content

Commit 349712e

Browse files
authored
Use shell: true and explicit cwd for all package manager commands (#963)
1 parent 7d50f92 commit 349712e

File tree

16 files changed

+100
-110
lines changed

16 files changed

+100
-110
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Use shell: true and explicit cwd for all package manager commands",
4+
"packageName": "beachball",
5+
"email": "elcraig@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

src/__e2e__/syncE2E.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe('sync command (e2e)', () => {
2222
const mockNpm = initNpmMock();
2323

2424
let repositoryFactory: RepositoryFactory | undefined;
25-
const publishOptions: Parameters<typeof packagePublish>[1] = { registry: 'fake', retries: 3 };
25+
const publishOptions: Parameters<typeof packagePublish>[1] = { registry: 'fake', retries: 3, path: undefined };
2626
const tempDirs: string[] = [];
2727

2828
initMockLogs();

src/__fixtures__/mockNpm.test.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -122,48 +122,48 @@ describe('_mockNpmShow', () => {
122122

123123
it("errors if package doesn't exist", () => {
124124
const emptyData = _makeRegistryData({});
125-
const result = _mockNpmShow(emptyData, ['foo'], {});
125+
const result = _mockNpmShow(emptyData, ['foo'], { cwd: undefined });
126126
expect(result).toEqual(getShowResult({ error: '[fake] code E404 - foo - not found' }));
127127
});
128128

129129
it('returns requested version plus dist-tags and version list', () => {
130-
const result = _mockNpmShow(data, ['foo@1.0.0'], {});
130+
const result = _mockNpmShow(data, ['foo@1.0.0'], { cwd: undefined });
131131
expect(result).toEqual(getShowResult({ data: data, name: 'foo', version: '1.0.0' }));
132132
});
133133

134134
it('returns requested version of scoped package', () => {
135-
const result = _mockNpmShow(data, ['@foo/bar@2.0.0'], {});
135+
const result = _mockNpmShow(data, ['@foo/bar@2.0.0'], { cwd: undefined });
136136
expect(result).toEqual(getShowResult({ data, name: '@foo/bar', version: '2.0.0' }));
137137
});
138138

139139
it('returns requested tag', () => {
140-
const result = _mockNpmShow(data, ['foo@beta'], {});
140+
const result = _mockNpmShow(data, ['foo@beta'], { cwd: undefined });
141141
expect(result).toEqual(getShowResult({ data, name: 'foo', version: '1.0.0-beta' }));
142142
});
143143

144144
it('returns requested tag of scoped package', () => {
145-
const result = _mockNpmShow(data, ['@foo/bar@beta'], {});
145+
const result = _mockNpmShow(data, ['@foo/bar@beta'], { cwd: undefined });
146146
expect(result).toEqual(getShowResult({ data, name: '@foo/bar', version: '2.0.0-beta' }));
147147
});
148148

149149
it('returns latest version if no version requested', () => {
150-
const result = _mockNpmShow(data, ['foo'], {});
150+
const result = _mockNpmShow(data, ['foo'], { cwd: undefined });
151151
expect(result).toEqual(getShowResult({ data, name: 'foo', version: '1.0.1' }));
152152
});
153153

154154
it('returns latest version of scoped package if no version requested', () => {
155-
const result = _mockNpmShow(data, ['@foo/bar'], {});
155+
const result = _mockNpmShow(data, ['@foo/bar'], { cwd: undefined });
156156
expect(result).toEqual(getShowResult({ data, name: '@foo/bar', version: '2.0.1' }));
157157
});
158158

159159
it("errors if requested version doesn't exist", () => {
160-
const result = _mockNpmShow(data, ['foo@2.0.0'], {});
160+
const result = _mockNpmShow(data, ['foo@2.0.0'], { cwd: undefined });
161161
expect(result).toEqual(getShowResult({ error: '[fake] code E404 - foo@2.0.0 - not found' }));
162162
});
163163

164164
// support for this could be added later
165165
it('currently throws if requested version is a range', () => {
166-
expect(() => _mockNpmShow(data, ['foo@^1.0.0'], {})).toThrow(/not currently supported/);
166+
expect(() => _mockNpmShow(data, ['foo@^1.0.0'], { cwd: undefined })).toThrow(/not currently supported/);
167167
});
168168
});
169169

@@ -199,7 +199,7 @@ describe('_mockNpmPublish', () => {
199199
});
200200

201201
it('throws if cwd is not specified', () => {
202-
expect(() => _mockNpmPublish({}, [], {})).toThrow('cwd is required for mock npm publish');
202+
expect(() => _mockNpmPublish({}, [], { cwd: undefined })).toThrow('cwd is required for mock npm publish');
203203
});
204204

205205
it('errors if reading package.json fails', () => {
@@ -294,7 +294,7 @@ describe('mockNpm', () => {
294294

295295
it('mocks npm show', async () => {
296296
npmMock.setRegistryData({ foo: { versions: ['1.0.0'] } });
297-
const result = await npm(['show', 'foo']);
297+
const result = await npm(['show', 'foo'], { cwd: undefined });
298298
expect(result).toMatchObject({
299299
success: true,
300300
stdout: expect.stringContaining('"name":"foo"'),
@@ -304,7 +304,7 @@ describe('mockNpm', () => {
304304
it('resets calls and registry after each test', async () => {
305305
expect(npmMock.mock).not.toHaveBeenCalled();
306306
// registry data for foo was set in the previous test but should have been cleared
307-
const result = await npm(['show', 'foo']);
307+
const result = await npm(['show', 'foo'], { cwd: undefined });
308308
expect(result).toMatchObject({
309309
success: false,
310310
stderr: expect.stringContaining('not found'),
@@ -313,7 +313,7 @@ describe('mockNpm', () => {
313313

314314
it('can "publish" a package to registry with helper', async () => {
315315
npmMock.publishPackage({ name: 'foo', version: '1.0.0' });
316-
const result = await npm(['show', 'foo']);
316+
const result = await npm(['show', 'foo'], { cwd: undefined });
317317
expect(result).toMatchObject({
318318
success: true,
319319
stdout: expect.stringContaining('"name":"foo"'),
@@ -330,22 +330,22 @@ describe('mockNpm', () => {
330330
});
331331

332332
it('throws on unsupported command', async () => {
333-
await expect(() => npm(['pack'])).rejects.toThrow('Command not supported by mock npm: pack');
333+
await expect(() => npm(['pack'], { cwd: undefined })).rejects.toThrow('Command not supported by mock npm: pack');
334334
});
335335

336336
it('respects mocked command', async () => {
337337
const mockShow = jest.fn(() => 'hi');
338338
npmMock.setCommandOverride('show', mockShow as any);
339-
const result = await npm(['show', 'foo']);
339+
const result = await npm(['show', 'foo'], { cwd: undefined });
340340
expect(result).toEqual('hi');
341-
expect(mockShow).toHaveBeenCalledWith(expect.any(Object), ['foo'], undefined);
341+
expect(mockShow).toHaveBeenCalledWith(expect.any(Object), ['foo'], { cwd: undefined });
342342
});
343343

344344
it("respects extra mocked command that's not normally supported", async () => {
345345
const mockPack = jest.fn(() => 'hi');
346346
npmMock.setCommandOverride('pack', mockPack as any);
347-
const result = await npm(['pack']);
347+
const result = await npm(['pack'], { cwd: undefined });
348348
expect(result).toEqual('hi');
349-
expect(mockPack).toHaveBeenCalledWith(expect.any(Object), [], undefined);
349+
expect(mockPack).toHaveBeenCalledWith(expect.any(Object), [], { cwd: undefined });
350350
});
351351
});

src/__fixtures__/npmShow.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export async function npmShow(
1919
const timeout = env.isCI && os.platform() === 'win32' ? 4500 : 1500;
2020
const registryArg = registry ? ['--registry', registry.getUrl()] : [];
2121

22-
const showResult = await npm([...registryArg, 'show', '--json', packageName], { timeout });
22+
const showResult = await npm([...registryArg, 'show', '--json', packageName], { timeout, cwd: undefined });
2323

2424
expect(!!showResult.timedOut).toBe(false);
2525
expect(showResult.failed).toBe(!!shouldFail);

src/__functional__/packageManager/packagePublish.test.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,11 @@ describe('packagePublish', () => {
8282
// Do a basic publishing test against the real registry
8383
await registry.reset();
8484
const testPackageInfo = getTestPackageInfo();
85-
const publishResult = await packagePublish(testPackageInfo, { registry: registry.getUrl(), retries: 2 });
85+
const publishResult = await packagePublish(testPackageInfo, {
86+
registry: registry.getUrl(),
87+
retries: 2,
88+
path: tempRoot,
89+
});
8690
expect(publishResult).toEqual(successResult);
8791
expect(npmSpy).toHaveBeenCalledTimes(1);
8892

@@ -103,12 +107,16 @@ describe('packagePublish', () => {
103107
// Use real npm for this because the republish detection relies on the real error message
104108
await registry.reset();
105109
const testPackageInfo = getTestPackageInfo();
106-
let publishResult = await packagePublish(testPackageInfo, { registry: registry.getUrl(), retries: 2 });
110+
let publishResult = await packagePublish(testPackageInfo, {
111+
registry: registry.getUrl(),
112+
retries: 2,
113+
path: tempRoot,
114+
});
107115
expect(publishResult).toEqual(successResult);
108116
expect(npmSpy).toHaveBeenCalledTimes(1);
109117
logs.clear();
110118

111-
publishResult = await packagePublish(testPackageInfo, { registry: registry.getUrl(), retries: 2 });
119+
publishResult = await packagePublish(testPackageInfo, { registry: registry.getUrl(), retries: 2, path: tempRoot });
112120
expect(publishResult).toEqual(failedResult);
113121
// `retries` should be ignored if the version already exists
114122
expect(npmSpy).toHaveBeenCalledTimes(2);
@@ -130,7 +138,7 @@ describe('packagePublish', () => {
130138
Promise.resolve({ success: false, all: 'sloooow', timedOut: true } as NpmResult)
131139
);
132140

133-
const publishResult = await packagePublish(testPackageInfo, { registry: 'fake', retries: 3 });
141+
const publishResult = await packagePublish(testPackageInfo, { registry: 'fake', retries: 3, path: tempRoot });
134142
expect(publishResult).toEqual(successResult);
135143
expect(npmSpy).toHaveBeenCalledTimes(3);
136144

@@ -146,7 +154,7 @@ describe('packagePublish', () => {
146154
// Again, mock all npm calls for this test instead of simulating an actual error condition.
147155
npmSpy.mockImplementation(() => Promise.resolve({ success: false, all: 'some errors' } as NpmResult));
148156

149-
const publishResult = await packagePublish(getTestPackageInfo(), { registry: 'fake', retries: 3 });
157+
const publishResult = await packagePublish(getTestPackageInfo(), { registry: 'fake', retries: 3, path: tempRoot });
150158
expect(publishResult).toEqual(failedResult);
151159
expect(npmSpy).toHaveBeenCalledTimes(4);
152160

@@ -162,7 +170,7 @@ describe('packagePublish', () => {
162170
const testPackageInfo = getTestPackageInfo();
163171
npmSpy.mockImplementation(() => Promise.resolve({ success: false, all: 'ERR! code ENEEDAUTH' } as NpmResult));
164172

165-
const publishResult = await packagePublish(testPackageInfo, { registry: 'fake', retries: 3 });
173+
const publishResult = await packagePublish(testPackageInfo, { registry: 'fake', retries: 3, path: tempRoot });
166174
expect(publishResult).toEqual(failedResult);
167175
expect(npmSpy).toHaveBeenCalledTimes(1);
168176

@@ -177,7 +185,7 @@ describe('packagePublish', () => {
177185
const testPackageInfo = getTestPackageInfo();
178186
npmSpy.mockImplementation(() => Promise.resolve({ success: false, all: 'ERR! code E404' } as NpmResult));
179187

180-
const publishResult = await packagePublish(testPackageInfo, { registry: 'fake', retries: 3 });
188+
const publishResult = await packagePublish(testPackageInfo, { registry: 'fake', retries: 3, path: tempRoot });
181189
expect(publishResult).toEqual(failedResult);
182190
expect(npmSpy).toHaveBeenCalledTimes(1);
183191

@@ -188,7 +196,7 @@ describe('packagePublish', () => {
188196
const testPackageInfo = getTestPackageInfo();
189197
npmSpy.mockImplementation(() => Promise.resolve({ success: false, all: 'ERR! code E403' } as NpmResult));
190198

191-
const publishResult = await packagePublish(testPackageInfo, { registry: 'fake', retries: 3 });
199+
const publishResult = await packagePublish(testPackageInfo, { registry: 'fake', retries: 3, path: tempRoot });
192200
expect(publishResult).toEqual(failedResult);
193201
expect(npmSpy).toHaveBeenCalledTimes(1);
194202

src/__tests__/packageManager/listPackageVersions.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jest.mock('../../packageManager/npm');
1414
describe('list npm versions', () => {
1515
/** Mock the `npm show` command for `npmAsync` calls. This also handles cleanup after each test. */
1616
const npmMock = initNpmMock();
17-
const npmOptions: NpmOptions = { registry: 'https://fake' };
17+
const npmOptions: NpmOptions = { registry: 'https://fake', path: undefined };
1818

1919
afterEach(() => {
2020
_clearPackageVersionsCache();

src/__tests__/packageManager/npmArgs.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ describe('getNpmAuthArgs', () => {
2525
});
2626

2727
describe('getNpmPublishArgs', () => {
28-
const options: NpmOptions = { registry: 'https://testRegistry' };
28+
const options: Omit<NpmOptions, 'path'> = { registry: 'https://testRegistry' };
2929

3030
const packageInfos = makePackageInfos({
3131
basic: {},

src/bump/performBump.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import { BeachballOptions } from '../types/BeachballOptions';
77
import { PackageInfos, PackageJson } from '../types/PackageInfo';
88
import { findProjectRoot } from 'workspace-tools';
99
import { npm } from '../packageManager/npm';
10-
import { pnpm } from '../packageManager/pnpm';
11-
import { yarn } from '../packageManager/yarn';
10+
import { packageManager } from '../packageManager/packageManager';
1211
import { callHook } from './callHook';
1312

1413
export function writePackageJson(modifiedPackages: Set<string>, packageInfos: PackageInfos): void {
@@ -50,19 +49,22 @@ export async function updatePackageLock(cwd: string): Promise<void> {
5049
const root = findProjectRoot(cwd);
5150
if (root && fs.existsSync(path.join(root, 'package-lock.json'))) {
5251
console.log('Updating package-lock.json after bumping packages');
53-
const res = await npm(['install', '--package-lock-only', '--ignore-scripts'], { stdio: 'inherit' });
52+
const res = await npm(['install', '--package-lock-only', '--ignore-scripts'], { stdio: 'inherit', cwd: root });
5453
if (!res.success) {
5554
console.warn('Updating package-lock.json failed. Continuing...');
5655
}
5756
} else if (root && fs.existsSync(path.join(root, 'pnpm-lock.yaml'))) {
5857
console.log('Updating pnpm-lock.yaml after bumping packages');
59-
const res = await pnpm(['install', '--lockfile-only', '--ignore-scripts'], { stdio: 'inherit' });
58+
const res = await packageManager('pnpm', ['install', '--lockfile-only', '--ignore-scripts'], {
59+
stdio: 'inherit',
60+
cwd: root,
61+
});
6062
if (!res.success) {
6163
console.warn('Updating pnpm-lock.yaml failed. Continuing...');
6264
}
6365
} else if (root && fs.existsSync(path.join(root, 'yarn.lock'))) {
6466
console.log('Updating yarn.lock after bumping packages');
65-
const version = await yarn(['--version']);
67+
const version = await packageManager('yarn', ['--version'], { cwd: root });
6668
if (!version.success) {
6769
console.warn('Failed to get yarn version. Continuing...');
6870
return;
@@ -74,7 +76,7 @@ export async function updatePackageLock(cwd: string): Promise<void> {
7476
}
7577

7678
// for yarn v2+
77-
const res = await yarn(['install', '--mode', 'update-lockfile'], { stdio: 'inherit' });
79+
const res = await packageManager('yarn', ['install', '--mode', 'update-lockfile'], { stdio: 'inherit', cwd: root });
7880
if (!res.success) {
7981
console.warn('Updating yarn.lock failed. Continuing...');
8082
}

src/commands/init.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export async function init(options: BeachballOptions): Promise<void> {
2828
errorExit(`Cannot find package.json at ${packageJsonFilePath}`);
2929
}
3030

31-
const npmResult = await npm(['info', 'beachball', '--json']);
31+
const npmResult = await npm(['info', 'beachball', '--json'], { cwd: undefined });
3232
if (!npmResult.success) {
3333
errorExit('Failed to retrieve beachball version from npm');
3434
}

src/packageManager/listPackageVersions.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ async function getNpmPackageInfo(packageName: string, options: NpmOptions): Prom
2828
const { registry, token, authType, timeout } = options;
2929

3030
if (env.beachballDisableCache || !packageVersionsCache[packageName]) {
31-
const args = ['show', '--registry', registry, '--json', packageName, ...getNpmAuthArgs(registry, token, authType)];
32-
33-
const showResult = await npm(args, { timeout });
31+
const showResult = await npm(
32+
['show', '--registry', registry, '--json', packageName, ...getNpmAuthArgs(registry, token, authType)],
33+
{ timeout, cwd: options.path }
34+
);
3435

3536
if (showResult.success && showResult.stdout !== '') {
3637
const packageInfo = JSON.parse(showResult.stdout);

0 commit comments

Comments
 (0)