1- #!/usr/bin/env zx
2- import 'zx/globals' ;
1+ #!/usr/bin/env node
2+ // @ts -ignore
3+ import { styleText } from 'node:util' ;
4+ import { $ , echo , fs } from 'zx' ;
35
46/**
57 * Validate changesets in CI
68 *
7- * Checks:
8- * 1. Changesets are present (PRs require changesets)
9- * 2. No major version bumps (breaking changes disallowed)
9+ * Checks for major version bumps (breaking changes disallowed).
10+ * Private/ignored packages (per .changeset/config.json) are excluded automatically.
1011 */
1112
12- // ANSI color codes
13- const colors = {
14- red : ( msg : string ) => `\x1b[31m${ msg } \x1b[0m` ,
15- green : ( msg : string ) => `\x1b[32m${ msg } \x1b[0m` ,
16- yellow : ( msg : string ) => `\x1b[33m${ msg } \x1b[0m` ,
17- } ;
13+ const tmpDir = fs . mkdtempSync ( '/tmp/changeset-status-' ) ;
14+ const STATUS_FILE = `${ tmpDir } /status.json` ;
1815
19- // Logging helpers
2016const log = {
21- error : ( msg : string ) => echo ( colors . red ( msg ) ) ,
22- success : ( msg : string ) => echo ( colors . green ( msg ) ) ,
23- warn : ( msg : string ) => echo ( colors . yellow ( msg ) ) ,
17+ error : ( msg : string ) => echo ( styleText ( 'red' , msg ) ) ,
18+ success : ( msg : string ) => echo ( styleText ( 'green' , msg ) ) ,
19+ warn : ( msg : string ) => echo ( styleText ( 'yellow' , msg ) ) ,
2420 info : ( msg : string ) => echo ( msg ) ,
2521} ;
2622
@@ -35,83 +31,37 @@ interface ChangesetStatusOutput {
3531 changesets: string [ ] ;
3632}
3733
38- async function checkChangesetPresence ( ) {
39- log . info ( '\n🔍 Checking for changeset presence...\n' ) ;
40-
41- const result = await $ `yarn changeset status --since=origin/main 2>&1` . nothrow ( ) ;
42-
43- if ( result . exitCode !== 0 ) {
44- log . error ( '❌ Changeset validation failed\n' ) ;
45- echo ( result . stdout ) ;
46- return false ;
47- }
48-
49- log . success ( '✅ Changesets found' ) ;
50- return true ;
51- }
52-
53- async function checkForMajorBumps ( ) {
54- log . info ( '\n🔍 Checking for major version bumps...\n' ) ;
55-
56- const result = await $ `yarn changeset status --output bumps.json` . nothrow ( ) ;
57-
58- // If no changesets, skip major bump check
59- if ( result . exitCode !== 0 && result . stdout . includes ( 'no changesets were found' ) ) {
60- log . warn ( 'No changesets found (skipping major check)' ) ;
61- return true ;
62- }
63-
64- // Other errors
65- if ( result . exitCode !== 0 || ! fs . existsSync ( 'bumps.json' ) ) {
66- log . error ( '❌ Failed to check for major bumps\n' ) ;
67- if ( result . stderr ) log . info ( result . stderr ) ;
68- return false ;
69- }
70-
71- const bumpsData : ChangesetStatusOutput = JSON . parse ( fs . readFileSync ( 'bumps.json' , 'utf-8' ) ) ;
72- fs . unlinkSync ( 'bumps.json' ) ;
73-
74- const majorBumps = bumpsData . releases . filter ( ( release ) => release . type === 'major' ) ;
75-
76- if ( majorBumps . length > 0 ) {
77- log . error ( '❌ Major version bumps detected!\n' ) ;
78- for ( const release of majorBumps ) {
79- log . error ( ` ${ release . name } : major` ) ;
80- if ( release . changesets . length > 0 ) {
81- log . error ( ` (from changesets: ${ release . changesets . join ( ', ' ) } )` ) ;
82- }
83- }
84- log . error ( '\nMajor version bumps are not allowed.' ) ;
85- log . warn ( 'If you need to make a breaking change, please discuss with the team first.\n' ) ;
86- return false ;
87- }
88-
89- log . success ( '✅ No major version bumps found' ) ;
90- return true ;
91- }
92-
93- // Main execution
9434log . info ( `\n${ '=' . repeat ( 60 ) } ` ) ;
9535log . info ( 'Changesets Validation' ) ;
96- log . info ( `${ '=' . repeat ( 60 ) } ` ) ;
97-
98- const results = {
99- presence : await checkChangesetPresence ( ) ,
100- majorBumps : await checkForMajorBumps ( ) ,
101- } ;
102-
103- log . info ( `\n${ '=' . repeat ( 60 ) } ` ) ;
104- log . info ( 'Validation Results:' ) ;
10536log . info ( `${ '=' . repeat ( 60 ) } \n` ) ;
10637
107- log . info ( `Changeset presence: ${ results . presence ? '✅ PASS' : '❌ FAIL' } ` ) ;
108- log . info ( `Major version check: ${ results . majorBumps ? '✅ PASS' : '❌ FAIL' } \n` ) ;
38+ // Pre-write empty state so changeset status always has a file to overwrite
39+ fs . writeJsonSync ( STATUS_FILE , { releases : [ ] , changesets : [ ] } ) ;
40+
41+ await $ `yarn changeset status --since=origin/main --output ${ STATUS_FILE } ` . nothrow ( ) ;
10942
110- const allPassed = results . presence && results . majorBumps ;
43+ const data : ChangesetStatusOutput = fs . readJsonSync ( STATUS_FILE ) ;
44+ fs . removeSync ( tmpDir ) ;
11145
112- if ( ! allPassed ) {
113- log . error ( 'Validation failed!\n' ) ;
46+ // Fail: major version bumps
47+ const majorBumps = data . releases . filter ( ( release ) => release . type === 'major' ) ;
48+ if ( majorBumps . length > 0 ) {
49+ log . error ( '❌ Major version bumps detected!\n' ) ;
50+ for ( const release of majorBumps ) {
51+ log . error ( ` ${ release . name } : major` ) ;
52+ if ( release . changesets . length > 0 ) {
53+ log . error ( ` (from changesets: ${ release . changesets . join ( ', ' ) } )` ) ;
54+ }
55+ }
56+ log . error ( '\nMajor version bumps are not allowed.' ) ;
57+ log . warn ( 'If you need to make a breaking change, please discuss with the team first.\n' ) ;
11458 throw new Error ( 'Validation failed' ) ;
11559}
11660
117- log . success ( 'All validations passed! ✅\n' ) ;
61+ // Pass
62+ if ( data . releases . length === 0 ) {
63+ log . info ( 'ℹ️ No public packages changed — no changeset required' ) ;
64+ } else {
65+ log . success ( `✅ Changesets found (${ data . releases . map ( ( r ) => r . name ) . join ( ', ' ) } )` ) ;
66+ }
67+ log . success ( '\nAll validations passed! ✅\n' ) ;
0 commit comments