55 * Usage:
66 * npm run data-sync:gb-releases # Parse all releases
77 * npm run data-sync:gb-releases -- --version 20.0 # Parse specific version
8+ * npm run data-sync:gb-releases -- --version 20.0.1 # Refresh the 20.0 aggregate
89 * npm run data-sync:gb-releases -- --from 19.0 --to 20.0 # Parse range
910 *
1011 * @module scripts/build-gb-releases
1314import { parseArgs } from 'node:util' ;
1415import { existsSync , mkdirSync } from 'node:fs' ;
1516import { dirname , basename } from 'node:path' ;
17+ import { pathToFileURL } from 'node:url' ;
1618import { writeJsonIfChanged } from './utils/file-utils.js' ;
1719import {
1820 fetchAllReleases ,
19- fetchReleaseByTag ,
2021 filterReleasesByVersion ,
22+ isReleaseCandidate ,
2123} from './utils/github-api.js' ;
2224import { parseRelease } from './utils/changelog-parser.js' ;
2325import {
@@ -26,7 +28,7 @@ import {
2628 getMinorVersion ,
2729 aggregatePatchReleases ,
2830} from './utils/release-utils.js' ;
29- import type { ParseArgs } from './utils/types.js' ;
31+ import type { GitHubRelease , ParseArgs } from './utils/types.js' ;
3032import type { Release } from './types.js' ;
3133
3234const PARSER_VERSION = '1.0.0' ;
@@ -86,11 +88,60 @@ function loadExistingReleases(outputPath: string): Release[] {
8688 }
8789}
8890
91+ function getReleaseVersion ( release : GitHubRelease ) : string {
92+ return release . tag_name . replace ( / ^ v / , '' ) ;
93+ }
94+
95+ function getRequestedVersion ( version : string ) : string {
96+ return version . replace ( / ^ v / , '' ) ;
97+ }
98+
99+ function hasStableReleaseTag ( releases : GitHubRelease [ ] , version : string ) : boolean {
100+ const requestedVersion = getRequestedVersion ( version ) ;
101+
102+ return releases . some ( ( release ) => {
103+ const releaseVersion = getReleaseVersion ( release ) ;
104+ return releaseVersion === requestedVersion && ! isReleaseCandidate ( releaseVersion ) ;
105+ } ) ;
106+ }
107+
108+ function shouldRequireExactStableTag ( version : string ) : boolean {
109+ return getRequestedVersion ( version ) . split ( '.' ) . length !== 2 ;
110+ }
111+
112+ /**
113+ * Select stable releases for a requested version.
114+ *
115+ * Two-part versions return every stable release for that minor. Three-part
116+ * versions prove the requested tag exists first, then still return the whole
117+ * minor line because gb-releases.json only stores minor aggregates.
118+ */
119+ export function selectStableReleasesForVersion (
120+ releases : GitHubRelease [ ] ,
121+ version : string
122+ ) : GitHubRelease [ ] {
123+ if ( shouldRequireExactStableTag ( version ) && ! hasStableReleaseTag ( releases , version ) ) {
124+ return [ ] ;
125+ }
126+
127+ const targetMinorVersion = getMinorVersion ( getRequestedVersion ( version ) ) ;
128+
129+ return releases . filter ( ( release ) => {
130+ const releaseVersion = getReleaseVersion ( release ) ;
131+
132+ if ( isReleaseCandidate ( releaseVersion ) ) {
133+ return false ;
134+ }
135+
136+ return getMinorVersion ( releaseVersion ) === targetMinorVersion ;
137+ } ) ;
138+ }
139+
89140/**
90141 * Merge new releases with existing ones.
91142 * New data takes precedence, but preserves contributorAggregates from existing.
92143 */
93- function mergeReleases ( existing : Release [ ] , newReleases : Release [ ] ) : Release [ ] {
144+ export function mergeReleases ( existing : Release [ ] , newReleases : Release [ ] ) : Release [ ] {
94145 const releaseMap = new Map < string , Release > ( ) ;
95146
96147 // Add existing releases (keyed by minor version)
@@ -136,14 +187,20 @@ async function main() {
136187 console . log ( 'Gutenberg Release Parser' ) ;
137188 console . log ( '========================' ) ;
138189
190+ const requestedVersion = args . version ? getRequestedVersion ( args . version ) : undefined ;
139191 // Check if --version is a minor version (e.g., "20.0") or patch version (e.g., "20.0.1")
140- const isMinorVersion = args . version && args . version . split ( '.' ) . length === 2 ;
192+ const isMinorVersion = requestedVersion && requestedVersion . split ( '.' ) . length === 2 ;
193+ const requestedMinorVersion = requestedVersion
194+ ? getMinorVersion ( requestedVersion )
195+ : undefined ;
141196
142197 if ( args . version ) {
143198 if ( isMinorVersion ) {
144- console . log ( `Parsing minor version: ${ args . version } (all patch releases)` ) ;
199+ console . log ( `Parsing minor version: ${ requestedMinorVersion } (stable releases only )` ) ;
145200 } else {
146- console . log ( `Parsing single version: ${ args . version } ` ) ;
201+ console . log (
202+ `Parsing version: ${ requestedVersion } (refreshing the ${ requestedMinorVersion } aggregate)`
203+ ) ;
147204 }
148205 } else if ( args . from || args . to ) {
149206 console . log ( `Parsing version range: ${ args . from ?? 'earliest' } to ${ args . to ?? 'latest' } ` ) ;
@@ -155,29 +212,17 @@ async function main() {
155212
156213 try {
157214 // Fetch releases
158- let releases ;
159- if ( args . version && ! isMinorVersion ) {
160- // Exact patch version lookup (e.g., "20.0.1")
161- const release = await fetchReleaseByTag ( args . version ) ;
162- releases = release ? [ release ] : [ ] ;
163- if ( releases . length === 0 ) {
164- console . error ( `Release not found: ${ args . version } ` ) ;
165- process . exit ( 1 ) ;
166- }
167- } else if ( args . version && isMinorVersion ) {
168- // Minor version: fetch all and filter by matching minor version
215+ let releases : GitHubRelease [ ] ;
216+ if ( args . version ) {
169217 const allReleases = await fetchAllReleases ( ) ;
170- releases = allReleases . filter ( ( r ) => {
171- const releaseVersion = r . tag_name . replace ( / ^ v / , '' ) ;
172- // Skip release candidates
173- if ( releaseVersion . includes ( '-rc' ) || releaseVersion . includes ( '-RC' ) ) {
174- return false ;
175- }
176- // Match by minor version (e.g., "20.0" matches "20.0.0", "20.0.1", etc.)
177- return getMinorVersion ( releaseVersion ) === args . version ;
178- } ) ;
218+ releases = selectStableReleasesForVersion ( allReleases , args . version ) ;
219+
179220 if ( releases . length === 0 ) {
180- console . error ( `No releases found for minor version: ${ args . version } ` ) ;
221+ if ( isMinorVersion ) {
222+ console . error ( `No stable releases found for minor version: ${ requestedMinorVersion } ` ) ;
223+ } else {
224+ console . error ( `Stable release not found: ${ requestedVersion } ` ) ;
225+ }
181226 process . exit ( 1 ) ;
182227 }
183228 } else {
@@ -271,4 +316,6 @@ async function main() {
271316 }
272317}
273318
274- main ( ) ;
319+ if ( process . argv [ 1 ] && import . meta. url === pathToFileURL ( process . argv [ 1 ] ) . href ) {
320+ main ( ) ;
321+ }
0 commit comments