Skip to content

Commit 49f2055

Browse files
authored
Centralize error handling (#1172)
1 parent d114028 commit 49f2055

25 files changed

+81
-140
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": "Centralize error handling instead of calling process.exit() throughout the code",
4+
"packageName": "beachball",
5+
"email": "elcraig@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

scripts/jestSetup.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
// @ts-check
22
const { jest, afterAll } = require('@jest/globals');
33

4+
// Safety net: production code should never call process.exit() directly
5+
// (errors should be thrown and caught at the top level in cli.ts).
6+
// This mock ensures any accidental process.exit() call in tests immediately
7+
// throws rather than silently exiting the test runner.
48
jest.spyOn(process, 'exit').mockImplementation(code => {
59
throw new Error(`process.exit called with code ${code}`);
610
});

src/__e2e__/validate.test.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ import { initMockLogs } from '../__fixtures__/mockLogs';
55
import { validate, type ValidateOptions } from '../validation/validate';
66
import type { Repository } from '../__fixtures__/repository';
77
import { getParsedOptions } from '../options/getOptions';
8-
import { mockProcessExit } from '../__fixtures__/mockProcessExit';
8+
import { BeachballError } from '../types/BeachballError';
99

1010
describe('validate', () => {
1111
let repositoryFactory: RepositoryFactory;
1212
let repo: Repository | undefined;
1313
const logs = initMockLogs();
14-
const processExit = mockProcessExit(logs);
1514

1615
function validateWrapper(validateOptions?: ValidateOptions) {
1716
const parsedOptions = getParsedOptions({
@@ -30,7 +29,6 @@ describe('validate', () => {
3029
});
3130

3231
afterEach(() => {
33-
processExit.mockClear();
3432
repo = undefined;
3533
});
3634

@@ -54,8 +52,7 @@ describe('validate', () => {
5452
repo.checkout('-b', 'test');
5553
repo.stageChange('packages/foo/test.js');
5654

57-
expect(() => validateWrapper({ checkChangeNeeded: true })).toThrowError(/process\.exit/);
58-
expect(processExit).toHaveBeenCalledWith(1);
55+
expect(() => validateWrapper({ checkChangeNeeded: true })).toThrow(BeachballError);
5956
expect(logs.mocks.error).toHaveBeenCalledWith('ERROR: Change files are needed!');
6057
});
6158

src/__fixtures__/mockNpm.test.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// But this added complexity greatly speeds up the other npm-related tests by removing the
33
// dependency on actual npm CLI calls and a fake registry (which are very slow).
44

5-
import { afterAll, afterEach, beforeAll, describe, expect, it, jest } from '@jest/globals';
5+
import { afterEach, beforeAll, describe, expect, it, jest } from '@jest/globals';
66
import fs from 'fs';
77
// import fetch from 'npm-registry-fetch';
88
import { type NpmResult, npm } from '../packageManager/npm';
@@ -208,10 +208,6 @@ describe('_mockNpmPublish', () => {
208208
packageJson = undefined;
209209
});
210210

211-
afterAll(() => {
212-
jest.restoreAllMocks();
213-
});
214-
215211
it('throws if cwd is not specified', async () => {
216212
await expect(() => _mockNpmPublish({}, [], { cwd: undefined })).rejects.toThrow(
217213
'cwd is required for mock npm publish'
@@ -311,10 +307,6 @@ describe('_mockNpmPack', () => {
311307
writtenFiles = [];
312308
});
313309

314-
afterAll(() => {
315-
jest.restoreAllMocks();
316-
});
317-
318310
it('throws if cwd is not specified', async () => {
319311
await expect(() => _mockNpmPack({}, [], { cwd: undefined })).rejects.toThrow('cwd is required for mock npm pack');
320312
});
@@ -370,10 +362,6 @@ describe('mockNpm', () => {
370362
packageJson = undefined;
371363
});
372364

373-
afterAll(() => {
374-
jest.restoreAllMocks();
375-
});
376-
377365
// describe('mockFetchJson', () => {
378366
// it('mocks registry fetch', async () => {
379367
// npmMock.setRegistryData({ foo: { versions: ['1.0.0'] } });

src/__fixtures__/mockProcessExit.ts

Lines changed: 0 additions & 11 deletions
This file was deleted.

src/__functional__/options/getCliOptions.test.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { afterAll, afterEach, describe, expect, it, jest } from '@jest/globals';
1+
import { afterEach, describe, expect, it, jest } from '@jest/globals';
22
import { getCliOptions } from '../../options/getCliOptions';
33
import { findProjectRoot, getDefaultRemoteBranch } from 'workspace-tools';
44

@@ -34,10 +34,6 @@ describe('getCliOptions', () => {
3434
jest.clearAllMocks();
3535
});
3636

37-
afterAll(() => {
38-
jest.restoreAllMocks();
39-
});
40-
4137
// start by making sure nothing went wrong with the mock
4238
it('uses fake project root', () => {
4339
expect(findProjectRoot(process.cwd())).toEqual(projectRoot);

src/__functional__/options/getOptions.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ describe('getOptions (deprecated)', () => {
3030

3131
afterAll(() => {
3232
repositoryFactory.cleanUp();
33-
jest.restoreAllMocks();
3433
});
3534

3635
it('uses the branch name defined in beachball.config.js', () => {
@@ -115,7 +114,6 @@ describe('getParsedOptions', () => {
115114

116115
afterAll(() => {
117116
repositoryFactory.cleanUp();
118-
jest.restoreAllMocks();
119117
});
120118

121119
it('uses the branch name defined in beachball.config.js', () => {

src/__functional__/packageManager/getNpmPackageInfo.test.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, expect, it, afterAll, beforeEach, jest } from '@jest/globals';
1+
import { describe, expect, it, beforeEach, jest } from '@jest/globals';
22
// import fetch from 'npm-registry-fetch';
33
import {
44
_npmShowProperties,
@@ -24,10 +24,6 @@ describe('getNpmPackageInfo', () => {
2424
// fetchJsonSpy.mockClear();
2525
});
2626

27-
afterAll(() => {
28-
jest.restoreAllMocks();
29-
});
30-
3127
it.each<{ desc: string; name: string; knownVersion: string }>([
3228
{ desc: 'unscoped', name: 'beachball', knownVersion: '2.60.1' },
3329
{ desc: 'scoped', name: '@lage-run/cli', knownVersion: '0.33.0' },

src/__functional__/publish/publishToRegistry.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,7 @@ describe('publishToRegistry', () => {
158158
// Pre-populate registry so version 1.0.0 already exists
159159
npmMock.setRegistryData({ foo: { versions: ['1.0.0'] } });
160160

161-
// process.exit is mocked in jestSetup.js to throw
162-
await expect(publishToRegistry(bumpInfo, defaultOptions)).rejects.toThrow('process.exit called with code 1');
161+
await expect(publishToRegistry(bumpInfo, defaultOptions)).rejects.toThrow('Pre-publish validation failed');
163162

164163
expect(logs.getMockLines('all')).toMatchInlineSnapshot(`
165164
"[log] Validating new package versions...

src/__tests__/bump/bumpInMemory.test.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { afterAll, beforeAll, describe, expect, it, jest } from '@jest/globals';
1+
import { describe, expect, it } from '@jest/globals';
22
import path from 'path';
33
import { generateChanges, type PartialChangeFile } from '../../__fixtures__/changeFiles';
44
import { initMockLogs } from '../../__fixtures__/mockLogs';
5-
import { mockProcessExit } from '../../__fixtures__/mockProcessExit';
65
import { makePackageInfosByFolder, type PartialPackageInfo } from '../../__fixtures__/packageInfos';
76
import { bumpInMemory } from '../../bump/bumpInMemory';
87
import { getParsedOptions } from '../../options/getOptions';
@@ -12,7 +11,7 @@ import { getScopedPackages } from '../../monorepo/getScopedPackages';
1211
import { getPackageGroups } from '../../monorepo/getPackageGroups';
1312

1413
describe('bumpInMemory', () => {
15-
const logs = initMockLogs();
14+
initMockLogs();
1615
const cwd = path.resolve('/fake-root');
1716

1817
function gatherBumpInfoWrapper(params: {
@@ -42,15 +41,6 @@ describe('bumpInMemory', () => {
4241
return { bumpInfo, options, originalPackageInfos };
4342
}
4443

45-
beforeAll(() => {
46-
// getPackageGroups currently can call process.exit
47-
mockProcessExit(logs);
48-
});
49-
50-
afterAll(() => {
51-
jest.restoreAllMocks();
52-
});
53-
5444
it('bumps only packages with change files with bumpDeps: false', () => {
5545
const { bumpInfo, originalPackageInfos } = gatherBumpInfoWrapper({
5646
packageFolders: {

0 commit comments

Comments
 (0)