Skip to content

Commit ddd4c0f

Browse files
committed
ci: discover macOS runner images from the runner-images README
Labels and readmes come from the README table, so a new macOS image or an Xcode preview image joins the matrix without configuration.
1 parent f1ba3c9 commit ddd4c0f

2 files changed

Lines changed: 49 additions & 10 deletions

File tree

‎README.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ as a browser-support-style matrix (Vue + Vite) for people.
2929

3030
1. **Matrix**: `scripts/build-matrix.mjs` pulls the newest CLI and runtime
3131
releases from npm, Node.js majors from nodejs.org, Xcode lines from
32-
xcodereleases.com (each mapped to the newest GitHub macOS runner image
33-
that ships it, including beta lines a runner already has), Android API levels from the SDK repository and JDK LTS releases
32+
xcodereleases.com (each sent to the GitHub-hosted runner image that ships
33+
it, discovered from the runner-images README, including Xcode preview
34+
images), Android API levels from the SDK repository and JDK LTS releases
3435
from Adoptium, forms every pinned combination, and drops the ones that
3536
already have a file under `data/verified/`. The first run is large (GitHub
3637
allows 256 jobs per matrix; the rest wait for the next run); afterwards a

‎scripts/build-matrix.mjs‎

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ const XCODE_FEED = "https://xcodereleases.com/data.json";
2020
const NODE_FEED = "https://nodejs.org/dist/index.json";
2121
const ANDROID_REPOSITORY = "https://dl.google.com/android/repository/repository2-3.xml";
2222
const ADOPTIUM_RELEASES = "https://api.adoptium.net/v3/info/available_releases";
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`;
23+
// The runner-images README lists every hosted image with its labels and readme,
24+
// so new macOS images and Xcode preview images are discovered, not configured.
25+
const RUNNER_IMAGES_README = "https://raw.githubusercontent.com/actions/runner-images/main/README.md";
2626

2727
const CLI_VERSIONS = 2; // newest stable CLI releases
2828
const RUNTIME_VERSIONS = 2; // newest stable releases of each runtime
@@ -93,21 +93,59 @@ async function xcodeLines() {
9393
return [...lines].sort(compareVersions).slice(0, XCODE_LINES);
9494
}
9595

96-
// Which GitHub macOS runner image ships each Xcode line, from the images' published readmes.
96+
// macOS images from the runner-images README table: name, arch, labels, readme.
97+
async function macosRunnerImages() {
98+
const readme = await text(RUNNER_IMAGES_README);
99+
const links = new Map(
100+
[...readme.matchAll(/^\[([^\]]+)\]:\s*(\S+)/gm)].map(([, ref, url]) => [ref.toLowerCase(), url]),
101+
);
102+
const images = [];
103+
for (const row of readme.split("\n")) {
104+
const cols = row.split("|").map((c) => c.trim());
105+
if (cols.length < 5) {
106+
continue;
107+
}
108+
const [, name, arch, labelCell, refCell] = cols;
109+
const ref = refCell.match(/^\[([^\]]+)\]$/)?.[1];
110+
if (!ref || !/macos|xcode/i.test(name)) {
111+
continue;
112+
}
113+
const url = links.get(ref.toLowerCase());
114+
const labels = [...labelCell.matchAll(/`([^`]+)`/g)].map((m) => m[1]);
115+
if (!url || !labels.length) {
116+
continue;
117+
}
118+
images.push({
119+
name: name.replace(/<br>.*$/s, "").trim(),
120+
arch,
121+
// The plain label: not the "-large" size variants, and not "macos-latest", which moves.
122+
label: labels.find((label) => !/large|latest/.test(label)) ?? labels[0],
123+
readme: url.replace("github.com/", "raw.githubusercontent.com/").replace("/blob/", "/"),
124+
os: Number(name.match(/macOS (\d+)/)?.[1] ?? 0),
125+
preview: /xcode/i.test(name),
126+
});
127+
}
128+
// arm64 first, regular images before Xcode preview images, newest OS first.
129+
return images.sort(
130+
(a, b) => Number(b.arch === "arm64") - Number(a.arch === "arm64") || Number(a.preview) - Number(b.preview) || b.os - a.os,
131+
);
132+
}
133+
134+
// Which runner label ships each Xcode line, from every image's readme.
97135
async function runnerXcodes() {
98136
const byLine = new Map();
99-
for (const image of MACOS_RUNNERS) {
137+
for (const image of await macosRunnerImages()) {
100138
try {
101-
const readme = await text(RUNNER_README(image));
139+
const readme = await text(image.readme);
102140
const section = readme.split(/^#+ .*Xcode.*$/m)[1] ?? readme;
103141
for (const [, version] of section.matchAll(/^\|\s*(\d+\.\d+(?:\.\d+)?)/gm)) {
104142
const line = version.split(".").slice(0, 2).join(".");
105143
if (!byLine.has(line)) {
106-
byLine.set(line, image);
144+
byLine.set(line, image.label);
107145
}
108146
}
109147
} catch (err) {
110-
console.warn(`${image} readme unavailable (${err.message})`);
148+
console.warn(`${image.name} readme unavailable (${err.message})`);
111149
}
112150
}
113151
return byLine;

0 commit comments

Comments
 (0)