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,219 @@ 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+ // Extract git hash for the latest version to get actual release date
260+ let publishedAt = null
261+ try {
262+ // Find the git hash for this version
263+ const hashPattern = new RegExp ( `- version: ${ version . replace ( / \. / g, '\\.' ) } [\\s\\S]*?hash: ([a-f0-9]+)` , 'i' )
264+ const hashMatch = yamlContent . match ( hashPattern )
265+
266+ if ( hashMatch && hashMatch [ 1 ] ) {
267+ const gitHash = hashMatch [ 1 ]
268+ console . log ( `🔍 Found git hash for ${ version } : ${ gitHash . substring ( 0 , 8 ) } ...` )
269+
270+ // Get commit date from GitHub API
271+ const commitResponse = await axios . get ( `https://api.github.com/repos/openstack/ironic/commits/${ gitHash } ` )
272+ publishedAt = commitResponse . data . commit . committer . date
273+ console . log ( `📅 Release date for ${ version } : ${ publishedAt } ` )
274+ }
275+ } catch ( error ) {
276+ console . log ( `⚠️ Could not fetch release date for ${ version } : ${ error . message } ` )
277+ }
278+
279+ // Generate release notes URL using dynamic series mapping
280+ let seriesVersion = seriesVersionMap [ releaseSeries ]
281+ if ( ! seriesVersion ) {
282+ console . log ( `⚠️ Unknown series '${ releaseSeries } ', using generic release notes URL` )
283+ seriesVersion = 'latest'
284+ }
285+
286+ const releaseNotesUrl = seriesVersion === 'latest'
287+ ? `https://docs.openstack.org/releasenotes/ironic/latest.html#relnotes-${ version . replace ( / \. / g, '-' ) } `
288+ : `https://docs.openstack.org/releasenotes/ironic/${ seriesVersion } .html#relnotes-${ version . replace ( / \. / g, '-' ) } `
289+
290+ const nodeData = {
291+ version,
292+ releaseNotesUrl,
293+ publishedAt,
294+ htmlUrl : `https://github.com/openstack/releases/blob/master/deliverables/${ releaseSeries } /ironic.yaml` ,
295+ releaseSeries,
296+ }
297+
298+ const nodeContent = JSON . stringify ( nodeData )
299+
300+ const nodeMeta = {
301+ id : createNodeId ( 'ironic-latest-release' ) ,
302+ parent : null ,
303+ children : [ ] ,
304+ internal : {
305+ type : 'IronicRelease' ,
306+ content : nodeContent ,
307+ contentDigest : createContentDigest ( nodeData ) ,
308+ } ,
309+ }
310+
311+ const node = Object . assign ( { } , nodeData , nodeMeta )
312+ createNode ( node )
313+
314+ console . log ( `✅ Fetched latest Ironic release: ${ version } (${ releaseSeries } series)` )
315+ } catch ( error ) {
316+ console . error ( '❌ Failed to fetch latest Ironic release:' , error . message )
317+ // Fallback to current known version if all APIs fail
318+ const fallbackData = {
319+ version : '34.0.0' ,
320+ releaseNotesUrl : 'https://docs.openstack.org/releasenotes/ironic/latest.html#relnotes-34-0-0' ,
321+ publishedAt : null ,
322+ htmlUrl : 'https://docs.openstack.org/releasenotes/ironic/' ,
323+ releaseSeries : 'fallback' ,
324+ }
325+
326+ const nodeContent = JSON . stringify ( fallbackData )
327+ const nodeMeta = {
328+ id : createNodeId ( 'ironic-latest-release' ) ,
329+ parent : null ,
330+ children : [ ] ,
331+ internal : {
332+ type : 'IronicRelease' ,
333+ content : nodeContent ,
334+ contentDigest : createContentDigest ( fallbackData ) ,
335+ } ,
336+ }
337+
338+ const node = Object . assign ( { } , fallbackData , nodeMeta )
339+ createNode ( node )
340+
341+ console . log ( '⚠️ Using fallback version: 34.0.0' )
342+ }
343+ }
344+
124345exports . onCreateNode = ( { node, actions, getNode } ) => {
125346 const { createNodeField } = actions
126347
0 commit comments