@@ -347,6 +347,126 @@ describe('version-string-replace generator', () => {
347347 ` ) ;
348348 } ) ;
349349 } ) ;
350+
351+ describe ( '--scope tools with --versionSuffix' , ( ) => {
352+ const suffix = 'experimental.my-feature.20260408-abc1234' ;
353+
354+ beforeEach ( ( ) => {
355+ // Tools packages
356+ tree = setupDummyPackage ( tree , {
357+ name : 'react-storybook-addon' ,
358+ version : '0.6.0' ,
359+ dependencies : { } ,
360+ projectConfiguration : { tags : [ 'tools' , 'platform:node' ] , sourceRoot : 'packages/react-storybook-addon/src' } ,
361+ } ) ;
362+ tree = setupDummyPackage ( tree , {
363+ name : 'eslint-plugin-react-components' ,
364+ version : '0.2.1' ,
365+ dependencies : {
366+ '@proj/react-storybook-addon' : '^0.6.0' ,
367+ } ,
368+ projectConfiguration : {
369+ tags : [ 'tools' , 'platform:node' ] ,
370+ sourceRoot : 'packages/eslint-plugin-react-components/src' ,
371+ } ,
372+ } ) ;
373+ // vNext package (should NOT be bumped)
374+ tree = setupDummyPackage ( tree , {
375+ name : 'react-button' ,
376+ version : '9.0.0' ,
377+ dependencies : { } ,
378+ projectConfiguration : { tags : [ 'vNext' , 'platform:web' ] , sourceRoot : 'packages/react-button/src' } ,
379+ } ) ;
380+ // Private tools package (should NOT be bumped)
381+ tree = setupDummyPackage ( tree , {
382+ name : 'private-tool' ,
383+ version : '1.0.0' ,
384+ dependencies : { } ,
385+ projectConfiguration : { tags : [ 'tools' , 'platform:node' ] , sourceRoot : 'packages/private-tool/src' } ,
386+ isPrivate : true ,
387+ } ) ;
388+ } ) ;
389+
390+ it ( 'should bump only tools packages with their own base version + suffix' , async ( ) => {
391+ await generator ( tree , { all : true , scope : 'tools' , versionSuffix : suffix } ) ;
392+
393+ const storybookAddon = readJson ( tree , 'packages/react-storybook-addon/package.json' ) ;
394+ const eslintPlugin = readJson ( tree , 'packages/eslint-plugin-react-components/package.json' ) ;
395+ const reactButton = readJson ( tree , 'packages/react-button/package.json' ) ;
396+
397+ expect ( storybookAddon . version ) . toBe ( `0.6.0-${ suffix } ` ) ;
398+ expect ( eslintPlugin . version ) . toBe ( `0.2.1-${ suffix } ` ) ;
399+ // vNext package should not be affected
400+ expect ( reactButton . version ) . toBe ( '9.0.0' ) ;
401+ } ) ;
402+
403+ it ( 'should not bump private tools packages' , async ( ) => {
404+ await generator ( tree , { all : true , scope : 'tools' , versionSuffix : suffix } ) ;
405+
406+ const privateTool = readJson ( tree , 'packages/private-tool/package.json' ) ;
407+ expect ( privateTool . version ) . toBe ( '1.0.0' ) ;
408+ } ) ;
409+
410+ it ( 'should update dependency versions for tools dependents' , async ( ) => {
411+ await generator ( tree , { all : true , scope : 'tools' , versionSuffix : suffix } ) ;
412+
413+ const eslintPlugin = readJson ( tree , 'packages/eslint-plugin-react-components/package.json' ) ;
414+ expect ( eslintPlugin . dependencies [ '@proj/react-storybook-addon' ] ) . toBe ( `0.6.0-${ suffix } ` ) ;
415+ } ) ;
416+
417+ it ( 'should remove beachball disallowedChangeTypes for tools packages' , async ( ) => {
418+ tree = setupDummyPackage ( tree , {
419+ name : 'react-conformance' ,
420+ version : '0.20.1' ,
421+ dependencies : { } ,
422+ projectConfiguration : { tags : [ 'tools' , 'platform:node' ] , sourceRoot : 'packages/react-conformance/src' } ,
423+ beachball : {
424+ disallowedChangeTypes : [ 'prerelease' ] ,
425+ } ,
426+ } ) ;
427+
428+ await generator ( tree , { all : true , scope : 'tools' , versionSuffix : suffix } ) ;
429+
430+ const reactConformance = readJson < PackageJsonWithBeachball > ( tree , 'packages/react-conformance/package.json' ) ;
431+ expect ( reactConformance . version ) . toBe ( `0.20.1-${ suffix } ` ) ;
432+ expect ( reactConformance . beachball ?. disallowedChangeTypes ) . toBeUndefined ( ) ;
433+ } ) ;
434+
435+ it ( 'should exclude specified tools packages' , async ( ) => {
436+ await generator ( tree , {
437+ all : true ,
438+ scope : 'tools' ,
439+ versionSuffix : suffix ,
440+ exclude : 'react-storybook-addon' ,
441+ } ) ;
442+
443+ const storybookAddon = readJson ( tree , 'packages/react-storybook-addon/package.json' ) ;
444+ const eslintPlugin = readJson ( tree , 'packages/eslint-plugin-react-components/package.json' ) ;
445+
446+ expect ( storybookAddon . version ) . toBe ( '0.6.0' ) ;
447+ expect ( eslintPlugin . version ) . toBe ( `0.2.1-${ suffix } ` ) ;
448+ } ) ;
449+ } ) ;
450+
451+ describe ( '--versionSuffix validation' , ( ) => {
452+ it ( 'should throw if --versionSuffix is used with --bumpType' , async ( ) => {
453+ await expect (
454+ generator ( tree , { all : true , versionSuffix : 'experimental.feat.20260408-abc' , bumpType : 'patch' } ) ,
455+ ) . rejects . toThrow ( '--versionSuffix is mutually exclusive with --bumpType and --explicitVersion' ) ;
456+ } ) ;
457+
458+ it ( 'should throw if --versionSuffix is used with --explicitVersion' , async ( ) => {
459+ await expect (
460+ generator ( tree , { all : true , versionSuffix : 'experimental.feat.20260408-abc' , explicitVersion : '1.0.0' } ) ,
461+ ) . rejects . toThrow ( '--versionSuffix is mutually exclusive with --bumpType and --explicitVersion' ) ;
462+ } ) ;
463+
464+ it ( 'should throw if --versionSuffix is used without --all' , async ( ) => {
465+ await expect (
466+ generator ( tree , { name : 'react-button' , versionSuffix : 'experimental.feat.20260408-abc' } ) ,
467+ ) . rejects . toThrow ( '--versionSuffix requires --all' ) ;
468+ } ) ;
469+ } ) ;
350470} ) ;
351471
352472function setupDummyPackage (
@@ -358,6 +478,7 @@ function setupDummyPackage(
358478 dependencies : Record < string , string > ;
359479 projectConfiguration : Partial < ReturnType < typeof readProjectConfiguration > > ;
360480 beachball : PackageJsonWithBeachball [ 'beachball' ] ;
481+ isPrivate : boolean ;
361482 } > ,
362483) {
363484 const workspaceConfig = getWorkspaceConfig ( tree ) ;
@@ -382,6 +503,7 @@ function setupDummyPackage(
382503 packageJson : {
383504 name : `@${ workspaceConfig . npmScope } /${ projectName } ` ,
384505 version : normalizedOptions . version ,
506+ ...( options . isPrivate ? { private : true } : { } ) ,
385507 dependencies : normalizedOptions . dependencies ,
386508 devDependencies : normalizedOptions . devDependencies ,
387509 beachball : options . beachball ,
0 commit comments