@@ -20,7 +20,9 @@ const XCODE_FEED = "https://xcodereleases.com/data.json";
2020const NODE_FEED = "https://nodejs.org/dist/index.json" ;
2121const ANDROID_REPOSITORY = "https://dl.google.com/android/repository/repository2-3.xml" ;
2222const ADOPTIUM_RELEASES = "https://api.adoptium.net/v3/info/available_releases" ;
23- const MACOS_RUNNER_README = "https://raw.githubusercontent.com/actions/runner-images/main/images/macos/macos-15-Readme.md" ;
23+ // Newest image first: an Xcode line shipped by several images runs on the newest one.
24+ const MACOS_RUNNERS = [ "macos-26" , "macos-15" , "macos-14" ] ;
25+ const RUNNER_README = ( image ) => `https://raw.githubusercontent.com/actions/runner-images/main/images/macos/${ image } -Readme.md` ;
2426
2527const CLI_VERSIONS = 2 ; // newest stable CLI releases
2628const RUNTIME_VERSIONS = 2 ; // newest stable releases of each runtime
@@ -91,16 +93,24 @@ async function xcodeLines() {
9193 return [ ...lines ] . sort ( compareVersions ) . slice ( 0 , XCODE_LINES ) ;
9294}
9395
94- // Xcode versions installed on the GitHub macOS runner image, from its published readme .
96+ // Which GitHub macOS runner image ships each Xcode line , from the images' published readmes .
9597async function runnerXcodes ( ) {
96- try {
97- const readme = await text ( MACOS_RUNNER_README ) ;
98- const section = readme . split ( / ^ # + .* X c o d e .* $ / m) [ 1 ] ?? readme ;
99- return [ ...section . matchAll ( / ^ \| \s * ( \d + \. \d + (?: \. \d + ) ? ) / gm) ] . map ( ( m ) => m [ 1 ] ) ;
100- } catch ( err ) {
101- console . warn ( `runner image readme unavailable (${ err . message } ); every Xcode line will be attempted` ) ;
102- return null ;
98+ const byLine = new Map ( ) ;
99+ for ( const image of MACOS_RUNNERS ) {
100+ try {
101+ const readme = await text ( RUNNER_README ( image ) ) ;
102+ const section = readme . split ( / ^ # + .* X c o d e .* $ / m) [ 1 ] ?? readme ;
103+ for ( const [ , version ] of section . matchAll ( / ^ \| \s * ( \d + \. \d + (?: \. \d + ) ? ) / gm) ) {
104+ const line = version . split ( "." ) . slice ( 0 , 2 ) . join ( "." ) ;
105+ if ( ! byLine . has ( line ) ) {
106+ byLine . set ( line , image ) ;
107+ }
108+ }
109+ } catch ( err ) {
110+ console . warn ( `${ image } readme unavailable (${ err . message } )` ) ;
111+ }
103112 }
113+ return byLine ;
104114}
105115
106116// The SDK repository is the only source of installable package ids: newer API
@@ -183,7 +193,12 @@ async function manualMatrix(env) {
183193 const matrix = { ios : [ ] , android : [ ] , deferred : { ios : 0 , android : 0 } , manual : true } ;
184194
185195 if ( platform === "ios" ) {
186- const job = { ...base , xcode : required ( "MANUAL_XCODE" ) } ;
196+ const xcode = required ( "MANUAL_XCODE" ) ;
197+ const runners = await runnerXcodes ( ) ;
198+ if ( ! runners . has ( xcode ) ) {
199+ throw new Error ( `no GitHub macOS runner image ships Xcode ${ xcode } (known: ${ [ ...runners . keys ( ) ] . join ( ", " ) } )` ) ;
200+ }
201+ const job = { ...base , xcode, runner : runners . get ( xcode ) } ;
187202 const result = { package : "@nativescript/ios" , version : job . runtime , toolchains : { xcode : job . xcode } , with : { nativescript : job . cli , node : job . node } } ;
188203 if ( force || ! isRecorded ( result ) ) {
189204 matrix . ios . push ( job ) ;
@@ -230,17 +245,20 @@ const [clis, nodes, iosRuntimes, androidRuntimes, xcodes, installedXcodes, level
230245 jdkReleases ( ) ,
231246] ) ;
232247
248+ // Lines a runner ships that are newer than the newest stable feed line are
249+ // betas; they join the matrix so a new Xcode is exercised before it ships.
250+ const newestStable = xcodes [ 0 ] ;
251+ const betaLines = [ ...installedXcodes . keys ( ) ] . filter ( ( line ) => compareVersions ( line , newestStable ) < 0 ) ;
252+ const xcodeLinesToTest = [ ...new Set ( [ ...betaLines , ...xcodes ] ) ] . filter ( ( line ) => installedXcodes . has ( line ) ) ;
253+
233254const ios = [ ] ;
234255for ( const cli of clis ) {
235256 for ( const node of nodes ) {
236257 for ( const runtime of iosRuntimes ) {
237- for ( const xcode of xcodes ) {
238- if ( installedXcodes && ! installedXcodes . some ( ( v ) => sameLine ( v , xcode ) ) ) {
239- continue ;
240- }
258+ for ( const xcode of xcodeLinesToTest ) {
241259 const result = { package : "@nativescript/ios" , version : runtime , toolchains : { xcode } , with : { nativescript : cli , node } } ;
242260 if ( ! isRecorded ( result ) ) {
243- ios . push ( { cli, node, runtime, xcode } ) ;
261+ ios . push ( { cli, node, runtime, xcode, runner : installedXcodes . get ( xcode ) } ) ;
244262 }
245263 }
246264 }
@@ -273,7 +291,7 @@ const matrix = {
273291 ios : ios . slice ( 0 , MAX_JOBS_PER_MATRIX ) ,
274292 android : android . slice ( 0 , MAX_JOBS_PER_MATRIX ) ,
275293 deferred : { ios : Math . max ( 0 , ios . length - MAX_JOBS_PER_MATRIX ) , android : Math . max ( 0 , android . length - MAX_JOBS_PER_MATRIX ) } ,
276- feeds : { clis, nodes, iosRuntimes, androidRuntimes, xcodes, installedXcodes, levels, jdks } ,
294+ feeds : { clis, nodes, iosRuntimes, androidRuntimes, xcodes, runners : Object . fromEntries ( installedXcodes ) , levels, jdks } ,
277295} ;
278296
279297emit ( matrix ) ;
0 commit comments