diff --git a/.github/scripts/__tests__/release-ready-plan.test.js b/.github/scripts/__tests__/release-ready-plan.test.js index 23d90dc..23bf4f5 100644 --- a/.github/scripts/__tests__/release-ready-plan.test.js +++ b/.github/scripts/__tests__/release-ready-plan.test.js @@ -1,6 +1,6 @@ 'use strict'; -const { buildPlan, summarize } = require('../release-ready-plan'); +const { buildPlan, summarize, isHotfixTrain } = require('../release-ready-plan'); const entries = list => list.map(([key, version]) => ({ key, version })); const branches = (...list) => (repo, branch) => list.includes(`${repo}:${branch}`); @@ -70,6 +70,33 @@ describe('buildPlan', () => { }); }); +describe('hotfix trains', () => { + const all = () => true; + const list = entries([ + ['spring-cloud-config', '5.0.6.1-SNAPSHOT'], + ['spring-cloud-commons', '5.0.4-SNAPSHOT'], + ]); + + it('recognises a 4-segment train', () => { + expect(isHotfixTrain('2025.1.4.1')).toBe(true); + expect(isHotfixTrain('2025.1.4')).toBe(false); + expect(isHotfixTrain('2026.0.0-RC1')).toBe(false); + }); + + it('plans only the hotfix versions', () => { + const { todo, skipped } = buildPlan({ + train: '2025.1.4.1', entries: list, branchExists: all, tagExists: noTags, + }); + expect(todo.map(t => t.project)).toEqual(['spring-cloud-config']); + expect(skipped).toEqual([{ project: 'spring-cloud-commons', reason: "not a hotfix version '5.0.4-SNAPSHOT'" }]); + }); + + it('plans the non-hotfix versions for a regular train', () => { + const { todo } = buildPlan({ train: '2025.1.4', entries: list, branchExists: all, tagExists: noTags }); + expect(todo).toHaveLength(2); + }); +}); + describe('summarize', () => { it('lists both sections', () => { const out = summarize({ diff --git a/.github/scripts/release-ready-plan.js b/.github/scripts/release-ready-plan.js index 90ca51f..ffe9f8c 100644 --- a/.github/scripts/release-ready-plan.js +++ b/.github/scripts/release-ready-plan.js @@ -13,14 +13,23 @@ // // A project the train is not releasing still has an entry, naming its previous release. That // release is tagged, or its branch is gone, so the same two checks leave it out. +// +// A hotfix train (2025.1.4.1) is the exception. Its file also lists the projects that are not +// getting a hotfix, at the versions they have in the regular train. Those have no tag yet and +// do have a branch, so the checks above would pick them up - but the regular train's run +// marks them ready, so only the 4-segment (x.y.z.h) versions are planned for a hotfix train. const fs = require('fs'); const { fetchReleaserConfig } = require('./releaser-config'); const { NON_PROJECT_KEYS, ORG, ghBranchExists, ghTagExists } = require('./release-branch-plan'); +// A hotfix train has four segments, like the hotfix versions in it. +const isHotfixTrain = train => /^\d+\.\d+\.\d+\.\d+$/.test(String(train).trim()); + // Pure: entries in, plan out. `branchExists(repo, branch)` and `tagExists(repo, tag)` are // injected so tests need no network. -const buildPlan = ({ entries, branchExists, tagExists }) => { +const buildPlan = ({ train, entries, branchExists, tagExists }) => { + const hotfix = isHotfixTrain(train); const todo = []; const skipped = []; for (const { key, version: raw } of entries) { @@ -30,6 +39,10 @@ const buildPlan = ({ entries, branchExists, tagExists }) => { skipped.push({ project: key, reason: `unusable version '${raw}'` }); continue; } + if (hotfix && !/^\d+\.\d+\.\d+\.\d+$/.test(version)) { + skipped.push({ project: key, reason: `not a hotfix version '${raw}'` }); + continue; + } const branch = `release/${version}`; const ossRepo = `${ORG}/${key}`; const commercialRepo = `${ossRepo}-commercial`; @@ -63,7 +76,7 @@ const summarize = ({ train, file, todo, skipped }) => [ '', ].join('\n'); -module.exports = { buildPlan, summarize }; +module.exports = { buildPlan, summarize, isHotfixTrain }; // CLI: node release-ready-plan.js // @@ -77,7 +90,7 @@ if (require.main === module) { } try { const { file, entries } = fetchReleaserConfig(train.trim()); - const plan = buildPlan({ entries, branchExists: ghBranchExists, tagExists: ghTagExists }); + const plan = buildPlan({ train, entries, branchExists: ghBranchExists, tagExists: ghTagExists }); const report = summarize({ train, file, ...plan }); process.stdout.write(report + '\n'); diff --git a/.github/workflows/README-mark-release-train-ready.md b/.github/workflows/README-mark-release-train-ready.md index 3ab9148..83c5617 100644 --- a/.github/workflows/README-mark-release-train-ready.md +++ b/.github/workflows/README-mark-release-train-ready.md @@ -30,6 +30,13 @@ The properties file is read as given, with no `-INTERNAL-SNAPSHOT` filter, so a the train is not releasing is left out only because its entry names a release that is already tagged or whose branch is gone. Check the *Skipped* list on an unfamiliar train. +## Hotfix trains + +A 4-segment train (e.g. `2025.1.4.1`) lists every project, including those without a hotfix, at +the versions they have in the regular train. Those have a branch and no tag yet, but the regular +train's run marks them ready, so for a hotfix train only the 4-segment (`x.y.z.h`) versions are +planned. The rest are skipped as "not a hotfix version". + ## Concurrent `projects.json` updates Each child ends by retiring its branch from `config/projects.json`, so the children push to