Skip to content

Commit d5b84ae

Browse files
authored
Fix double bump issue (#1102)
1 parent d7e7482 commit d5b84ae

4 files changed

Lines changed: 65 additions & 14 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Fix double bump issue",
4+
"packageName": "beachball",
5+
"email": "elcraig@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

src/__e2e__/publishE2E.test.ts

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { _mockNpmPublish, initNpmMock } from '../__fixtures__/mockNpm';
1414
import type { PackageJson } from '../types/PackageInfo';
1515
import { getParsedOptions } from '../options/getOptions';
1616
import { getPackageInfos } from '../monorepo/getPackageInfos';
17+
import { validate } from '../validation/validate';
1718

1819
// Spawning actual npm to run commands against a fake registry is extremely slow, so mock it for
1920
// this test (packagePublish covers the more complete npm registry scenario).
@@ -60,11 +61,14 @@ describe('publish command (e2e)', () => {
6061
repositoryFactory = new RepositoryFactory('single');
6162
repo = repositoryFactory.cloneRepository();
6263

63-
const { options, packageInfos } = getOptionsAndPackages();
64+
const { options, parsedOptions, packageInfos } = getOptionsAndPackages();
6465

6566
generateChangeFiles(['foo'], options);
6667
repo.push();
6768

69+
// For this test, run validate first to simulate what the CLI does
70+
validate(options, { checkDependencies: true }, packageInfos);
71+
6872
await publish(options, packageInfos);
6973

7074
expect(await npmShow('foo')).toMatchObject({
@@ -76,6 +80,10 @@ describe('publish command (e2e)', () => {
7680
repo.checkout(defaultBranchName);
7781
repo.pull();
7882
expect(repo.getCurrentTags()).toEqual(['foo_v1.1.0']);
83+
84+
// Also verify it's correct on disk
85+
const newPackageInfos = getPackageInfos(parsedOptions);
86+
expect(newPackageInfos.foo.version).toBe('1.1.0');
7987
});
8088

8189
it('can perform a successful npm publish in detached HEAD', async () => {
@@ -139,13 +147,18 @@ describe('publish command (e2e)', () => {
139147

140148
// this indicates 2 tries
141149
expect(fetchCount).toBe(2);
150+
151+
// TODO: this uses the modified version 1.0.2, which is wrong because the bumped version is newer.
152+
// Needs further investigation...
153+
// const newPackageInfos = getPackageInfos(parsedOptions);
154+
// expect(newPackageInfos.foo.version).toBe('1.1.0');
142155
});
143156

144157
it('can perform a successful npm publish from a race condition in the dependencies', async () => {
145158
repositoryFactory = new RepositoryFactory('single');
146159
repo = repositoryFactory.cloneRepository();
147160

148-
const { options, packageInfos } = getOptionsAndPackages();
161+
const { options, parsedOptions, packageInfos } = getOptionsAndPackages();
149162

150163
generateChangeFiles(['foo'], options);
151164
repo.push();
@@ -184,16 +197,16 @@ describe('publish command (e2e)', () => {
184197
// this indicates 2 tries
185198
expect(fetchCount).toBe(2);
186199

187-
const packageJsonFile = repo.pathTo('package.json');
188-
const contents = JSON.parse(fs.readFileSync(packageJsonFile, 'utf-8')) as PackageJson;
189-
expect(contents.dependencies?.baz).toBeUndefined();
200+
const newPackageInfos = getPackageInfos(parsedOptions);
201+
expect(newPackageInfos.foo.version).toBe('1.1.0');
202+
expect(newPackageInfos.foo.dependencies?.baz).toBeUndefined();
190203
});
191204

192205
it('can perform a successful npm publish without bump', async () => {
193206
repositoryFactory = new RepositoryFactory('single');
194207
repo = repositoryFactory.cloneRepository();
195208

196-
const { options, packageInfos } = getOptionsAndPackages({ bump: false });
209+
const { options, parsedOptions, packageInfos } = getOptionsAndPackages({ bump: false });
197210

198211
generateChangeFiles(['foo'], options);
199212

@@ -210,17 +223,23 @@ describe('publish command (e2e)', () => {
210223
repo.checkout(defaultBranchName);
211224
repo.pull();
212225
expect(repo.getCurrentTags()).toEqual([]);
226+
227+
const newPackageInfos = getPackageInfos(parsedOptions);
228+
expect(newPackageInfos.foo.version).toBe('1.0.0');
213229
});
214230

215231
it('publishes only changed packages in a monorepo', async () => {
216232
repositoryFactory = new RepositoryFactory('monorepo');
217233
repo = repositoryFactory.cloneRepository();
218234

219-
const { options, packageInfos } = getOptionsAndPackages();
235+
const { options, parsedOptions, packageInfos } = getOptionsAndPackages();
220236

221237
generateChangeFiles(['foo'], options);
222238
repo.push();
223239

240+
// For this test, run validate first to simulate what the CLI does
241+
validate(options, { checkDependencies: true }, packageInfos);
242+
224243
await publish(options, packageInfos);
225244

226245
await npmShow('bar', { shouldFail: true });
@@ -234,20 +253,26 @@ describe('publish command (e2e)', () => {
234253
repo.checkout(defaultBranchName);
235254
repo.pull();
236255
expect(repo.getCurrentTags()).toEqual(['foo_v1.1.0']);
256+
257+
const newPackageInfos = getPackageInfos(parsedOptions);
258+
expect(newPackageInfos.foo.version).toBe('1.1.0');
237259
});
238260

239261
it('publishes dependent packages in a monorepo', async () => {
240262
repositoryFactory = new RepositoryFactory('monorepo');
241263
repo = repositoryFactory.cloneRepository();
242264

243-
const { options, packageInfos } = getOptionsAndPackages();
265+
const { options, parsedOptions, packageInfos } = getOptionsAndPackages();
244266

245267
// bump baz => dependent bump bar => dependent bump foo
246268
generateChangeFiles(['baz'], options);
247269
expect(repositoryFactory.fixture.folders.packages.foo.dependencies!.bar).toBeTruthy();
248270
expect(repositoryFactory.fixture.folders.packages.bar.dependencies!.baz).toBeTruthy();
249271
repo.push();
250272

273+
// For this test, run validate first to simulate what the CLI does
274+
validate(options, { checkDependencies: true }, packageInfos);
275+
251276
await publish(options, packageInfos);
252277

253278
expect(await npmShow('baz')).toMatchObject({
@@ -273,6 +298,13 @@ describe('publish command (e2e)', () => {
273298
repo.checkout(defaultBranchName);
274299
repo.pull();
275300
expect(repo.getCurrentTags()).toEqual(['bar_v1.3.5', 'baz_v1.4.0', 'foo_v1.0.1']);
301+
302+
const newPackageInfos = getPackageInfos(parsedOptions);
303+
expect(newPackageInfos.foo.version).toBe('1.0.1');
304+
expect(newPackageInfos.foo.dependencies!.bar).toBe('^1.3.5');
305+
expect(newPackageInfos.bar.version).toBe('1.3.5');
306+
expect(newPackageInfos.bar.dependencies!.baz).toBe('^1.4.0');
307+
expect(newPackageInfos.baz.version).toBe('1.4.0');
276308
});
277309

278310
it('publishes new monorepo packages if requested', async () => {
@@ -305,7 +337,7 @@ describe('publish command (e2e)', () => {
305337
repositoryFactory = new RepositoryFactory('monorepo');
306338
repo = repositoryFactory.cloneRepository();
307339

308-
const { options, packageInfos } = getOptionsAndPackages({ scope: ['!packages/foo'] });
340+
const { options, parsedOptions, packageInfos } = getOptionsAndPackages({ scope: ['!packages/foo'] });
309341

310342
generateChangeFiles(['foo'], options);
311343
generateChangeFiles(['bar'], options);
@@ -326,6 +358,10 @@ describe('publish command (e2e)', () => {
326358
repo.checkout(defaultBranchName);
327359
repo.pull();
328360
expect(repo.getCurrentTags()).toEqual(['bar_v1.4.0']);
361+
362+
const newPackageInfos = getPackageInfos(parsedOptions);
363+
expect(newPackageInfos.bar.version).toBe('1.4.0');
364+
expect(newPackageInfos.foo.version).toBe('1.0.0');
329365
});
330366

331367
it('should respect prepublish hooks', async () => {
@@ -402,7 +438,7 @@ describe('publish command (e2e)', () => {
402438
repositoryFactory = new RepositoryFactory('single');
403439
repo = repositoryFactory.cloneRepository();
404440

405-
const { options, packageInfos } = getOptionsAndPackages({
441+
const { options, parsedOptions, packageInfos } = getOptionsAndPackages({
406442
fetch: false,
407443
});
408444

@@ -427,6 +463,11 @@ describe('publish command (e2e)', () => {
427463

428464
// no fetch when flag set to false
429465
expect(fetchCount).toBe(0);
466+
467+
repo.checkout(defaultBranchName);
468+
repo.pull();
469+
const newPackageInfos = getPackageInfos(parsedOptions);
470+
expect(newPackageInfos.foo.version).toBe('1.1.0');
430471
});
431472

432473
it('should specify fetch depth when depth param is defined', async () => {
@@ -484,8 +525,8 @@ describe('publish command (e2e)', () => {
484525

485526
// All git results should still have previous information
486527
expect(repo.getCurrentTags()).toEqual(['foo_v1.1.0']);
487-
const manifestJson = fs.readFileSync(repo.pathTo('foo.txt'));
488-
expect(manifestJson.toString()).toEqual('foo');
528+
const fooText = fs.readFileSync(repo.pathTo('foo.txt'), 'utf8');
529+
expect(fooText).toEqual('foo');
489530
});
490531

491532
it('publishes multiple packages concurrently respecting the concurrency limit', async () => {

src/bump/gatherBumpInfo.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ import type { BeachballOptions } from '../types/BeachballOptions';
66
import { getScopedPackages } from '../monorepo/getScopedPackages';
77
import type { PackageInfos } from '../types/PackageInfo';
88
import { getPackageGroups } from '../monorepo/getPackageGroups';
9+
import { cloneObject } from '../object/cloneObject';
910

1011
/**
1112
* Gather bump info and bump versions in memory.
13+
* Does NOT mutate the given `originalPackageInfos`.
1214
*/
13-
export function gatherBumpInfo(options: BeachballOptions, packageInfos: PackageInfos): BumpInfo {
15+
export function gatherBumpInfo(options: BeachballOptions, originalPackageInfos: PackageInfos): BumpInfo {
16+
const packageInfos = cloneObject(originalPackageInfos);
1417
const changes = readChangeFiles(options, packageInfos);
1518

1619
// Determine base change types for each package (not considering disallowedChangeTypes or groups)

src/publish/bumpAndPush.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export async function bumpAndPush(bumpInfo: BumpInfo, publishBranch: string, opt
3030
console.log('-'.repeat(80));
3131
console.log(`Bumping versions and pushing to git (attempt ${tryNumber}/${BUMP_PUSH_RETRIES})`);
3232
console.log('Reverting');
33-
revertLocalChanges(cwd);
33+
revertLocalChanges({ cwd });
3434

3535
// pull in latest from origin branch
3636
if (options.fetch !== false) {

0 commit comments

Comments
 (0)