You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by ja...@apache.org on 2026/05/03 11:58:45 UTC
(grails-static-website) 05/09: Add SiteMap multi-major helpers for the multi-version page redesign
This is an automated email from the ASF dual-hosted git repository.
jamesfredley pushed a commit to branch multi-version-support
in repository https://gitbox.apache.org/repos/asf/grails-static-website.git
commit 794b1605123e7573e8b05c4e457899a88c23614c
Author: James Fredley <ja...@users.noreply.github.com>
AuthorDate: Sat May 2 22:13:29 2026 -0400
Add SiteMap multi-major helpers for the multi-version page redesign
Adds four query methods that drive the upcoming multi-major rendering changes on the home, downloads, and documentation pages:
highestStablePerMajor(File): the highest stable release per major - the cross-major reference point used to decide which pre-releases are still relevant.
latestStablePerMinorLine(File): one entry per X.Y minor line, pinned to the highest patch on that line. Drives one card per minor line on the redesigned downloads page.
latestPreReleasePerMajor(File): the latest M/RC per major, suppressed once a stable at or above it has shipped. Apache release policy still requires the milestone/RC artefacts to be linked, so this map drives that section. New-major M/RCs surface even when their major has no stable yet.
activeMinorLines(File): which X.Y lines should each get a card. Always includes every minor line of the highest stable major; if that major has fewer than two minor lines, also includes the previous major's lines so the page never shrinks to a single card right after a new major ships its first minor. Once the new major grows a second minor line, the previous major's lines roll off automatically.
13 SiteMapSpec scenarios cover the today / 8.0.0 ship-day / 8.1.0 ship-day transitions, pre-release suppression, brand-new-major pre-releases, companion artefact lookup, and legacy 'releases:' key compatibility.
No callers consume these helpers yet - the next commit wires them into the downloads page redesign.
Assisted-by: claude-code:claude-opus-4-7
---
.../website/model/documentation/SiteMap.groovy | 107 ++++++++
.../website/model/documentation/SiteMapSpec.groovy | 288 +++++++++++++++++++++
2 files changed, 395 insertions(+)
diff --git a/buildSrc/src/main/groovy/website/model/documentation/SiteMap.groovy b/buildSrc/src/main/groovy/website/model/documentation/SiteMap.groovy
index 12c687f4eb3..bbe08a6cf62 100644
--- a/buildSrc/src/main/groovy/website/model/documentation/SiteMap.groovy
+++ b/buildSrc/src/main/groovy/website/model/documentation/SiteMap.groovy
@@ -95,4 +95,111 @@ class SiteMap {
)
}
}
+
+ /**
+ * @return the highest stable {@link ReleaseVersion} per major version,
+ * keyed by major. Used as the cross-major reference point for
+ * deciding which pre-releases are still relevant (a pre-release
+ * is suppressed once a stable release at or above it has shipped).
+ */
+ static Map<Integer, ReleaseVersion> highestStablePerMajor(File releases) {
+ Map<Integer, ReleaseVersion> result = new LinkedHashMap<Integer, ReleaseVersion>()
+ stableVersions(releases).each { ReleaseVersion v ->
+ // stableVersions is sorted descending, so the first encounter per
+ // major is automatically the highest one for that major.
+ if (!result.containsKey(v.major)) {
+ result[v.major] = v
+ }
+ }
+ result
+ }
+
+ /**
+ * @return the highest stable {@link ReleaseVersion} per minor line,
+ * keyed by the {@code "X.Y"} string. Today (with 7.0.0..7.0.10
+ * and 7.1.0 published) this returns
+ * {@code ["7.0": 7.0.10, "7.1": 7.1.0]}. The redesigned downloads
+ * page renders one card per entry, oldest line on the right.
+ */
+ static Map<String, ReleaseVersion> latestStablePerMinorLine(File releases) {
+ Map<String, ReleaseVersion> result = new LinkedHashMap<String, ReleaseVersion>()
+ stableVersions(releases).each { ReleaseVersion v ->
+ String key = "${v.major}.${v.minor}".toString()
+ if (!result.containsKey(key)) {
+ result[key] = v
+ }
+ }
+ result
+ }
+
+ /**
+ * @return the latest pre-release {@link ReleaseVersion} per major, but only
+ * when that pre-release is still relevant (no stable at or above
+ * it has been published). Today this typically returns at most one
+ * entry (the upcoming next-minor RC). Once stable ships, the entry
+ * drops automatically. Apache release policy still requires
+ * /download.html and /documentation.html to link to the released
+ * milestone/RC artefacts - this map drives that section.
+ */
+ static Map<Integer, ReleaseVersion> latestPreReleasePerMajor(File releases) {
+ Map<Integer, ReleaseVersion> highestStables = highestStablePerMajor(releases)
+ Map<Integer, ReleaseVersion> result = new LinkedHashMap<Integer, ReleaseVersion>()
+ preReleaseVersions(releases).each { ReleaseVersion v ->
+ if (result.containsKey(v.major)) {
+ return
+ }
+ ReleaseVersion stableForMajor = highestStables[v.major]
+ if (stableForMajor == null || v > stableForMajor) {
+ result[v.major] = v
+ }
+ }
+ result
+ }
+
+ /**
+ * Computes which {@code "X.Y"} minor lines should each get their own card
+ * on the home page hero, the downloads page, and the documentation page.
+ *
+ * <p>Rule (chosen so the slot count adapts to the release cadence rather
+ * than a static config):
+ * <ul>
+ * <li>Always include every minor line of the highest major that has at
+ * least one stable release. So today, with the highest stable major
+ * being 7 and minor lines 7.0 and 7.1 both published, the result is
+ * {@code ["7.1", "7.0"]} - two slots.</li>
+ * <li>If that highest major has fewer than two minor lines (e.g. just
+ * after 8.0.0 ships), also include every minor line of the previous
+ * major so we don't drop from two cards to one. Once the new major
+ * grows a second minor line of its own (8.1.0), the previous major's
+ * lines roll off automatically.</li>
+ * </ul>
+ *
+ * <p>Returns lines in descending version order
+ * ({@code ["8.0", "7.1", "7.0"]} after 8.0.0 ships).
+ */
+ static List<String> activeMinorLines(File releases) {
+ Map<String, ReleaseVersion> latestPerLine = latestStablePerMinorLine(releases)
+ if (latestPerLine.isEmpty()) {
+ return Collections.<String> emptyList()
+ }
+ int highestMajor = latestPerLine.values()*.major.max() as int
+ List<String> latestMajorLines = latestPerLine.keySet()
+ .findAll { it.startsWith("${highestMajor}.") }
+ .toSorted { a, b -> b <=> a }
+ if (latestMajorLines.size() >= 2) {
+ return latestMajorLines
+ }
+ // Highest major has 0 or 1 minor line; pull in the previous major so
+ // the page doesn't shrink to a single card right after a new major
+ // ships its first minor.
+ List<Integer> majorsWithStables = latestPerLine.values()*.major.unique().toSorted().reverse()
+ if (majorsWithStables.size() < 2) {
+ return latestMajorLines
+ }
+ int previousMajor = majorsWithStables[1]
+ List<String> previousMajorLines = latestPerLine.keySet()
+ .findAll { it.startsWith("${previousMajor}.") }
+ .toSorted { a, b -> b <=> a }
+ return latestMajorLines + previousMajorLines
+ }
}
diff --git a/buildSrc/src/test/groovy/website/model/documentation/SiteMapSpec.groovy b/buildSrc/src/test/groovy/website/model/documentation/SiteMapSpec.groovy
new file mode 100644
index 00000000000..8e396860c05
--- /dev/null
+++ b/buildSrc/src/test/groovy/website/model/documentation/SiteMapSpec.groovy
@@ -0,0 +1,288 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package website.model.documentation
+
+import spock.lang.Specification
+import spock.lang.TempDir
+
+import java.nio.file.Path
+
+/**
+ * Verifies the multi-major helpers introduced for the multi-version downloads
+ * /documentation page redesign: latestStablePerMinorLine, latestPreReleasePerMajor,
+ * highestStablePerMajor, and activeMinorLines.
+ */
+class SiteMapSpec extends Specification {
+
+ @TempDir
+ Path tempDir
+
+ private File releasesFile(String yaml) {
+ File f = tempDir.resolve('releases.yml').toFile()
+ f.text = yaml
+ f
+ }
+
+ void 'latestStablePerMinorLine groups by major.minor and picks highest patch per line'() {
+
+ given:
+ File releases = releasesFile('''
+coreReleases:
+ - version: 7.0.0
+ - version: 7.0.5
+ - version: 7.0.10
+ - version: 7.1.0
+'''.stripIndent())
+
+ when:
+ Map<String, ReleaseVersion> result = SiteMap.latestStablePerMinorLine(releases)
+
+ then:
+ result.keySet() as List == ['7.1', '7.0']
+ result['7.0'].versionText == '7.0.10'
+ result['7.1'].versionText == '7.1.0'
+ }
+
+ void 'latestStablePerMinorLine excludes pre-releases'() {
+
+ given:
+ File releases = releasesFile('''
+coreReleases:
+ - version: 7.0.0
+ - version: 7.1.0-RC1
+ - version: 7.1.0
+'''.stripIndent())
+
+ when:
+ Map<String, ReleaseVersion> result = SiteMap.latestStablePerMinorLine(releases)
+
+ then:
+ result['7.1'].versionText == '7.1.0'
+ !result.values()*.versionText.any { it.contains('RC') }
+ }
+
+ void 'highestStablePerMajor returns one entry per major'() {
+
+ given:
+ File releases = releasesFile('''
+coreReleases:
+ - version: 6.2.3
+ - version: 7.0.0
+ - version: 7.0.10
+ - version: 7.1.0
+'''.stripIndent())
+
+ when:
+ Map<Integer, ReleaseVersion> result = SiteMap.highestStablePerMajor(releases)
+
+ then:
+ result.keySet() as List == [7, 6]
+ result[7].versionText == '7.1.0'
+ result[6].versionText == '6.2.3'
+ }
+
+ void 'latestPreReleasePerMajor includes a pre-release that has not been superseded by stable'() {
+
+ given:
+ File releases = releasesFile('''
+coreReleases:
+ - version: 7.0.0
+ - version: 7.0.10
+ - version: 7.1.0-RC1
+'''.stripIndent())
+
+ when:
+ Map<Integer, ReleaseVersion> result = SiteMap.latestPreReleasePerMajor(releases)
+
+ then:
+ result.containsKey(7)
+ result[7].versionText == '7.1.0-RC1'
+ }
+
+ void 'latestPreReleasePerMajor suppresses a pre-release once stable has shipped at or above it'() {
+
+ given:
+ File releases = releasesFile('''
+coreReleases:
+ - version: 7.1.0-RC1
+ - version: 7.1.0
+'''.stripIndent())
+
+ when:
+ Map<Integer, ReleaseVersion> result = SiteMap.latestPreReleasePerMajor(releases)
+
+ then:
+ !result.containsKey(7)
+ }
+
+ void 'latestPreReleasePerMajor exposes a brand new majors first M/RC even when no stable exists for that major'() {
+
+ given:
+ File releases = releasesFile('''
+coreReleases:
+ - version: 7.1.0
+ - version: 8.0.0-M1
+'''.stripIndent())
+
+ when:
+ Map<Integer, ReleaseVersion> result = SiteMap.latestPreReleasePerMajor(releases)
+
+ then:
+ result.containsKey(8)
+ result[8].versionText == '8.0.0-M1'
+ }
+
+ void 'activeMinorLines today (highest major has 2 minor lines) returns just those lines'() {
+
+ given: 'today: 7.0.x line plus 7.1.x line shipped, no 8.x stable yet'
+ File releases = releasesFile('''
+coreReleases:
+ - version: 6.2.3
+ - version: 7.0.0
+ - version: 7.0.10
+ - version: 7.1.0
+'''.stripIndent())
+
+ when:
+ List<String> lines = SiteMap.activeMinorLines(releases)
+
+ then:
+ lines == ['7.1', '7.0']
+ }
+
+ void 'activeMinorLines after 8.0.0 ships pulls in the previous majors lines so we keep at least two cards'() {
+
+ given:
+ File releases = releasesFile('''
+coreReleases:
+ - version: 7.0.10
+ - version: 7.1.0
+ - version: 8.0.0
+'''.stripIndent())
+
+ when:
+ List<String> lines = SiteMap.activeMinorLines(releases)
+
+ then:
+ lines == ['8.0', '7.1', '7.0']
+ }
+
+ void 'activeMinorLines drops the previous majors lines once the new major grows a second minor'() {
+
+ given:
+ File releases = releasesFile('''
+coreReleases:
+ - version: 7.0.10
+ - version: 7.1.0
+ - version: 8.0.0
+ - version: 8.1.0
+'''.stripIndent())
+
+ when:
+ List<String> lines = SiteMap.activeMinorLines(releases)
+
+ then:
+ lines == ['8.1', '8.0']
+ }
+
+ void 'activeMinorLines returns an empty list when no stable releases exist'() {
+
+ given:
+ File releases = releasesFile('''
+coreReleases:
+ - version: 7.0.0-M1
+ - version: 7.0.0-RC1
+'''.stripIndent())
+
+ when:
+ List<String> lines = SiteMap.activeMinorLines(releases)
+
+ then:
+ lines == []
+ }
+
+ void 'companionArtifactsFor returns an empty list for a major with no entry'() {
+
+ given:
+ File releases = releasesFile('''
+coreReleases:
+ - version: 7.0.0
+companionArtifacts:
+ '7':
+ - artifactId: grails-redis
+ version: '5.0.1'
+ mirrorDirectory: redis
+ releaseNotesRepo: apache/grails-redis
+ displayName: Grails Redis Plugin
+'''.stripIndent())
+
+ expect:
+ SiteMap.companionArtifactsFor(releases, 8) == []
+ SiteMap.companionArtifactsFor(releases, 6) == []
+ }
+
+ void 'companionArtifactsFor returns the parsed CompanionArtifact list for a populated major'() {
+
+ given:
+ File releases = releasesFile('''
+coreReleases:
+ - version: 7.0.0
+companionArtifacts:
+ '7':
+ - artifactId: grails-redis
+ version: '5.0.1'
+ mirrorDirectory: redis
+ releaseNotesRepo: apache/grails-redis
+ displayName: Grails Redis Plugin
+ - artifactId: grails-quartz
+ version: '4.0.1'
+ mirrorDirectory: quartz
+ releaseNotesRepo: apache/grails-quartz
+ displayName: Grails Quartz Plugin
+'''.stripIndent())
+
+ when:
+ List<CompanionArtifact> result = SiteMap.companionArtifactsFor(releases, 7)
+
+ then:
+ result.size() == 2
+ result[0].artifactId == 'grails-redis'
+ result[0].version == '5.0.1'
+ result[0].mirrorDirectory == 'redis'
+ result[0].releaseNotesRepo == 'apache/grails-redis'
+ result[0].displayName == 'Grails Redis Plugin'
+ result[1].artifactId == 'grails-quartz'
+ }
+
+ void 'versions accepts the legacy releases: key during the migration window'() {
+
+ given:
+ File releases = releasesFile('''
+releases:
+ - version: 7.0.0
+ - version: 7.1.0
+'''.stripIndent())
+
+ when:
+ List<ReleaseVersion> result = SiteMap.versions(releases)
+
+ then:
+ result*.versionText == ['7.0.0', '7.1.0']
+ }
+}