diff --git a/.devcontainer/quarto-1.10/devcontainer.json b/.devcontainer/quarto-1.10/devcontainer.json new file mode 100644 index 0000000..16a7af7 --- /dev/null +++ b/.devcontainer/quarto-1.10/devcontainer.json @@ -0,0 +1,36 @@ +{ + "name": "1.10 - Quarto Codespaces", + "image": "ghcr.io/mcanouil/quarto-codespaces:latest", + "remoteUser": "vscode", + "features": { + "ghcr.io/rocker-org/devcontainer-features/quarto-cli:1": { + "version": "1.10" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "quarto.quarto", + "mcanouil.quarto-wizard", + "REditorSupport.r", + "ms-python.python", + "julialang.language-julia", + "ms-python.black-formatter", + "ms-python.flake8", + "ms-python.vscode-pylance", + "ms-python.pylint", + "ms-python.isort", + "ms-toolsai.jupyter", + "Posit.air-vscode" + ], + "settings": { + "r.rterm.option": ["--no-save", "--no-restore-data", "--quiet"], + "r.useRenvLibPath": true, + "[r]": { + "editor.defaultFormatter": "Posit.air-vscode", + "editor.formatOnSave": true + } + } + } + } +} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0232be3..16be87f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -8,6 +8,10 @@ on: paths: - "docs/**" - ".github/workflows/deploy.yml" + pull_request: + paths: + - "docs/**" + - ".github/workflows/deploy.yml" release: types: [ published ] @@ -17,24 +21,22 @@ permissions: id-token: write concurrency: - group: "pages" - cancel-in-progress: false + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - deploy: + build: runs-on: ubuntu-latest - name: Deploy - Documentation website - - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} + name: Build - Documentation website steps: - name: Setup Git user uses: mcanouil/quarto-workflows/.github/actions/setup-git-user@main id: setup-git-user with: - gh-app-id: ${{ vars.APP_ID }} + # Pull requests from forks cannot read the app key, so fall back to + # the workflow token there; nothing is pushed from this workflow. + gh-app-id: ${{ github.event_name != 'pull_request' && vars.APP_ID || '' }} app-key: ${{ secrets.APP_KEY }} gh-token: ${{ secrets.GITHUB_TOKEN }} @@ -53,17 +55,30 @@ jobs: working-directory: ./docs run: quarto render . - - name: Prepare site for deployment - run: touch docs/_site/.nojekyll - - name: Configure GitHub Pages + if: github.event_name != 'pull_request' uses: actions/configure-pages@v6 - name: Upload Pages artifact + if: github.event_name != 'pull_request' uses: actions/upload-pages-artifact@v5 with: path: "./docs/_site" + deploy: + runs-on: ubuntu-latest + name: Deploy - Documentation website + + needs: + - "build" + + if: github.event_name != 'pull_request' + + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v5 diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 1a34903..3147a4d 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -225,6 +225,9 @@ jobs: permissions: contents: write + env: + TARGET_FILE: docs/reference/quarto-check.qmd + steps: - name: Checkout repository uses: actions/checkout@v7 @@ -265,13 +268,20 @@ jobs: env: BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - TARGET_FILE: docs/reference/quarto-check.qmd run: | update_docs_section() { local version=$1 local output_file=$2 local build_url=$3 + # An empty transcript would be committed as an empty code block, so + # fail loudly when the artifact is missing rather than silently + # publishing a blank section. + if [ ! -s "${output_file}" ]; then + echo "::error::Missing or empty quarto check output: ${output_file}" + return 1 + fi + # Normalise version for marker tags local version_upper version_upper=$(echo "${version}" | tr '[:lower:]' '[:upper:]') @@ -286,16 +296,19 @@ jobs: local temp_file temp_file=$(mktemp) - # Update quarto check output section. The transcript is read from - # the file inside awk rather than passed with -v, because a - # multi-line -v value is not portable across awk variants. The - # blank lines around the injected block are required: without them + # Replace both marked sections in a single pass. The transcript is + # read from the file inside awk rather than passed with -v, because + # a multi-line -v value is not portable across awk variants. The + # blank lines around each injected block are required: without them # Pandoc reads the code fence as part of the HTML comment block. - awk -v start="${output_start}" \ - -v end="${output_end}" \ - -v outfile="${output_file}" ' + awk -v output_start="${output_start}" \ + -v output_end="${output_end}" \ + -v url_start="${url_start}" \ + -v url_end="${url_end}" \ + -v outfile="${output_file}" \ + -v url="${build_url}" ' BEGIN { in_block=0 } - index($0, start) { + index($0, output_start) { print $0 print "" print "```txt" @@ -309,20 +322,7 @@ jobs: in_block=1 next } - index($0, end) { - print $0 - in_block=0 - next - } - !in_block { print } - ' "${TARGET_FILE}" > "${temp_file}" - - # Update build URL section - awk -v start="${url_start}" \ - -v end="${url_end}" \ - -v url="${build_url}" ' - BEGIN { in_block=0 } - index($0, start) { + index($0, url_start) { print $0 print "" print "[View build log](" url ")" @@ -330,15 +330,15 @@ jobs: in_block=1 next } - index($0, end) { + index($0, output_end) || index($0, url_end) { print $0 in_block=0 next } !in_block { print } - ' "${temp_file}" > "${TARGET_FILE}" + ' "${TARGET_FILE}" > "${temp_file}" - rm -f "${temp_file}" + mv "${temp_file}" "${TARGET_FILE}" echo "Updated ${version} section in ${TARGET_FILE}" } @@ -352,7 +352,6 @@ jobs: GH_TOKEN: ${{ steps.app-token.outputs.token }} BRANCH: "ci/update-quarto-check-docs" COMMIT: "ci: update quarto check outputs in documentation" - TARGET_FILE: docs/reference/quarto-check.qmd run: | if git show-ref --quiet refs/heads/${BRANCH}; then echo "Branch ${BRANCH} already exists." @@ -371,10 +370,9 @@ jobs: sleep 2 gh pr merge --auto --squash --delete-branch - build-versions: + list-versions: runs-on: ubuntu-latest - - name: Build - Quarto ${{ matrix.QUARTO_VERSION }} + name: List - Version-pinned configurations needs: - "merge" @@ -387,21 +385,42 @@ jobs: always() && needs.merge.result == 'success' && needs.update-docs.result == 'success' + outputs: + versions: ${{ steps.versions.outputs.versions }} + + steps: + - uses: actions/checkout@v7 + + # Derive the matrix from the repository so that adding a + # .devcontainer/quarto-/ directory is the only step needed to + # build a new version. + - name: Collect version-pinned configurations + id: versions + run: | + VERSIONS=$(find .devcontainer -mindepth 1 -maxdepth 1 -type d -name 'quarto-[0-9]*' \ + -exec basename {} \; | sed 's/^quarto-//' | sort -V | jq -R . | jq -sc .) + + if [ "${VERSIONS}" = "[]" ]; then + echo "::error::No version-pinned configurations found under .devcontainer/" + exit 1 + fi + + echo "Versions: ${VERSIONS}" + echo "versions=${VERSIONS}" >> "${GITHUB_OUTPUT}" + + build-versions: + runs-on: ubuntu-latest + + name: Build - Quarto ${{ matrix.QUARTO_VERSION }} + + needs: + - "list-versions" + strategy: matrix: PLATFORM: - "linux/amd64" - QUARTO_VERSION: - - "1.0" - - "1.1" - - "1.2" - - "1.3" - - "1.4" - - "1.5" - - "1.6" - - "1.7" - - "1.8" - - "1.9" + QUARTO_VERSION: ${{ fromJSON(needs.list-versions.outputs.versions) }} steps: - uses: actions/checkout@v7 diff --git a/README.md b/README.md index a2eea33..20f39fd 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ docker pull ghcr.io/mcanouil/quarto-codespaces:latest | `latest` | stable release | `amd64`, `arm64` | | `release`, `release-noble` | stable release | `amd64`, `arm64` | | `prerelease`, `prerelease-noble` | prerelease | `amd64`, `arm64` | -| `1.0` … `1.9`, `1.0-noble` … `1.9-noble` | that minor version | `amd64` | +| one per Quarto minor version, with a `-noble` variant | that minor version | `amd64` | | any tag with a `-` suffix | as above, pinned to one build | as above | Images are rebuilt every Sunday and scanned with [Trivy](https://trivy.dev/). @@ -47,7 +47,7 @@ Full tag list: [`ghcr.io/mcanouil/quarto-codespaces`](https://github.com/mcanoui } ``` -Configurations for the Quarto prerelease and for every minor version from `1.0` to `1.9` are in [`.devcontainer/`](.devcontainer). +Configurations for the Quarto prerelease and for every supported Quarto minor version are in [`.devcontainer/`](.devcontainer), and listed on the [configurations](https://m.canouil.dev/quarto-codespaces/reference/configurations.html) page. Click **Use this template** to start your own repository from this one. ## Documentation diff --git a/docs/_brand.yml b/docs/_brand.yml index 6a63a1a..f1cac9f 100644 --- a/docs/_brand.yml +++ b/docs/_brand.yml @@ -10,10 +10,10 @@ color: palette: midnight: "#0B1220" midnight-2: "#111C2E" - slate: "#1B2942" frost: "#F5F7FA" ink: "#16202E" mist: "#DCE3EC" + mist-muted: "#8EA0B8" amber: "#E8A33D" amber-deep: "#A25E0C" background: diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 2c2d104..9cfe348 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -50,6 +50,7 @@ format: - assets/stylesheets/theme.scss filters: + - assets/filters/devcontainer-configurations.lua - at: post-quarto path: gitlink diff --git a/docs/assets/filters/devcontainer-configurations.lua b/docs/assets/filters/devcontainer-configurations.lua new file mode 100644 index 0000000..9614f83 --- /dev/null +++ b/docs/assets/filters/devcontainer-configurations.lua @@ -0,0 +1,141 @@ +--[[ +Generate the configuration reference from the repository itself. + +The `.devcontainer/` directory gains a new `quarto-` configuration +every time Quarto releases a minor version, and every place that lists those +versions by hand drifts the moment one is added. This filter reads the +directory at render time and fills in: + + - `::: {#devcontainer-configurations}`, replaced by the configuration table; + - `[]{.version-range}`, replaced by "`` … ``"; + - `[]{.version-latest}`, replaced by the highest version. + +Both spans accept a `suffix` attribute, appended inside the code spans, so +`[]{.version-range suffix="-noble"}` renders "`1.0-noble` … `1.10-noble`". +]] + +local IMAGE = "`quarto-codespaces:latest`" + +--- Compare two dotted version strings numerically, so 1.10 sorts after 1.9. +local function version_less_than(a, b) + local a_major, a_minor = a:match("^(%d+)%.(%d+)$") + local b_major, b_minor = b:match("^(%d+)%.(%d+)$") + if tonumber(a_major) ~= tonumber(b_major) then + return tonumber(a_major) < tonumber(b_major) + end + return tonumber(a_minor) < tonumber(b_minor) +end + +--- Locate `.devcontainer/`. Pandoc runs with the input file's directory as the +--- working directory, so search from the project root and then walk upwards. +local function devcontainer_directory() + local candidates = {} + local project = os.getenv("QUARTO_PROJECT_DIR") + if project then + table.insert(candidates, project .. "/../.devcontainer") + table.insert(candidates, project .. "/.devcontainer") + end + local prefix = "" + for _ = 1, 4 do + table.insert(candidates, prefix .. ".devcontainer") + prefix = prefix .. "../" + end + + for _, candidate in ipairs(candidates) do + local ok, entries = pcall(pandoc.system.list_directory, candidate) + if ok and entries then + return candidate, entries + end + end + return nil, nil +end + +--- Version-pinned configurations, sorted from newest to oldest. +local function pinned_versions() + local _, entries = devcontainer_directory() + if not entries then + quarto.log.warning("[devcontainer-configurations] .devcontainer/ not found; leaving the placeholder in place") + return {} + end + + local versions = {} + for _, entry in ipairs(entries) do + local version = entry:match("^quarto%-(%d+%.%d+)$") + if version then + table.insert(versions, version) + end + end + table.sort(versions, function(a, b) + return version_less_than(b, a) + end) + return versions +end + +local function row(path, name, image, quarto_version) + return string.format("| `%s` | %s | %s | %s |", path, name, image, quarto_version) +end + +local function configuration_table() + local versions = pinned_versions() + if #versions == 0 then + return nil + end + + local rows = { + "| Path | Name | Base | Quarto |", + "| --- | --- | --- | --- |", + row(".devcontainer/devcontainer.json", "Release", IMAGE, "`release`"), + row(".devcontainer/quarto-prerelease/devcontainer.json", "Pre-release", IMAGE, "`prerelease`"), + } + for _, version in ipairs(versions) do + table.insert( + rows, + row( + string.format(".devcontainer/quarto-%s/devcontainer.json", version), + version, + IMAGE, + string.format("`%s`", version) + ) + ) + end + table.insert(rows, row(".devcontainer/universal/devcontainer.json", "Universal", "`devcontainers/universal:latest`", "`release`")) + table.insert(rows, row(".github/.devcontainer/devcontainer.json", "Build recipe", "`buildpack-deps:noble-curl`", "build argument")) + + return pandoc.read(table.concat(rows, "\n"), "markdown").blocks +end + +function Div(div) + if div.identifier ~= "devcontainer-configurations" then + return nil + end + local blocks = configuration_table() + if not blocks then + return nil + end + div.identifier = "" + div.content = blocks + return div +end + +function Span(span) + local is_range = span.classes:includes("version-range") + local is_latest = span.classes:includes("version-latest") + if not (is_range or is_latest) then + return nil + end + + local versions = pinned_versions() + if #versions == 0 then + return nil + end + + local suffix = span.attributes["suffix"] or "" + if is_latest then + return pandoc.Code(versions[1] .. suffix) + end + return pandoc.Inlines({ + pandoc.Code(versions[#versions] .. suffix), + pandoc.Str(" … "), + pandoc.Code(versions[1] .. suffix), + }) +end diff --git a/docs/assets/stylesheets/theme.scss b/docs/assets/stylesheets/theme.scss index fba8bf7..a7d978f 100644 --- a/docs/assets/stylesheets/theme.scss +++ b/docs/assets/stylesheets/theme.scss @@ -1,112 +1,104 @@ /*-- scss:rules --*/ -// Pin the dark navbar and footer surfaces to the midnight/amber palette from -// _brand.yml. Atelier defaults these tokens to a neutral slate and bridges them -// onto the gitlink widget, so overriding them here is enough for both bars. +// Pin the dark navbar and footer surfaces to the palette from _brand.yml. +// Atelier defaults these tokens to a neutral slate and bridges them onto the +// gitlink widget, so overriding them here is enough for both bars. The values +// come from the brand palette rather than repeated literals, so the site has a +// single source of truth for its colours. :root { - --atelier-navbar-bg: #0b1220; - --atelier-navbar-surface: #111c2e; - --atelier-navbar-fg: #dce3ec; - --atelier-navbar-muted: #8ea0b8; - --atelier-navbar-accent: #e8a33d; - --atelier-navbar-accent-soft: rgba(232, 163, 61, 0.16); - --atelier-navbar-border: rgba(220, 227, 236, 0.14); + --atelier-navbar-bg: #{$brand-midnight}; + --atelier-navbar-surface: #{$brand-midnight-2}; + --atelier-navbar-fg: #{$brand-mist}; + --atelier-navbar-muted: #{$brand-mist-muted}; + --atelier-navbar-accent: #{$brand-amber}; + --atelier-navbar-accent-soft: #{rgba($brand-amber, 0.16)}; + --atelier-navbar-border: #{rgba($brand-mist, 0.14)}; // Space Grotesk is geometric and reads better with negative tracking than // the positive default tuned for the fallback stack. --atelier-heading-letter-spacing: -0.012em; } +// =========================================================================== +// SHARED SURFACES +// =========================================================================== +// The hero, the section cards, and the Codespaces entry points are all raised +// panels over the page background; body-mix() keeps them correct in both +// colour schemes. +%codespaces-surface { + padding: 1.1rem 1.25rem; + border: 1px solid body-mix(85%); + border-radius: $border-radius; + background: body-mix(97%); + + p:last-child { + margin-bottom: 0; + } +} + +// Small uppercase monospace lead-in, used above the hero and each entry point. +%codespaces-eyebrow { + font-family: var(--bs-font-monospace); + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.05em; + text-transform: uppercase; + color: $primary; +} + // =========================================================================== // HOME HERO // =========================================================================== .codespaces-hero { + @extend %codespaces-surface; + margin-block: 0 2.5rem; padding: 2rem 2rem 1.75rem; - border: 1px solid body-mix(80%); - border-radius: $border-radius-lg; background: - radial-gradient(120% 140% at 100% 0%, rgba(232, 163, 61, 0.14), transparent 60%), + radial-gradient(120% 140% at 100% 0%, rgba($brand-amber, 0.14), transparent 60%), body-mix(96%); - p { + > p { max-width: 60ch; font-size: 1.05rem; } - - p:last-child { - margin-bottom: 0; - } } .codespaces-hero-eyebrow { + @extend %codespaces-eyebrow; + display: inline-block; margin-bottom: 0.75rem; padding: 0.2rem 0.6rem; - border: 1px solid rgba(232, 163, 61, 0.4); + border: 1px solid rgba($brand-amber, 0.4); border-radius: 2rem; - background: rgba(232, 163, 61, 0.1); - color: $primary; - font-family: var(--bs-font-monospace); - font-size: 0.75rem; - letter-spacing: 0.06em; - text-transform: uppercase; + background: rgba($brand-amber, 0.1); } -// The two Codespaces badges are identical images, so each one needs its own -// label and caption to be distinguishable; lay them out as side-by-side cards. +// =========================================================================== +// CARD AND ACTION GRIDS +// =========================================================================== +.codespaces-grid, .codespaces-actions { display: grid; - grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: 1rem; - margin-block: 1.5rem 0; -} - -.codespaces-action { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 0.5rem; - padding: 1rem 1.1rem; - border: 1px solid body-mix(85%); - border-radius: $border-radius; - background: body-mix(98%); - - p { - margin: 0; - font-size: 0.9rem; - color: body-mix(35%); - } - - img { - display: block; - } -} - -.codespaces-action-label { - font-family: var(--bs-font-monospace); - font-size: 0.75rem; - font-weight: 600; - letter-spacing: 0.04em; - text-transform: uppercase; - color: $primary; + margin-block: 1.5rem; } -// =========================================================================== -// CARD GRID (section landing pages) -// =========================================================================== .codespaces-grid { - display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); - gap: 1rem; - margin-block: 1.5rem; +} + +// The two Codespaces badges are identical images, so each one needs its own +// label and caption to be distinguishable. +.codespaces-actions { + grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); + margin-bottom: 0; } .codespaces-card { - padding: 1.1rem 1.25rem; - border: 1px solid body-mix(85%); - border-radius: $border-radius; - background: body-mix(97%); + @extend %codespaces-surface; + transition: border-color 0.15s ease-in-out; &:hover, @@ -118,12 +110,31 @@ margin-top: 0; font-size: 1.05rem; } +} - p:last-child { - margin-bottom: 0; +.codespaces-action { + @extend %codespaces-surface; + + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + + p { + margin: 0; + font-size: 0.9rem; + color: body-mix(35%); + } + + img { + display: block; } } +.codespaces-action-label { + @extend %codespaces-eyebrow; +} + // =========================================================================== // TAG AND OPTION TABLES // =========================================================================== diff --git a/docs/getting-started/codespaces.qmd b/docs/getting-started/codespaces.qmd index cd6a439..c78680f 100644 --- a/docs/getting-started/codespaces.qmd +++ b/docs/getting-started/codespaces.qmd @@ -17,7 +17,7 @@ The default configuration is `.devcontainer/devcontainer.json`, which pulls the ## Choose a different configuration -The repository provides configurations for the universal base image, the Quarto prerelease, and each Quarto minor version from `1.0` to `1.9`. +The repository provides configurations for the universal base image, the Quarto prerelease, and every Quarto minor version, []{.version-range}. They are listed in full on the [configurations](../reference/configurations.qmd) page. ### From a URL diff --git a/docs/reference/configurations.qmd b/docs/reference/configurations.qmd index 0eafe2b..8f5f4a5 100644 --- a/docs/reference/configurations.qmd +++ b/docs/reference/configurations.qmd @@ -8,24 +8,9 @@ subtitle: "Every `devcontainer.json` in the repository, and how to write your ow Codespaces uses `.devcontainer/devcontainer.json` unless you select another path, either through the `devcontainer_path` URL parameter or the creation dialog. See [GitHub Codespaces](../getting-started/codespaces.qmd) for both routes. -::: {.codespaces-tags} - -| Path | Name | Base | Quarto | -| --- | --- | --- | --- | -| `.devcontainer/devcontainer.json` | Release | `quarto-codespaces:latest` | `release` | -| `.devcontainer/quarto-prerelease/devcontainer.json` | Pre-release | `quarto-codespaces:latest` | `prerelease` | -| `.devcontainer/quarto-1.9/devcontainer.json` | 1.9 | `quarto-codespaces:latest` | `1.9` | -| `.devcontainer/quarto-1.8/devcontainer.json` | 1.8 | `quarto-codespaces:latest` | `1.8` | -| `.devcontainer/quarto-1.7/devcontainer.json` | 1.7 | `quarto-codespaces:latest` | `1.7` | -| `.devcontainer/quarto-1.6/devcontainer.json` | 1.6 | `quarto-codespaces:latest` | `1.6` | -| `.devcontainer/quarto-1.5/devcontainer.json` | 1.5 | `quarto-codespaces:latest` | `1.5` | -| `.devcontainer/quarto-1.4/devcontainer.json` | 1.4 | `quarto-codespaces:latest` | `1.4` | -| `.devcontainer/quarto-1.3/devcontainer.json` | 1.3 | `quarto-codespaces:latest` | `1.3` | -| `.devcontainer/quarto-1.2/devcontainer.json` | 1.2 | `quarto-codespaces:latest` | `1.2` | -| `.devcontainer/quarto-1.1/devcontainer.json` | 1.1 | `quarto-codespaces:latest` | `1.1` | -| `.devcontainer/quarto-1.0/devcontainer.json` | 1.0 | `quarto-codespaces:latest` | `1.0` | -| `.devcontainer/universal/devcontainer.json` | Universal | `devcontainers/universal:latest` | `release` | -| `.github/.devcontainer/devcontainer.json` | Build recipe | `buildpack-deps:noble-curl` | build argument | +::: {#devcontainer-configurations .codespaces-tags} + +This table is generated from `.devcontainer/` when the site is rendered. ::: diff --git a/docs/reference/images.qmd b/docs/reference/images.qmd index 73b6620..de53bed 100644 --- a/docs/reference/images.qmd +++ b/docs/reference/images.qmd @@ -18,8 +18,8 @@ docker pull ghcr.io/mcanouil/quarto-codespaces:latest | `latest` | stable release | `amd64`, `arm64` | | `release`, `release-noble` | stable release | `amd64`, `arm64` | | `prerelease`, `prerelease-noble` | prerelease | `amd64`, `arm64` | -| `1.0` … `1.9` | that minor version | `amd64` | -| `1.0-noble` … `1.9-noble` | that minor version | `amd64` | +| []{.version-range} | that minor version | `amd64` | +| []{.version-range suffix="-noble"} | that minor version | `amd64` | | `release-`, `prerelease-`, `-` | as above, pinned | as above | ::: @@ -44,7 +44,7 @@ Use `latest` to always get the current stable toolchain, and a build-metadata ta `release` and `prerelease` are built on native runners, `ubuntu-latest` for `linux/amd64` and `ubuntu-24.04-arm` for `linux/arm64`, then combined into a multi-architecture manifest. Docker picks the right one automatically, so Apple Silicon and ARM servers run natively. -The version-pinned images (`1.0` through `1.9`) are `linux/amd64` only. +The version-pinned images ([]{.version-range}) are `linux/amd64` only. They still run on ARM hosts through emulation, more slowly. ## Build schedule