From 6bc84f204e3dcdc3f7abf2aec01fee03295d5e82 Mon Sep 17 00:00:00 2001 From: Julien Pivotto <291750+roidelapluie@users.noreply.github.com> Date: Fri, 11 Sep 2026 15:33:46 +0200 Subject: [PATCH] Generate LTS documentation and downloads from support dates Signed-off-by: Julien Pivotto <291750+roidelapluie@users.noreply.github.com> --- README.md | 10 ++++++ docs-config.ts | 11 +++++-- docs/introduction/release-cycle.md | 9 +---- scripts/fetch-downloads-info.ts | 32 +++++++++++------- scripts/fetch-repo-docs.ts | 53 ++++++++++++++++++++++++++---- scripts/utils.test.ts | 50 ++++++++++++++++++++++++++++ scripts/utils.ts | 42 +++++++++++++++++++++++ src/app/sitemap.ts | 6 ++-- src/docs-config-types.ts | 9 ++++- 9 files changed, 191 insertions(+), 31 deletions(-) create mode 100644 scripts/utils.test.ts diff --git a/README.md b/README.md index f5275259b..b62dc4642 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,16 @@ You can configure some high-level settings for the documentation website in the * Which repositories to fetch download information from. * Information about LTS (long-term-support) versions. +`ltsVersions` lists all known LTS versions, including past and upcoming releases, +with a `releaseDate` and an inclusive `endDate` in `YYYY-MM-DD` format (UTC). +Upcoming releases can use `TBD` as the version and `YYYY-MM` as the release date. +`fetch-repo-docs` replaces `` in the release-cycle page +with a table generated from this config, writing the result under `generated/local-docs/`. +The generators treat a version as an active LTS only once a stable GitHub release exists and through its +end date. Active LTS versions are retained in the documentation even outside the +recent-version window, included in downloads, and labeled as LTS. Expired versions +can still appear under the usual recent/latest-version rules, without an LTS label. + ## Automatic Deployment This site is automatically deployed using [Netlify](https://www.netlify.com/). diff --git a/docs-config.ts b/docs-config.ts index a1286c915..7072158cb 100644 --- a/docs-config.ts +++ b/docs-config.ts @@ -55,9 +55,16 @@ export default { }, ], - // Long-term support versions configuration. + // All long-term support versions and their inclusive end-of-support dates. ltsVersions: { - prometheus: ["3.13"], + prometheus: [ + { version: "2.37", releaseDate: "2022-07-14", endDate: "2023-07-31" }, + { version: "2.45", releaseDate: "2023-06-23", endDate: "2024-07-31" }, + { version: "2.53", releaseDate: "2024-06-16", endDate: "2025-07-31" }, + { version: "3.5", releaseDate: "2025-07-14", endDate: "2026-07-31" }, + { version: "3.13", releaseDate: "2026-07-01", endDate: "2027-07-31" }, + { version: "TBD", releaseDate: "2027-06", endDate: "2028-07-31" }, + ], }, // Repositories for the downloads page. The order in this file is the diff --git a/docs/introduction/release-cycle.md b/docs/introduction/release-cycle.md index 0f850d0ce..04a54414f 100644 --- a/docs/introduction/release-cycle.md +++ b/docs/introduction/release-cycle.md @@ -23,14 +23,7 @@ having a Prometheus server maintained by the community. ## List of LTS releases -| Release | Date | End of support | Status | -| ------------------- | -------------- | -------------- | ------------- | -| Prometheus 2.37 | 2022-07-14 | 2023-07-31 | End of life | -| Prometheus 2.45 | 2023-06-23 | 2024-07-31 | End of life | -| Prometheus 2.53 | 2024-06-16 | 2025-07-31 | End of life | -| Prometheus 3.5 | 2025-07-14 | 2026-07-31 | End of life | -| **Prometheus 3.13** | **2026-07-01** | **2027-07-31** | **Supported** | -| TBD | 2027-06 | 2028-07-31 | Upcoming | + ## Limitations of LTS support diff --git a/scripts/fetch-downloads-info.ts b/scripts/fetch-downloads-info.ts index 4a5f2afb0..2bc2bb504 100644 --- a/scripts/fetch-downloads-info.ts +++ b/scripts/fetch-downloads-info.ts @@ -2,6 +2,7 @@ import { octokit } from "./githubClient"; import { GetResponseDataTypeFromEndpointMethod } from "@octokit/types"; import * as fs from "fs"; import * as path from "path"; +import { valid } from "semver"; import docsConfig from "../docs-config"; import { Downloads, Release, Binary } from "@/downloads-metadata-types"; import { @@ -9,7 +10,12 @@ import { DownloadRelease, DownloadFile, } from "@/download-json-types"; -import { compareFullVersion, filterUnique, majorMinor } from "./utils"; +import { + compareFullVersion, + filterUnique, + getActiveLTSVersions, + majorMinor, +} from "./utils"; const OUTDIR = "./generated"; @@ -72,16 +78,24 @@ for (const repoName of docsConfig.downloads.repos) { repo: repoName, }); - // Fetch releases information for the repo. + // Fetch all release pages so older supported LTS versions are included. console.log(`Fetching releases info for ${repoName}`); const releases = ( - await octokit.rest.repos.listReleases({ + await octokit.paginate(octokit.rest.repos.listReleases, { owner: docsConfig.downloads.owner, repo: repoName, + per_page: 100, }) - ).data; + ).filter((r) => !r.draft && valid(r.tag_name)); releases.sort((a, b) => compareFullVersion(a.tag_name, b.tag_name)).reverse(); + const ltsVersions = getActiveLTSVersions( + docsConfig.ltsVersions, + repoName, + releases + .filter((r) => !r.prerelease && !r.tag_name.includes("-")) + .map((r) => majorMinor(r.tag_name)) + ); // Select the relevant stable, pre-release, and LTS versions to show. const preReleases: string[] = []; @@ -103,7 +117,7 @@ for (const repoName of docsConfig.downloads.repos) { preReleases.push(version); } } else if ( - docsConfig.ltsVersions[repoName]?.includes(version) && + ltsVersions.includes(version) && !stableReleases.includes(version) ) { shownReleases.push(r); @@ -153,9 +167,7 @@ for (const repoName of docsConfig.downloads.repos) { name: r.name || "", url: r.html_url, prerelease: r.prerelease, - ltsRelease: docsConfig.ltsVersions[repoName]?.includes( - majorMinor(r.tag_name) - ), + ltsRelease: !r.prerelease && ltsVersions.includes(majorMinor(r.tag_name)), majorMinor: majorMinor(r.tag_name), binaries: getBinaries(r), }) @@ -172,9 +184,7 @@ for (const repoName of docsConfig.downloads.repos) { version: r.tag_name, stable: !r.prerelease, latest: r.id === latestStableID, - lts: - docsConfig.ltsVersions[repoName]?.includes(majorMinor(r.tag_name)) ?? - false, + lts: !r.prerelease && ltsVersions.includes(majorMinor(r.tag_name)), files: getBinaries(r).map( (b): DownloadFile => ({ url: b.url, diff --git a/scripts/fetch-repo-docs.ts b/scripts/fetch-repo-docs.ts index b8e98c2b6..77779bd7b 100644 --- a/scripts/fetch-repo-docs.ts +++ b/scripts/fetch-repo-docs.ts @@ -13,7 +13,14 @@ import { } from "@/docs-collection-types"; import matter from "gray-matter"; import { octokit } from "./githubClient"; -import { compareFullVersion, filterUnique, majorMinor } from "./utils"; +import { + compareFullVersion, + compareMajorMinor, + filterUnique, + generateLTSTable, + getActiveLTSVersions, + majorMinor, +} from "./utils"; const OUTDIR = "./generated"; @@ -111,15 +118,21 @@ const fetchRepoDocs = async ({ }); const allReleaseTags: string[] = []; + const stableVersions: string[] = []; for await (const { data: releases } of iterator) { // Skip ancient releases. Some have non-semver compatible strings and the // version comparison below breaks. Also note the lack of `v` in the tag. - const validReleases = (repo === "prometheus") - ? releases.filter((r) => !r.tag_name.startsWith("0.")) - : releases; + const validReleases = releases.filter( + (r) => !r.draft && !(repo === "prometheus" && r.tag_name.startsWith("0.")) + ); allReleaseTags.push(...validReleases.map((r) => r.tag_name)); + stableVersions.push( + ...validReleases + .filter((r) => !r.prerelease && !r.tag_name.includes("-")) + .map((r) => majorMinor(r.tag_name)) + ); // For the Prometheus repo for efficieny-reasons, stop once we have found at least // one release starting with "v1.". // TODO: Do we even still want to show the latest v1 release? @@ -156,6 +169,18 @@ const fetchRepoDocs = async ({ } } + // Keep supported LTS docs even when they fall outside the recent versions. + const ltsVersions = + owner === "prometheus" + ? getActiveLTSVersions(docsConfig.ltsVersions, repo, stableVersions) + : []; + for (const version of ltsVersions) { + if (!recentVersions.includes(version)) { + recentVersions.push(version); + } + } + recentVersions.sort(compareMajorMinor).reverse(); + const latestTag = allReleaseTags.find((tag) => !tag.includes("-")); if (!latestTag) { throw new Error(`No latest version found for ${owner}/${repo}.`); @@ -169,7 +194,7 @@ const fetchRepoDocs = async ({ allRepoVersions[owner][repo] = { versions: recentVersions, latestVersion, - ltsVersions: (owner === "prometheus" && docsConfig.ltsVersions[repo]) || [], + ltsVersions, }; console.log( @@ -290,6 +315,20 @@ for (const sourceConfig of docsConfig.localMarkdownSources) { } const filePath = path.relative(docsDir, file); + let content = fs.readFileSync(file, "utf-8"); + let outputFile = file; + if (content.includes("")) { + content = content.replace( + "", + generateLTSTable( + docsConfig.ltsVersions.prometheus, + allRepoVersions.prometheus.prometheus.ltsVersions + ) + ); + outputFile = path.join(OUTDIR, "local-docs", docsDir, filePath); + fs.mkdirSync(path.dirname(outputFile), { recursive: true }); + fs.writeFileSync(outputFile, content); + } const { data: { title, @@ -298,7 +337,7 @@ for (const sourceConfig of docsConfig.localMarkdownSources) { nav_icon: navIcon, hide_in_nav: hideInNav, }, - } = matter(fs.readFileSync(file, "utf-8")); + } = matter(content); if (!title) { throw new Error(`Missing title in ${file}`); } @@ -319,7 +358,7 @@ for (const sourceConfig of docsConfig.localMarkdownSources) { docsCollection[slug] = { type: "local-doc", slug, - filePath: file, + filePath: outputFile, title, navTitle, sortRank: sortRank ?? 0, diff --git a/scripts/utils.test.ts b/scripts/utils.test.ts new file mode 100644 index 000000000..0bcafdf68 --- /dev/null +++ b/scripts/utils.test.ts @@ -0,0 +1,50 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { generateLTSTable, getActiveLTSVersions } from "./utils"; + +const config = { + prometheus: [ + { version: "2.53", releaseDate: "2024-06-16", endDate: "2025-07-31" }, + { version: "3.5", releaseDate: "2025-07-14", endDate: "2026-07-31" }, + { version: "3.13", releaseDate: "2026-07-01", endDate: "2027-07-31" }, + ], +}; + +test("LTS support includes the entire end date in UTC", () => { + const stableVersions = ["2.53", "3.5", "3.13"]; + assert.deepEqual( + getActiveLTSVersions(config, "prometheus", stableVersions, new Date("2026-07-31T23:59:59.999Z")), + ["3.5", "3.13"] + ); + assert.deepEqual( + getActiveLTSVersions(config, "prometheus", stableVersions, new Date("2026-08-01T00:00:00Z")), + ["3.13"] + ); +}); + +test("upcoming LTS versions require a stable release", () => { + assert.deepEqual( + getActiveLTSVersions(config, "prometheus", ["2.53", "3.5"], new Date("2026-06-01T00:00:00Z")), + ["3.5"] + ); + assert.deepEqual( + getActiveLTSVersions(config, "prometheus", [], new Date("2026-06-01T00:00:00Z")), + [] + ); +}); + +test("repositories without LTS configuration have no active LTS versions", () => { + assert.deepEqual(getActiveLTSVersions(config, "alertmanager", ["0.31"]), []); +}); + +test("the LTS table includes expired, supported, and upcoming releases", () => { + const table = generateLTSTable( + [...config.prometheus, { version: "TBD", releaseDate: "2027-06", endDate: "2028-07-31" }], + ["3.5"], + new Date("2026-06-01T00:00:00Z") + ); + assert.match(table, /\| Prometheus 2\.53 \| 2024-06-16 \| 2025-07-31 \| End of life \|/); + assert.match(table, /\| \*\*Prometheus 3\.5\*\* \| \*\*2025-07-14\*\* \| \*\*2026-07-31\*\* \| \*\*Supported\*\* \|/); + assert.match(table, /\| Prometheus 3\.13 \| 2026-07-01 \| 2027-07-31 \| Upcoming \|/); + assert.match(table, /\| TBD \| 2027-06 \| 2028-07-31 \| Upcoming \|/); +}); diff --git a/scripts/utils.ts b/scripts/utils.ts index 763c8c231..0eead3305 100644 --- a/scripts/utils.ts +++ b/scripts/utils.ts @@ -1,4 +1,46 @@ import { compare } from "semver"; +import { LTSConfig } from "../src/docs-config-types"; + +// Only stable, published versions still within their support period are active LTS. +export const getActiveLTSVersions = ( + config: LTSConfig, + repo: string, + stableVersions: string[], + now = new Date() +): string[] => { + const today = now.toISOString().slice(0, 10); + return (config[repo] ?? []) + .filter( + ({ version, endDate }) => stableVersions.includes(version) && today <= endDate + ) + .map(({ version }) => version); +}; + +export const generateLTSTable = ( + versions: LTSConfig[string], + activeVersions: string[], + now = new Date() +): string => { + const today = now.toISOString().slice(0, 10); + const rows = versions.map(({ version, releaseDate, endDate }) => { + const supported = today <= endDate && activeVersions.includes(version); + const status = today > endDate + ? "End of life" + : supported ? "Supported" : "Upcoming"; + const cells = [ + version === "TBD" ? "TBD" : `Prometheus ${version}`, + releaseDate, + endDate, + status, + ]; + return `| ${cells.map((cell) => supported ? `**${cell}**` : cell).join(" | ")} |`; + }); + return [ + "| Release | Date | End of support | Status |", + "| --- | --- | --- | --- |", + ...rows, + ].join("\n"); +}; // Takes a full Prometheus tag / version string and returns the major and minor version. // "v3.4.0-rc.0" -> "3.4" diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 3ad54bda5..780cd392f 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -1,7 +1,7 @@ import fs from "fs"; import path from "path"; import { MetadataRoute } from "next"; -import { docsCollection } from "@/docs-collection"; +import { allRepoVersions, docsCollection } from "@/docs-collection"; import { getAllPostFileNames, postFileNameToPath } from "@/blog-helpers"; import docsConfig from "../../docs-config"; @@ -44,7 +44,9 @@ export default function sitemap(): MetadataRoute.Sitemap { if (doc.version === doc.latestVersion) { return true; } - return docsConfig.ltsVersions[doc.repo]?.includes(doc.version) ?? false; + return ( + allRepoVersions[doc.owner]?.[doc.repo]?.ltsVersions.includes(doc.version) ?? false + ); }) .map((doc) => ({ url: `${base}/docs/${doc.slug}/` })); diff --git a/src/docs-config-types.ts b/src/docs-config-types.ts index 0c74f9fc7..94c94ee39 100644 --- a/src/docs-config-types.ts +++ b/src/docs-config-types.ts @@ -58,7 +58,14 @@ export type GithubSinglePageSource = { }; export type LTSConfig = { - [repo: string]: string[]; + [repo: string]: { + // Major.minor version, or TBD for an unassigned upcoming release. + version: string; + // Release date (YYYY-MM-DD), or planned month (YYYY-MM) for upcoming releases. + releaseDate: string; + // Inclusive end-of-support date in YYYY-MM-DD format (UTC). + endDate: string; + }[]; }; export type DownloadConfig = {