From 4a835eee57162068b9e728b2b7a463badd9bb6e3 Mon Sep 17 00:00:00 2001 From: CyberNative AI LLC Date: Mon, 12 Oct 2026 14:25:45 -0700 Subject: [PATCH] Suggest the latest patch release when a version is unavailable Newer runner images such as ubuntu-26.04 only have builds of the latest patch release of each series, so an exact version like 3.4.2 from .ruby-version fails with a 404 and only a link to request the build. On a 404, check whether the latest release of the same series exists for the platform. If it does, name it and the short version that always picks it. If it does not, say so and suggest a newer version or an older runner image. If that check fails for another reason, keep the previous message. Also document this in the README. --- README.md | 4 ++++ dist/index.js | 38 +++++++++++++++++++++++++++++++++++++- ruby-builder.js | 38 +++++++++++++++++++++++++++++++++++++- 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 44a86be27..a24d22c43 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,10 @@ Not all combinations of runner images and versions are supported. The list of available Ruby versions can be seen in [ruby-builder-versions.json](ruby-builder-versions.json) for Ubuntu and macOS (although some combinations are not available, see [the full list](https://github.com/ruby/ruby-builder/releases/tag/toolcache)) and in [windows-versions.json](windows-versions.json) for Windows. +Newer runner images usually only have builds of the latest patch release of each series, +so prefer a short version like `'3.4'` over an exact one like `'3.4.2'` in `ruby-version` or `.ruby-version`. +For example, `ubuntu-26.04` (which `ubuntu-latest` moves to, see [this announcement](https://github.com/actions/runner-images/issues/14748)) +only has builds of CRuby 3.2 and newer, as older CRuby versions do not compile on it. The prebuilt releases are generated by [ruby-builder](https://github.com/ruby/ruby-builder) and on Windows by [RubyInstaller2](https://github.com/oneclick/rubyinstaller2). diff --git a/dist/index.js b/dist/index.js index e11589521..ebcff2703 100644 --- a/dist/index.js +++ b/dist/index.js @@ -50281,7 +50281,7 @@ async function downloadAndExtract(platform, engine, version, rubyPrefix) { } catch (error) { if (error.message.includes('404')) { throw new Error(`Unavailable version ${version} for ${engine} on ${platform} - You can request it at https://github.com/ruby/setup-ruby/issues + ${await unavailableVersionHint(platform, engine, version)} Cause: ${error.message}`) } else { throw error @@ -50303,6 +50303,42 @@ async function downloadAndExtract(platform, engine, version, rubyPrefix) { } } +// Newer runner images usually only have builds of the latest patch release of each series, +// so point to that release if it is available, or explain that the series is not available. +async function unavailableVersionHint(platform, engine, version) { + const request = 'You can request it at https://github.com/ruby/setup-ruby/issues' + const series = version.split('.').slice(0, 2).join('.') + const latest = getAvailableVersions(platform, engine) + .filter(v => v.startsWith(`${series}.`) && common.isStableVersion(engine, v)) + .pop() + if (common.isHeadVersion(version) || latest === undefined) { + return request + } + + const input = v => engine === 'ruby' ? v : `${engine}-${v}` + if (latest !== version) { + let response + try { + response = await fetch(getDownloadURL(platform, engine, latest), { method: 'HEAD', signal: AbortSignal.timeout(10000) }) + } catch { + return request + } + if (response.ok) { + return `Only the latest patch release of each series is built for newer runner images. + ${engine}-${latest} is available on ${platform}: use ruby-version: '${input(latest)}', or '${input(series)}' to always use the latest ${series}.x release.` + } else if (response.status !== 404) { + return request + } + } + + const latestUnavailable = latest === version + ? `${engine}-${version} is the latest ${series}.x release.` + : `${engine}-${latest}, the latest ${series}.x release, is not available on ${platform} either.` + return `${latestUnavailable} + Use a newer ${engine} version on ${platform}, or an older runner image for ${engine} ${series}. + Note that ubuntu-latest and macos-latest move to newer runner images over time.` +} + function getDownloadURL(platform, engine, version) { let builderPlatform = null if (platform.startsWith('windows-')) { diff --git a/ruby-builder.js b/ruby-builder.js index 52041c889..502779080 100644 --- a/ruby-builder.js +++ b/ruby-builder.js @@ -78,7 +78,7 @@ async function downloadAndExtract(platform, engine, version, rubyPrefix) { } catch (error) { if (error.message.includes('404')) { throw new Error(`Unavailable version ${version} for ${engine} on ${platform} - You can request it at https://github.com/ruby/setup-ruby/issues + ${await unavailableVersionHint(platform, engine, version)} Cause: ${error.message}`) } else { throw error @@ -100,6 +100,42 @@ async function downloadAndExtract(platform, engine, version, rubyPrefix) { } } +// Newer runner images usually only have builds of the latest patch release of each series, +// so point to that release if it is available, or explain that the series is not available. +async function unavailableVersionHint(platform, engine, version) { + const request = 'You can request it at https://github.com/ruby/setup-ruby/issues' + const series = version.split('.').slice(0, 2).join('.') + const latest = getAvailableVersions(platform, engine) + .filter(v => v.startsWith(`${series}.`) && common.isStableVersion(engine, v)) + .pop() + if (common.isHeadVersion(version) || latest === undefined) { + return request + } + + const input = v => engine === 'ruby' ? v : `${engine}-${v}` + if (latest !== version) { + let response + try { + response = await fetch(getDownloadURL(platform, engine, latest), { method: 'HEAD', signal: AbortSignal.timeout(10000) }) + } catch { + return request + } + if (response.ok) { + return `Only the latest patch release of each series is built for newer runner images. + ${engine}-${latest} is available on ${platform}: use ruby-version: '${input(latest)}', or '${input(series)}' to always use the latest ${series}.x release.` + } else if (response.status !== 404) { + return request + } + } + + const latestUnavailable = latest === version + ? `${engine}-${version} is the latest ${series}.x release.` + : `${engine}-${latest}, the latest ${series}.x release, is not available on ${platform} either.` + return `${latestUnavailable} + Use a newer ${engine} version on ${platform}, or an older runner image for ${engine} ${series}. + Note that ubuntu-latest and macos-latest move to newer runner images over time.` +} + function getDownloadURL(platform, engine, version) { let builderPlatform = null if (platform.startsWith('windows-')) {