11const _ = require ( 'lodash' )
22const path = require ( 'path' )
33const { createFilePath } = require ( 'gatsby-source-filesystem' )
4+ const axios = require ( 'axios' )
45
56exports . createSchemaCustomization = ( { actions } ) => {
67 const { createTypes } = actions
@@ -15,11 +16,18 @@ exports.createSchemaCustomization = ({ actions }) => {
1516 subTitle: String
1617 company: String
1718 }
18-
19+
1920 type BlogCategory {
2021 label: String
2122 id: String
2223 }
24+
25+ type IronicRelease implements Node {
26+ version: String!
27+ releaseNotesUrl: String!
28+ publishedAt: Date @dateformat
29+ htmlUrl: String!
30+ }
2331 `
2432
2533 createTypes ( typeDefs )
@@ -121,6 +129,199 @@ exports.createPages = ({ actions, graphql }) => {
121129 } )
122130}
123131
132+ async function getSeriesStatusData ( ) {
133+ try {
134+ console . log ( '📋 Fetching OpenStack series status data...' )
135+ const response = await axios . get ( 'https://raw.githubusercontent.com/openstack/releases/master/data/series_status.yaml' )
136+
137+ // Simple YAML parsing for series data
138+ const yamlContent = response . data
139+ const seriesMatches = yamlContent . match ( / - n a m e : ( [ ^ \s ] + ) \s + r e l e a s e - i d : ( [ ^ \s ] + ) / g)
140+
141+ if ( ! seriesMatches ) {
142+ throw new Error ( 'Could not parse series status data' )
143+ }
144+
145+ const seriesData = { }
146+ const seriesOrder = [ ]
147+
148+ seriesMatches . forEach ( match => {
149+ const nameMatch = match . match ( / n a m e : ( [ ^ \s ] + ) / )
150+ const idMatch = match . match ( / r e l e a s e - i d : ( [ ^ \s ] + ) / )
151+
152+ if ( nameMatch && idMatch ) {
153+ const name = nameMatch [ 1 ]
154+ const releaseId = idMatch [ 1 ]
155+ seriesData [ name ] = releaseId
156+ seriesOrder . push ( name ) // This gives us newest-first order from the YAML
157+ }
158+ } )
159+
160+ console . log ( `✅ Found ${ Object . keys ( seriesData ) . length } series in OpenStack data` )
161+ return { seriesData, seriesOrder }
162+ } catch ( error ) {
163+ console . log ( '⚠️ Could not fetch series status, using fallback data' )
164+ // Fallback to known series if API fails
165+ const fallbackOrder = [
166+ 'hibiscus' , 'gazpacho' , 'flamingo' , 'epoxy' , 'dalmatian' , 'caracal' ,
167+ 'bobcat' , 'antelope' , 'zed' , 'yoga' , 'xena' , 'wallaby' , 'victoria' , 'ussuri'
168+ ]
169+ const fallbackData = {
170+ 'hibiscus' : '2026.2' , 'gazpacho' : '2026.1' , 'flamingo' : '2025.2' , 'epoxy' : '2025.1' ,
171+ 'dalmatian' : '2024.2' , 'caracal' : '2024.1' , 'bobcat' : '2023.2' , 'antelope' : '2023.1' ,
172+ 'zed' : '2022.2' , 'yoga' : '2022.1' , 'xena' : '2021.2' , 'wallaby' : '2021.1' ,
173+ 'victoria' : '2020.2' , 'ussuri' : '2020.1'
174+ }
175+ return { seriesData : fallbackData , seriesOrder : fallbackOrder }
176+ }
177+ }
178+
179+ async function getLatestReleaseSeries ( ) {
180+ try {
181+ console . log ( '🔍 Auto-detecting latest OpenStack release series...' )
182+
183+ // Get dynamic series data from OpenStack
184+ const { seriesData, seriesOrder } = await getSeriesStatusData ( )
185+ let knownSeries = seriesOrder
186+
187+ console . log ( `📋 Checking series in order: ${ knownSeries . slice ( 0 , 5 ) . join ( ', ' ) } ${ knownSeries . length > 5 ? '...' : '' } ` )
188+
189+ // Try each series until we find one with ironic.yaml
190+ for ( const series of knownSeries ) {
191+ try {
192+ await axios . head ( `https://raw.githubusercontent.com/openstack/releases/master/deliverables/${ series } /ironic.yaml` )
193+ console . log ( `✅ Found Ironic releases in series: ${ series } ` )
194+ return { series, seriesData }
195+ } catch ( error ) {
196+ // Series doesn't have ironic.yaml, try next
197+ continue
198+ }
199+ }
200+
201+ throw new Error ( 'No ironic.yaml found in any release series' )
202+ } catch ( error ) {
203+ console . log ( '⚠️ Auto-detection failed, using known fallbacks' )
204+ // Return known good series as fallbacks with basic mapping
205+ const fallbackData = {
206+ 'gazpacho' : '2026.1' , 'epoxy' : '2025.1' , 'dalmatian' : '2024.2' , 'caracal' : '2024.1'
207+ }
208+ return {
209+ series : [ 'gazpacho' , 'epoxy' , 'dalmatian' , 'caracal' ] ,
210+ seriesData : fallbackData
211+ }
212+ }
213+ }
214+
215+ exports . sourceNodes = async ( { actions, createNodeId, createContentDigest } ) => {
216+ const { createNode } = actions
217+
218+ try {
219+ // Auto-detect the latest release series with dynamic version mapping
220+ const detectionResult = await getLatestReleaseSeries ( )
221+ let response
222+ let releaseSeries
223+ let seriesVersionMap
224+
225+ if ( Array . isArray ( detectionResult . series ) ) {
226+ // Fallback mode - try known series
227+ console . log ( '🔄 Trying fallback series...' )
228+ seriesVersionMap = detectionResult . seriesData
229+ for ( const series of detectionResult . series ) {
230+ try {
231+ response = await axios . get ( `https://raw.githubusercontent.com/openstack/releases/master/deliverables/${ series } /ironic.yaml` )
232+ releaseSeries = series
233+ break
234+ } catch ( error ) {
235+ continue
236+ }
237+ }
238+ if ( ! response ) {
239+ throw new Error ( 'All fallback series failed' )
240+ }
241+ } else {
242+ // Auto-detection succeeded
243+ releaseSeries = detectionResult . series
244+ seriesVersionMap = detectionResult . seriesData
245+ response = await axios . get ( `https://raw.githubusercontent.com/openstack/releases/master/deliverables/${ releaseSeries } /ironic.yaml` )
246+ }
247+
248+ // Parse YAML content (simple parsing for releases section)
249+ const yamlContent = response . data
250+ const releaseMatches = yamlContent . match ( / - v e r s i o n : ( [ \d . ] + ) / g)
251+ if ( ! releaseMatches || releaseMatches . length === 0 ) {
252+ throw new Error ( 'No releases found in YAML' )
253+ }
254+
255+ // Get the latest version (last one in the list)
256+ const latestVersionMatch = releaseMatches [ releaseMatches . length - 1 ]
257+ const version = latestVersionMatch . replace ( '- version: ' , '' )
258+
259+ // Generate release notes URL using dynamic series mapping
260+ let seriesVersion = seriesVersionMap [ releaseSeries ]
261+ if ( ! seriesVersion ) {
262+ console . log ( `⚠️ Unknown series '${ releaseSeries } ', using generic release notes URL` )
263+ seriesVersion = 'latest'
264+ }
265+
266+ const releaseNotesUrl = seriesVersion === 'latest'
267+ ? `https://docs.openstack.org/releasenotes/ironic/latest.html#relnotes-${ version . replace ( / \. / g, '-' ) } `
268+ : `https://docs.openstack.org/releasenotes/ironic/${ seriesVersion } .html#relnotes-${ version . replace ( / \. / g, '-' ) } `
269+
270+ const nodeData = {
271+ version,
272+ releaseNotesUrl,
273+ publishedAt : new Date ( ) . toISOString ( ) , // Use current date as we don't have publish date from YAML
274+ htmlUrl : `https://github.com/openstack/releases/blob/master/deliverables/${ releaseSeries } /ironic.yaml` ,
275+ releaseSeries,
276+ }
277+
278+ const nodeContent = JSON . stringify ( nodeData )
279+
280+ const nodeMeta = {
281+ id : createNodeId ( 'ironic-latest-release' ) ,
282+ parent : null ,
283+ children : [ ] ,
284+ internal : {
285+ type : 'IronicRelease' ,
286+ content : nodeContent ,
287+ contentDigest : createContentDigest ( nodeData ) ,
288+ } ,
289+ }
290+
291+ const node = Object . assign ( { } , nodeData , nodeMeta )
292+ createNode ( node )
293+
294+ console . log ( `✅ Fetched latest Ironic release: ${ version } (${ releaseSeries } series)` )
295+ } catch ( error ) {
296+ console . error ( '❌ Failed to fetch latest Ironic release:' , error . message )
297+ // Fallback to current known version if all APIs fail
298+ const fallbackData = {
299+ version : '34.0.0' ,
300+ releaseNotesUrl : 'https://docs.openstack.org/releasenotes/ironic/latest.html#relnotes-34-0-0' ,
301+ publishedAt : null ,
302+ htmlUrl : 'https://docs.openstack.org/releasenotes/ironic/' ,
303+ releaseSeries : 'fallback' ,
304+ }
305+
306+ const nodeContent = JSON . stringify ( fallbackData )
307+ const nodeMeta = {
308+ id : createNodeId ( 'ironic-latest-release' ) ,
309+ parent : null ,
310+ children : [ ] ,
311+ internal : {
312+ type : 'IronicRelease' ,
313+ content : nodeContent ,
314+ contentDigest : createContentDigest ( fallbackData ) ,
315+ } ,
316+ }
317+
318+ const node = Object . assign ( { } , fallbackData , nodeMeta )
319+ createNode ( node )
320+
321+ console . log ( '⚠️ Using fallback version: 34.0.0' )
322+ }
323+ }
324+
124325exports . onCreateNode = ( { node, actions, getNode } ) => {
125326 const { createNodeField } = actions
126327
0 commit comments