Skip to content

Commit cb407cb

Browse files
committed
Temporarily revert npm-registry-fetch usage
1 parent 4082c09 commit cb407cb

19 files changed

Lines changed: 431 additions & 655 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ $ beachball publish -r http://localhost:4873 -t beta
9393

9494
### Overriding concurrency
9595

96-
In large monorepos, the process of fetching versions for sync or before publishing can be time-consuming due to the high number of packages. To optimize performance, you can override the concurrency for fetching from the registry by setting `options.npmReadConcurrency` (default: 10). You can also increase concurrency for hook calls and publish operations via `options.concurrency` (default: 1; respects topological order).
96+
In large monorepos, the process of fetching versions for sync or before publishing can be time-consuming due to the high number of packages. To optimize performance, you can override the concurrency for fetching from the registry by setting `options.npmReadConcurrency` (default: 5). You can also increase concurrency for hook calls and publish operations via `options.concurrency` (default: 1; respects topological order).
9797

9898
### API surface
9999

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "minor",
3+
"comment": "Revert to using npm CLI for fetching package info due to vulnerability in older `npm-registry-fetch`'s old `tar` dependency (updating would require a major change to bump beachball's minimum Node version)",
4+
"packageName": "beachball",
5+
"email": "elcraig@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

docs/overview/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ For the latest full list of supported options, see `RepoOptions` [in this file](
9191
| `groupChanges` | `boolean` | `false` | repo | Write multiple changes to a single change file |
9292
| `hooks` | [`HooksOptions`][4] | | repo | Hooks for custom pre/post publish actions |
9393
| `ignorePatterns` | `string[]` | | repo | Ignore changes in files matching these glob patterns ([see notes][6]) |
94-
| `npmReadConcurrency` | number | 10 | repo | Maximum concurrency for fetching package versions from the registry (see `concurrency` for write operations) |
94+
| `npmReadConcurrency` | number | 5 | repo | Maximum concurrency for fetching package versions from the registry (see `concurrency` for write operations) |
9595
| `package` | `string` | | repo | Specifies which package the command relates to (overrides change detection based on `git diff`) |
9696
| `prereleasePrefix` | `string` | | repo | Prerelease prefix, e.g. `"beta"`. Note that if this is specified, packages with change type major/minor/patch will be bumped as prerelease instead. |
9797
| `publish` | `boolean` | `true` | repo | Whether to publish to npm registry |

package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@
5454
"cosmiconfig": "^9.0.0",
5555
"execa": "^5.0.0",
5656
"minimatch": "^3.0.4",
57-
"npm-registry-fetch": "^14.0.0",
5857
"p-graph": "^1.1.2",
5958
"p-limit": "^3.0.2",
6059
"prompts": "^2.4.2",
@@ -67,7 +66,6 @@
6766
"@jest/globals": "^29.0.0",
6867
"@types/minimatch": "^5.0.0",
6968
"@types/node": "^14.0.0",
70-
"@types/npm-registry-fetch": "^8.0.9",
7169
"@types/prompts": "^2.4.2",
7270
"@types/semver": "^7.3.13",
7371
"@types/tmp": "^0.2.3",

src/__e2e__/publishE2E.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { deepFreezeProperties } from '../__fixtures__/object';
2424
// If an issue is found in the future that could only be caught by this test using real npm,
2525
// a new test file with a real registry should be created to cover that specific scenario.
2626
jest.mock('../packageManager/npm');
27-
jest.mock('npm-registry-fetch');
27+
// jest.mock('npm-registry-fetch');
2828

2929
describe('publish command (e2e)', () => {
3030
const npmMock = initNpmMock();

src/__e2e__/publishRegistry.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { deepFreezeProperties } from '../__fixtures__/object';
2020
// If an issue is found in the future that could only be caught by this test using real npm,
2121
// a new test file with a real registry should be created to cover that specific scenario.
2222
jest.mock('../packageManager/npm');
23-
jest.mock('npm-registry-fetch');
23+
// jest.mock('npm-registry-fetch');
2424

2525
describe('publish command (registry)', () => {
2626
const npmMock = initNpmMock();
@@ -188,7 +188,8 @@ describe('publish command (registry)', () => {
188188

189189
expect(npmMock.getPublishedPackage('foo')!.version).toEqual('1.1.0');
190190
expect(npmMock.getPublishedPackage('bar')!.version).toEqual('1.4.0');
191-
expect(npmMock.mock).toHaveBeenCalledTimes(2);
191+
expect(npmMock.mock).toHaveBeenCalledTimes(4);
192+
// expect(npmMock.mock).toHaveBeenCalledTimes(2);
192193
expect(logs.mocks.error).not.toHaveBeenCalled();
193194
});
194195

src/__e2e__/syncE2E.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { getScopedPackages } from '../monorepo/getScopedPackages';
1818
// If an issue is found in the future that could only be caught by this test using real npm,
1919
// a new test file with a real registry should be created to cover that specific scenario.
2020
jest.mock('../packageManager/npm');
21-
jest.mock('npm-registry-fetch');
21+
// jest.mock('npm-registry-fetch');
2222

2323
describe('sync command (e2e)', () => {
2424
const mockNpm = initNpmMock();

src/__fixtures__/mockNpm.test.ts

Lines changed: 145 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,21 @@
44

55
import { afterAll, afterEach, beforeAll, describe, expect, it, jest } from '@jest/globals';
66
import fs from 'fs';
7-
import fetch from 'npm-registry-fetch';
7+
// import fetch from 'npm-registry-fetch';
88
import { type NpmResult, npm } from '../packageManager/npm';
99
import type { PackageJson } from '../types/PackageInfo';
10-
import { initNpmMock, _makeRegistryData, _mockNpmPack, _mockNpmPublish, type MockNpmResult } from './mockNpm';
10+
import {
11+
initNpmMock,
12+
_makeRegistryData,
13+
_mockNpmPack,
14+
_mockNpmPublish,
15+
_mockNpmShow,
16+
type MockNpmResult,
17+
} from './mockNpm';
1118
import * as readJsonModule from '../object/readJson';
1219

1320
jest.mock('fs');
14-
jest.mock('npm-registry-fetch');
21+
// jest.mock('npm-registry-fetch');
1522
jest.mock('../object/readJson');
1623
jest.mock('../packageManager/npm');
1724

@@ -86,6 +93,94 @@ describe('_makeRegistryData', () => {
8693
});
8794
});
8895

96+
describe('_mockNpmShow', () => {
97+
function getErrorResult(errorMessage: string) {
98+
return {
99+
stdout: '',
100+
stderr: errorMessage,
101+
all: errorMessage,
102+
success: false,
103+
failed: true,
104+
} as NpmResult;
105+
}
106+
107+
function getShowResult(params: { name: string; version: string }) {
108+
const { name, version } = params;
109+
const output = JSON.stringify({
110+
...data[name].versions[version],
111+
'dist-tags': data[name]['dist-tags'],
112+
versions: Object.keys(data[name].versions),
113+
});
114+
115+
return {
116+
stdout: output,
117+
stderr: '',
118+
all: output,
119+
success: true,
120+
failed: false,
121+
} as NpmResult;
122+
}
123+
124+
const data = _makeRegistryData({
125+
foo: {
126+
versions: ['1.0.0-beta', '1.0.0', '1.0.1'],
127+
'dist-tags': { latest: '1.0.1', beta: '1.0.0-beta' },
128+
},
129+
'@foo/bar': {
130+
versions: ['2.0.0-beta', '2.0.0', '2.0.1'],
131+
'dist-tags': { latest: '2.0.1', beta: '2.0.0-beta' },
132+
},
133+
});
134+
135+
it("errors if package doesn't exist", async () => {
136+
const emptyData = _makeRegistryData({});
137+
const result = await _mockNpmShow(emptyData, ['foo'], { cwd: undefined });
138+
expect(result).toEqual(getErrorResult('[fake] code E404 - foo - not found'));
139+
});
140+
141+
it('returns requested version plus dist-tags and version list', async () => {
142+
const result = await _mockNpmShow(data, ['foo@1.0.0'], { cwd: undefined });
143+
expect(result).toEqual(getShowResult({ name: 'foo', version: '1.0.0' }));
144+
});
145+
146+
it('returns requested version of scoped package', async () => {
147+
const result = await _mockNpmShow(data, ['@foo/bar@2.0.0'], { cwd: undefined });
148+
expect(result).toEqual(getShowResult({ name: '@foo/bar', version: '2.0.0' }));
149+
});
150+
151+
it('returns requested tag', async () => {
152+
const result = await _mockNpmShow(data, ['foo@beta'], { cwd: undefined });
153+
expect(result).toEqual(getShowResult({ name: 'foo', version: '1.0.0-beta' }));
154+
});
155+
156+
it('returns requested tag of scoped package', async () => {
157+
const result = await _mockNpmShow(data, ['@foo/bar@beta'], { cwd: undefined });
158+
expect(result).toEqual(getShowResult({ name: '@foo/bar', version: '2.0.0-beta' }));
159+
});
160+
161+
it('returns latest version if no version requested', async () => {
162+
const result = await _mockNpmShow(data, ['foo'], { cwd: undefined });
163+
expect(result).toEqual(getShowResult({ name: 'foo', version: '1.0.1' }));
164+
});
165+
166+
it('returns latest version of scoped package if no version requested', async () => {
167+
const result = await _mockNpmShow(data, ['@foo/bar'], { cwd: undefined });
168+
expect(result).toEqual(getShowResult({ name: '@foo/bar', version: '2.0.1' }));
169+
});
170+
171+
it("errors if requested version doesn't exist", async () => {
172+
const result = await _mockNpmShow(data, ['foo@2.0.0'], { cwd: undefined });
173+
expect(result).toEqual(getErrorResult('[fake] code E404 - foo@2.0.0 - not found'));
174+
});
175+
176+
// support for this could be added later
177+
it('currently throws if requested version is a range', async () => {
178+
await expect(() => _mockNpmShow(data, ['foo@^1.0.0'], { cwd: undefined })).rejects.toThrow(
179+
/not currently supported/
180+
);
181+
});
182+
});
183+
89184
describe('_mockNpmPublish', () => {
90185
function getPublishResult(params: { error?: string; tag?: string }) {
91186
const { error, tag } = params;
@@ -279,25 +374,25 @@ describe('mockNpm', () => {
279374
jest.restoreAllMocks();
280375
});
281376

282-
describe('mockFetchJson', () => {
283-
it('mocks registry fetch', async () => {
284-
npmMock.setRegistryData({ foo: { versions: ['1.0.0'] } });
285-
expect(fetch.json).toHaveProperty('mock');
286-
const result = await fetch.json('/foo');
287-
expect(result).toEqual({
288-
name: 'foo',
289-
modified: expect.any(String),
290-
versions: { '1.0.0': { name: 'foo', version: '1.0.0' } },
291-
'dist-tags': { latest: '1.0.0' },
292-
});
293-
});
294-
295-
it('resets calls and registry after each test', () => {
296-
expect(npmMock.mockFetchJson).not.toHaveBeenCalled();
297-
// registry data for foo was set in the previous test but should have been cleared
298-
expect(() => fetch.json('/foo')).toThrow('404 Not Found');
299-
});
300-
});
377+
// describe('mockFetchJson', () => {
378+
// it('mocks registry fetch', async () => {
379+
// npmMock.setRegistryData({ foo: { versions: ['1.0.0'] } });
380+
// expect(fetch.json).toHaveProperty('mock');
381+
// const result = await fetch.json('/foo');
382+
// expect(result).toEqual({
383+
// name: 'foo',
384+
// modified: expect.any(String),
385+
// versions: { '1.0.0': { name: 'foo', version: '1.0.0' } },
386+
// 'dist-tags': { latest: '1.0.0' },
387+
// });
388+
// });
389+
390+
// it('resets calls and registry after each test', () => {
391+
// expect(npmMock.mockFetchJson).not.toHaveBeenCalled();
392+
// // registry data for foo was set in the previous test but should have been cleared
393+
// expect(() => fetch.json('/foo')).toThrow('404 Not Found');
394+
// });
395+
// });
301396

302397
describe('getPublishedVersions', () => {
303398
it('gets data for a package', () => {
@@ -350,14 +445,23 @@ describe('mockNpm', () => {
350445
versions: ['1.0.0'],
351446
'dist-tags': { latest: '1.0.0' },
352447
});
353-
expect(await fetch.json('/foo')).toEqual({
354-
name: 'foo',
355-
modified: expect.any(String),
356-
versions: {
357-
'1.0.0': { name: 'foo', version: '1.0.0' },
358-
},
359-
'dist-tags': { latest: '1.0.0' },
448+
expect(await npm(['show', 'foo'], { cwd: undefined })).toMatchObject({
449+
success: true,
450+
stdout: JSON.stringify({
451+
name: 'foo',
452+
version: '1.0.0',
453+
'dist-tags': { latest: '1.0.0' },
454+
versions: ['1.0.0'],
455+
}),
360456
});
457+
// expect(await fetch.json('/foo')).toEqual({
458+
// name: 'foo',
459+
// modified: expect.any(String),
460+
// versions: {
461+
// '1.0.0': { name: 'foo', version: '1.0.0' },
462+
// },
463+
// 'dist-tags': { latest: '1.0.0' },
464+
// });
361465
});
362466
});
363467

@@ -373,6 +477,11 @@ describe('mockNpm', () => {
373477
expect(npmMock.mock).toHaveBeenCalledWith(['publish'], expect.objectContaining({ cwd: 'fake' }));
374478
});
375479

480+
it('resets calls after each test', () => {
481+
expect(npmMock.mock).not.toHaveBeenCalled();
482+
expect(npmMock.getPublishedVersions('foo')).toBeUndefined();
483+
});
484+
376485
it('mocks npm pack command', async () => {
377486
packageJson = { name: 'foo', version: '2.0.0' };
378487
const result = await npm(['pack'], { cwd: 'fake' });
@@ -398,8 +507,13 @@ describe('mockNpm', () => {
398507
expect(npmMock.mock).toHaveBeenCalledWith(['foo'], expect.objectContaining({ cwd: undefined }));
399508
});
400509

401-
it('resets calls after each test', () => {
402-
expect(npmMock.mock).not.toHaveBeenCalled();
510+
it('TEMP mocks npm show command', async () => {
511+
npmMock.setRegistryData({ foo: { versions: ['1.0.0'] } });
512+
const result = await npm(['show', 'foo'], { cwd: undefined });
513+
expect(result).toMatchObject({
514+
success: true,
515+
stdout: expect.stringContaining('"name":"foo"'),
516+
});
403517
});
404518
});
405519

0 commit comments

Comments
 (0)