11import { describe , expect , it , afterEach , jest } from '@jest/globals' ;
22import fs from 'fs' ;
3- import fsPromises from 'fs/promises' ;
43import path from 'path' ;
54import { addGitObserver , clearGitObservers } from 'workspace-tools' ;
65import { generateChangeFiles , getChangeFiles } from '../__fixtures__/changeFiles' ;
@@ -9,14 +8,15 @@ import { initMockLogs } from '../__fixtures__/mockLogs';
98import type { Repository } from '../__fixtures__/repository' ;
109import { type PackageJsonFixture , RepositoryFactory } from '../__fixtures__/repositoryFactory' ;
1110import { publish } from '../commands/publish' ;
12- import type { RepoOptions } from '../types/BeachballOptions' ;
11+ import type { ParsedOptions , RepoOptions } from '../types/BeachballOptions' ;
1312import { _mockNpmPublish , initNpmMock } from '../__fixtures__/mockNpm' ;
1413import type { PackageJson } from '../types/PackageInfo' ;
1514import { getParsedOptions } from '../options/getOptions' ;
1615import { getPackageInfos } from '../monorepo/getPackageInfos' ;
1716import { validate } from '../validation/validate' ;
1817import { readJson } from '../object/readJson' ;
1918import { createCommandContext } from '../monorepo/createCommandContext' ;
19+ import { deepFreezeProperties } from '../__fixtures__/object' ;
2020
2121// Spawning actual npm to run commands against a fake registry is extremely slow, so mock it for
2222// this test (packagePublish covers the more complete npm registry scenario).
@@ -51,6 +51,19 @@ describe('publish command (e2e)', () => {
5151 return { options : parsedOptions . options , parsedOptions } ;
5252 }
5353
54+ /**
55+ * For more realistic testing, call `validate()` like the CLI command does, then call `publish()`.
56+ * This helps catch any new issues with double bumps or context mutation.
57+ */
58+ async function publishWrapper ( parsedOptions : ParsedOptions ) {
59+ // This does an initial bump
60+ const { context } = validate ( parsedOptions , { checkDependencies : true } ) ;
61+ // Ensure the later bump process does not modify the context
62+ deepFreezeProperties ( context . bumpInfo ) ;
63+ deepFreezeProperties ( context . originalPackageInfos ) ;
64+ await publish ( parsedOptions . options , context ) ;
65+ }
66+
5467 afterEach ( ( ) => {
5568 clearGitObservers ( ) ;
5669
@@ -75,11 +88,7 @@ describe('publish command (e2e)', () => {
7588 args [ 0 ] === 'fetch' && fetchCount ++ ;
7689 } ) ;
7790
78- // For this test, run validate first to simulate what the CLI does.
79- // This would catch double bump issues if the validate step's bump call mutated the original PackageInfos.
80- const { context } = validate ( parsedOptions , { checkDependencies : true } ) ;
81-
82- await publish ( options , context ) ;
91+ await publishWrapper ( parsedOptions ) ;
8392
8493 const publishedFoo = npmMock . getPublishedVersions ( 'foo' ) ;
8594 expect ( publishedFoo ) . toEqual ( {
@@ -112,7 +121,7 @@ describe('publish command (e2e)', () => {
112121
113122 repo . checkout ( '--detach' ) ;
114123
115- await publish ( options , createCommandContext ( parsedOptions ) ) ;
124+ await publishWrapper ( parsedOptions ) ;
116125
117126 expect ( npmMock . getPublishedVersions ( 'foo' ) ) . toEqual ( {
118127 versions : [ '1.1.0' ] ,
@@ -145,7 +154,7 @@ describe('publish command (e2e)', () => {
145154 }
146155 } ) ;
147156
148- await publish ( options , createCommandContext ( parsedOptions ) ) ;
157+ await publishWrapper ( parsedOptions ) ;
149158
150159 expect ( npmMock . getPublishedVersions ( 'foo' ) ) . toEqual ( {
151160 versions : [ '1.1.0' ] ,
@@ -193,7 +202,7 @@ describe('publish command (e2e)', () => {
193202 }
194203 } ) ;
195204
196- await publish ( options , createCommandContext ( parsedOptions ) ) ;
205+ await publishWrapper ( parsedOptions ) ;
197206
198207 expect ( npmMock . getPublishedVersions ( 'foo' ) ) . toEqual ( {
199208 versions : [ '1.1.0' ] ,
@@ -221,7 +230,7 @@ describe('publish command (e2e)', () => {
221230 generateChangeFiles ( [ 'foo' ] , options ) ;
222231 repo . push ( ) ;
223232
224- await publish ( options , createCommandContext ( parsedOptions ) ) ;
233+ await publishWrapper ( parsedOptions ) ;
225234
226235 expect ( npmMock . getPublishedVersions ( 'foo' ) ) . toEqual ( {
227236 versions : [ '1.0.0' ] ,
@@ -236,36 +245,7 @@ describe('publish command (e2e)', () => {
236245 expect ( newPackageInfos . foo . version ) . toBe ( '1.0.0' ) ;
237246 } ) ;
238247
239- it ( 'publishes only changed packages in a monorepo' , async ( ) => {
240- repositoryFactory = new RepositoryFactory ( 'monorepo' ) ;
241- repo = repositoryFactory . cloneRepository ( ) ;
242-
243- const { options, parsedOptions } = getOptions ( { fetch : false } ) ;
244-
245- generateChangeFiles ( [ 'foo' ] , options ) ;
246- repo . push ( ) ;
247-
248- // For this test, run validate first to simulate what the CLI does
249- validate ( parsedOptions , { checkDependencies : true } ) ;
250-
251- await publish ( options , createCommandContext ( parsedOptions ) ) ;
252-
253- expect ( npmMock . getPublishedVersions ( 'bar' ) ) . toBeUndefined ( ) ;
254-
255- expect ( npmMock . getPublishedVersions ( 'foo' ) ) . toEqual ( {
256- versions : [ '1.1.0' ] ,
257- 'dist-tags' : { latest : '1.1.0' } ,
258- } ) ;
259-
260- repo . checkout ( defaultBranchName ) ;
261- repo . pull ( ) ;
262- expect ( repo . getCurrentTags ( ) ) . toEqual ( [ 'foo_v1.1.0' ] ) ;
263-
264- const newPackageInfos = getPackageInfos ( parsedOptions ) ;
265- expect ( newPackageInfos . foo . version ) . toBe ( '1.1.0' ) ;
266- } ) ;
267-
268- it ( 'publishes dependent packages in a monorepo' , async ( ) => {
248+ it ( 'publishes changed and dependent packages in a monorepo' , async ( ) => {
269249 repositoryFactory = new RepositoryFactory ( 'monorepo' ) ;
270250 repo = repositoryFactory . cloneRepository ( ) ;
271251
@@ -280,7 +260,7 @@ describe('publish command (e2e)', () => {
280260 // For this test, run validate first to simulate what the CLI does
281261 validate ( parsedOptions , { checkDependencies : true } ) ;
282262
283- await publish ( options , createCommandContext ( parsedOptions ) ) ;
263+ await publishWrapper ( parsedOptions ) ;
284264
285265 expect ( npmMock . getPublishedVersions ( 'baz' ) ) . toEqual ( { versions : [ '1.4.0' ] , 'dist-tags' : { latest : '1.4.0' } } ) ;
286266
@@ -314,7 +294,7 @@ describe('publish command (e2e)', () => {
314294 generateChangeFiles ( [ 'foo' ] , options ) ;
315295 repo . push ( ) ;
316296
317- await publish ( options , createCommandContext ( parsedOptions ) ) ;
297+ await publishWrapper ( parsedOptions ) ;
318298
319299 expect ( npmMock . getPublishedVersions ( 'foo' ) ) . toEqual ( { versions : [ '1.1.0' ] , 'dist-tags' : { latest : '1.1.0' } } ) ;
320300 expect ( npmMock . getPublishedVersions ( 'bar' ) ) . toEqual ( { versions : [ '1.3.4' ] , 'dist-tags' : { latest : '1.3.4' } } ) ;
@@ -337,7 +317,7 @@ describe('publish command (e2e)', () => {
337317 expect ( getChangeFiles ( options ) ) . toHaveLength ( 2 ) ;
338318 repo . push ( ) ;
339319
340- await publish ( options , createCommandContext ( parsedOptions ) ) ;
320+ await publishWrapper ( parsedOptions ) ;
341321
342322 expect ( npmMock . getPublishedVersions ( 'foo' ) ) . toBeUndefined ( ) ;
343323 expect ( npmMock . getPublishedVersions ( 'bar' ) ) . toEqual ( {
@@ -375,9 +355,13 @@ describe('publish command (e2e)', () => {
375355 const { options, parsedOptions } = getOptions ( {
376356 fetch : false ,
377357 hooks : {
378- prepublish : ( packagePath : string ) => {
358+ prepublish : ( packagePath , name , version ) => {
379359 const packageJsonPath = path . join ( packagePath , 'package.json' ) ;
380360 const packageJson = readJson < ExtraPackageJson > ( packageJsonPath ) ;
361+ if ( name === 'foo' ) {
362+ expect ( version ) . toBe ( '1.1.0' ) ;
363+ expect ( packageJson . version ) . toBe ( '1.1.0' ) ; // bumped version
364+ }
381365 if ( packageJson . customOnPublish ) {
382366 Object . assign ( packageJson , packageJson . customOnPublish ) ;
383367 delete packageJson . customOnPublish ;
@@ -397,7 +381,7 @@ describe('publish command (e2e)', () => {
397381 generateChangeFiles ( [ 'foo' ] , options ) ;
398382 repo . push ( ) ;
399383
400- await publish ( options , createCommandContext ( parsedOptions ) ) ;
384+ await publishWrapper ( parsedOptions ) ;
401385
402386 // Query the information from package.json from the registry to see if it was successfully patched
403387 const publishedFooJson = npmMock . getPublishedPackage ( 'foo' ) ! ;
@@ -416,69 +400,6 @@ describe('publish command (e2e)', () => {
416400 expect ( notified ) . toBe ( fooJsonPost . customAfterPublish ?. notify ) ;
417401 } ) ;
418402
419- it ( 'specifies fetch depth when depth param is defined' , async ( ) => {
420- repositoryFactory = new RepositoryFactory ( 'single' ) ;
421- repo = repositoryFactory . cloneRepository ( ) ;
422-
423- const { options, parsedOptions } = getOptions ( {
424- depth : 10 ,
425- } ) ;
426-
427- generateChangeFiles ( [ 'foo' ] , options ) ;
428- repo . push ( ) ;
429-
430- let fetchCommand = '' ;
431-
432- addGitObserver ( args => {
433- if ( args [ 0 ] === 'fetch' ) {
434- fetchCommand = args . join ( ' ' ) ;
435- }
436- } ) ;
437-
438- await publish ( options , createCommandContext ( parsedOptions ) ) ;
439-
440- expect ( npmMock . getPublishedVersions ( 'foo' ) ) . toEqual ( {
441- versions : [ '1.1.0' ] ,
442- 'dist-tags' : { latest : '1.1.0' } ,
443- } ) ;
444-
445- expect ( fetchCommand ) . toMatch ( '--depth=10' ) ;
446- } ) ;
447-
448- it ( 'calls precommit hook before committing changes' , async ( ) => {
449- repositoryFactory = new RepositoryFactory ( 'monorepo' ) ;
450- repo = repositoryFactory . cloneRepository ( ) ;
451-
452- const { options, parsedOptions } = getOptions ( {
453- publish : false , // irrelevant to this test
454- fetch : false ,
455- hooks : {
456- precommit : jest . fn ( async ( cwd : string ) => {
457- expect ( readJson < PackageJson > ( path . join ( cwd , 'packages/foo/package.json' ) ) . version ) . toBe ( '1.1.0' ) ;
458- const filePath = path . join ( cwd , 'foo.txt' ) ;
459- await fsPromises . writeFile ( filePath , 'foo' ) ;
460- } ) ,
461- } ,
462- } ) ;
463-
464- generateChangeFiles ( [ 'foo' , 'bar' ] , options ) ;
465- repo . push ( ) ;
466-
467- await publish ( options , createCommandContext ( parsedOptions ) ) ;
468-
469- // precommit was called (once for whole repo, not per package)
470- expect ( options . hooks ?. precommit ) . toHaveBeenCalledTimes ( 1 ) ;
471- // but changes from publish process were reverted locally
472- const txtPath = repo . pathTo ( 'foo.txt' ) ;
473- expect ( fs . existsSync ( txtPath ) ) . toBe ( false ) ;
474-
475- repo . checkout ( defaultBranchName ) ;
476- repo . pull ( ) ;
477-
478- // changes from publish process were committed
479- expect ( fs . existsSync ( txtPath ) ) . toBe ( true ) ;
480- } ) ;
481-
482403 it ( 'respects concurrency limit when publishing multiple packages' , async ( ) => {
483404 const packagesToPublish = [ 'pkg1' , 'pkg2' , 'pkg3' , 'pkg4' , 'pkg5' ] ;
484405 const packages : { [ packageName : string ] : PackageJsonFixture } = { } ;
@@ -511,6 +432,7 @@ describe('publish command (e2e)', () => {
511432 return result ;
512433 } ) ;
513434
435+ // skip validate for this test since it's not relevant
514436 await publish ( options , createCommandContext ( parsedOptions ) ) ;
515437 // Verify that at most `concurrency` number of packages were published concurrently
516438 expect ( maxConcurrency ) . toBe ( concurrency ) ;
@@ -556,6 +478,7 @@ describe('publish command (e2e)', () => {
556478 return _mockNpmPublish ( registryData , args , opts ) ;
557479 } ) ;
558480
481+ // skip validate for this test since it's not relevant
559482 await expect ( publish ( options , createCommandContext ( parsedOptions ) ) ) . rejects . toThrow (
560483 'Error publishing! Refer to the previous logs for recovery instructions.'
561484 ) ;
@@ -620,6 +543,7 @@ describe('publish command (e2e)', () => {
620543
621544 generateChangeFiles ( packagesToPublish , options ) ;
622545
546+ // skip validate for this test since it's not relevant
623547 await publish ( options , createCommandContext ( parsedOptions ) ) ;
624548 // Verify that at most `concurrency` number of postpublish hooks were running concurrently
625549 expect ( maxConcurrency ) . toBe ( concurrency ) ;
0 commit comments