From a1ef4ed9265eb56df1db3a68986212f962f76d0d Mon Sep 17 00:00:00 2001 From: DemchaAV Date: Sun, 2 Aug 2026 21:36:42 +0100 Subject: [PATCH 1/3] feat(release-script): re-render the previews a cut publishes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The committed previews record the version they were rendered at, and nothing moved it. A cut bumped every pom, regenerated the showcase site at the new version, and left assets/readme on the release before — with the drift gate comparing both sides at the recorded version and staying green through it. The figures README and the site show would have gone one release out of date at every tag, invisibly, which is the drift the gate was written to end. The cut now moves that property with the tag and re-renders the previews from the same catalogue the site is built from. It runs before the verify step, because that is where the gate compares them: scheduled after it, a cut would fail on exactly the files it was about to bring up to date. Generating the catalogue and syncing the site were one function, so skipping the site skipped the render as well. They are separate now, and -SkipShowcase skips only what it names — the published tree under web/. The previews ship in the repository and are refreshed either way; -PostReleaseOnly leaves them alone, since they belong to the tag rather than to the branch it opens. The two halves — the refresh and the staging list that carries it into the release commit — sit in different parts of the script and neither fails without the other, so a guard holds them together, and holds the refresh before the step that checks it. Dry-run in all three modes: a plain cut bumps the property, renders, refreshes 67 previews and stages them; -SkipShowcase does the same without touching web/; -PostReleaseOnly touches neither. --- CHANGELOG.md | 10 ++ .../ReleaseAssetStepGuardTest.java | 62 +++++++++ scripts/cut-release.ps1 | 127 ++++++++++++++++-- 3 files changed, 191 insertions(+), 8 deletions(-) create mode 100644 core/src/test/java/com/demcha/documentation/ReleaseAssetStepGuardTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 715c4026..21b34368 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ follow semantic versioning; release dates are ISO 8601. ### Build +- **A release re-renders the previews it publishes.** The committed previews record + the version they were rendered at, and until now nothing moved it: a cut bumped + every pom, regenerated the showcase site at the new version, and left + `assets/readme/**` on the release before — with the drift gate comparing both + sides at the old version and staying green through it. The cut now bumps that + property with the tag and re-renders the previews from the same catalogue the + site is built from, before the verify step that checks them. `-SkipShowcase` + skips the published site under `web/` and no longer skips these, since they ship + in the repository; `-PostReleaseOnly` leaves them alone, because they belong to + the tag rather than to the branch it opens. - **A committed preview cannot fall behind the code that renders it.** README and the showcase site read files under `assets/readme/**` rather than rendering anything, and nothing held those files to the catalogue: a change to an example, diff --git a/core/src/test/java/com/demcha/documentation/ReleaseAssetStepGuardTest.java b/core/src/test/java/com/demcha/documentation/ReleaseAssetStepGuardTest.java new file mode 100644 index 00000000..dfe22619 --- /dev/null +++ b/core/src/test/java/com/demcha/documentation/ReleaseAssetStepGuardTest.java @@ -0,0 +1,62 @@ +package com.demcha.documentation; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Keeps the release script's asset step joined to the commit it has to land in. + * + *

A cut re-renders the previews under {@code assets/readme} at the version it is tagging, and + * commits them with everything else it bumped. The two halves sit in different parts of the + * script and neither fails without the other: a refresh that is not staged leaves the release + * carrying previews from the version before, and a staged path nothing refreshes commits + * whatever happened to be in the working tree. Both come back as the same symptom — a release + * publishing figures that do not match the code — which is what the drift gate was written to + * end, and what this keeps it from being reintroduced beneath.

+ * + *

The order matters as much as the presence. The verify step runs the drift gate, so a + * refresh scheduled after it fails the cut on the previews it was about to fix.

+ */ +class ReleaseAssetStepGuardTest { + + private static final Path SCRIPT = RepoRoot.get().resolve("scripts/cut-release.ps1"); + + @Test + void theReleaseScriptRefreshesTheCommittedPreviewsAndCommitsThem() throws IOException { + String script = Files.readString(SCRIPT); + + assertThat(script) + .describedAs("cut-release.ps1 no longer refreshes the committed previews: a cut " + + "would tag a release whose figures are the previous one's") + .contains("Refresh-CommittedPreviews"); + assertThat(script) + .describedAs("cut-release.ps1 no longer stages assets/readme/examples, so a refresh " + + "would happen and never reach the release commit") + .contains("'assets/readme/examples'"); + assertThat(script) + .describedAs("cut-release.ps1 no longer moves the version the previews record; the " + + "drift gate would then compare a release's previews at the version before " + + "it, and pass") + .contains("graphcompose.examples.assetVersion"); + } + + @Test + void thePreviewsAreRefreshedBeforeTheStepThatChecksThem() throws IOException { + String script = Files.readString(SCRIPT); + + int refresh = script.indexOf(" Refresh-CommittedPreviews"); + int verify = script.indexOf("Run mvnw clean verify"); + assertThat(refresh).describedAs("the refresh call is gone").isNotNegative(); + assertThat(verify).describedAs("the verify step is gone").isNotNegative(); + + assertThat(refresh) + .describedAs("the previews are refreshed after the verify step that compares them, " + + "so a cut fails on exactly the files it was about to bring up to date") + .isLessThan(verify); + } +} diff --git a/scripts/cut-release.ps1 b/scripts/cut-release.ps1 index b1d6da09..f29b58d4 100644 --- a/scripts/cut-release.ps1 +++ b/scripts/cut-release.ps1 @@ -216,6 +216,20 @@ function Update-PomVersion($pomPath, $newVersion) { return } + # 3. property (examples/pom.xml only). + # It records the version the committed previews under assets/readme were + # rendered at, and CommittedAssetDriftTest renders at it to compare like + # with like. It moves here, in the same commit as the tag, because the + # previews are re-rendered at the same version a few steps later; leaving + # it behind would put the gate on the old version and hide the drift it + # exists to catch. + $assetRegex = [regex]'[\w\.\-]+' + $assetNew = "$newVersion" + if ($assetRegex.IsMatch($content)) { + $content = $assetRegex.Replace($content, $assetNew, 1) + Note "bumped : $pomPath -> $newVersion" + } + if ($DryRun) { Write-Host " [DRY RUN] Bump $pomPath -> $newVersion" -ForegroundColor Yellow } else { @@ -554,7 +568,10 @@ function Update-ShowcaseGhBase($newRef) { return $true } -function Run-ShowcaseSync { +function Build-ExampleCatalogue { + # Renders the whole example catalogue into examples/target/generated-pdfs at the + # version the poms now carry. Both the published site and the committed previews + # are copied out of that tree, so it is built once and read twice. # Quote the -D argument: PowerShell's call operator drops the leading # '-D' on the way to mvnw.cmd, so Maven sees ".mainClass=..." as a # lifecycle phase. Wrapping the whole token in quotes preserves it @@ -576,7 +593,6 @@ function Run-ShowcaseSync { Write-Host " [DRY RUN] $mvnw -B -ntp -DskipTests install -f $modulePom" -ForegroundColor Yellow } Write-Host " [DRY RUN] $mvnw -B -ntp -f examples/pom.xml -DskipTests clean compile exec:java $generateProp" -ForegroundColor Yellow - Write-Host " [DRY RUN] $mvnw -B -ntp -f examples/pom.xml -DskipTests compile exec:java $execProp" -ForegroundColor Yellow return } Push-Location $repoRoot @@ -627,6 +643,21 @@ function Run-ShowcaseSync { if ($LASTEXITCODE -ne 0) { throw "GenerateAllExamples failed (exit $LASTEXITCODE)" } + } finally { + Pop-Location + } +} + +function Sync-ShowcaseSite { + # Copies the generated catalogue into web/showcase and writes web/examples.json. + # Reads the tree Build-ExampleCatalogue leaves behind — call it first. + $execProp = '"-Dexec.mainClass=com.demcha.examples.support.ShowcaseSync"' + if ($DryRun) { + Write-Host " [DRY RUN] $mvnw -B -ntp -f examples/pom.xml -DskipTests compile exec:java $execProp" -ForegroundColor Yellow + return + } + Push-Location $repoRoot + try { # `compile` before exec:java is REQUIRED: Step 3 rewrote ShowcaseMetadata.GH_BASE # to /blob/, and exec:java runs the COMPILED class. Without recompiling it here, # ShowcaseSync would emit examples.json with the previous release's "View Code" links @@ -645,6 +676,68 @@ function Run-ShowcaseSync { } } +function Run-ShowcaseSync { + # The pair, for the post-release pass: it regenerates the site with branch links + # and deliberately leaves the committed previews alone — those belong to the tag. + Build-ExampleCatalogue + Sync-ShowcaseSite +} + +function Refresh-CommittedPreviews { + # Copies the freshly generated catalogue over the previews the repository + # commits under assets/readme/examples. README and the showcase site read + # those files rather than rendering anything, and until CommittedAssetDriftTest + # arrived nothing held them to the code: they drifted release by release, and + # a deck went two of them without the bold weights its styles asked for. + # + # Which previews are published is the folder itself — every file already there + # gets its counterpart, and nothing new is added. That is the same set the + # drift gate compares, so the refresh and the check cannot disagree about what + # is published. + # + # Runs on every cut, -SkipShowcase or not: that flag is about the published + # site under web/, while these files ship in the repository. It must also run + # BEFORE Step 5, since `mvnw verify` is where the drift gate would otherwise + # fail the release on previews this step exists to refresh. + $previews = Join-Path $repoRoot 'assets/readme/examples' + $generated = Join-Path $repoRoot 'examples/target/generated-pdfs' + if (-not (Test-Path $previews)) { + Note "no committed previews at $previews — nothing to refresh" + return + } + if (-not $DryRun -and -not (Test-Path $generated)) { + throw "Refresh-CommittedPreviews: $generated is missing — the catalogue must be generated first." + } + + $committed = Get-ChildItem -Path $previews -File + if ($DryRun) { + Write-Host " [DRY RUN] refresh $($committed.Count) committed previews from $generated" -ForegroundColor Yellow + return + } + + $rendered = @{} + foreach ($file in Get-ChildItem -Path $generated -File -Recurse) { + if (-not $rendered.ContainsKey($file.Name)) { + $rendered[$file.Name] = $file.FullName + } + } + + $missing = @() + foreach ($file in $committed) { + if ($rendered.ContainsKey($file.Name)) { + Copy-Item -Path $rendered[$file.Name] -Destination $file.FullName -Force + } else { + $missing += $file.Name + } + } + if ($missing.Count -gt 0) { + # A committed preview no example renders cannot be refreshed, and the drift + # gate fails on it a step later. Say so here, where the name is still known. + throw "Refresh-CommittedPreviews: nothing renders $($missing -join ', ')." + } + Note "previews: $($committed.Count) refreshed from the catalogue" +} + function Render-ReadmeBanner { # Re-renders assets/readme/repository_showcase_render.png — the 2.0 module-first # hero (EngineDeckV2Example.renderBannerImage) — so the hero's version pill @@ -968,12 +1061,24 @@ try { if (-not $SkipShowcase) { Step 3 "Switch ShowcaseMetadata GH_BASE to /blob/$tag" Update-ShowcaseGhBase $tag | Out-Null + } else { + Step 3 "Skipped showcase GH_BASE flip (-SkipShowcase)" + } + + # The README assets are re-rendered on every cut. -SkipShowcase is about the + # published site under web/; these files ship in the repository, and a preview + # left at the previous release is what CommittedAssetDriftTest fails the next + # build on. It also has to happen before Step 5, which is where that gate runs. + Step 4 "Re-render the README assets at $Version" + Build-ExampleCatalogue + Refresh-CommittedPreviews + Render-ReadmeBanner - Step 4 "Regenerate web/examples.json with $tag links" - Run-ShowcaseSync - Render-ReadmeBanner + if (-not $SkipShowcase) { + Step "4b" "Regenerate web/examples.json with $tag links" + Sync-ShowcaseSite } else { - Step 3 "Skipped showcase GH_BASE flip + regen + banner (-SkipShowcase)" + Step "4b" "Skipped web/showcase sync (-SkipShowcase)" } if (-not $SkipVerify) { @@ -1076,12 +1181,18 @@ try { $commitFiles += $moduleReadme } } + # The README assets are re-rendered on every cut, -SkipShowcase or not: that + # flag is about the published site, and a preview left behind is what the drift + # gate fails the next build on. + $commitFiles += @( + 'assets/readme/examples', + 'assets/readme/repository_showcase_render.png' + ) if (-not $SkipShowcase) { $commitFiles += @( 'examples/src/main/java/com/demcha/examples/support/ShowcaseMetadata.java', 'web/examples.json', - 'web/showcase', - 'assets/readme/repository_showcase_render.png' + 'web/showcase' ) } if ($DryRun) { From 801d713f86dcb0031b2eaa8fe1e0295d853e3145 Mon Sep 17 00:00:00 2001 From: DemchaAV Date: Sun, 2 Aug 2026 22:23:29 +0100 Subject: [PATCH 2/3] fix(release-script): move the previews' version on a final tag and nowhere else MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumping it inside Update-PomVersion put it on every pom that pass touches, in every mode that calls it. The post-release step carries the train to X.Y.(Z+1)-SNAPSHOT and would have carried this with it; surefire hands the value to the examples module as a display version, and ExampleVersion accepts a released X.Y.Z and nothing else, so the next build would have thrown before comparing a single preview. A pre-release cut is the same defect with a different value: X.Y.Z-rc.N is rejected too, and its qualifier-stripped form names a release that does not exist — the previews would have advertised it. Neither showed in a dry run. This tree is already on a -SNAPSHOT, so the post-release bump finds nothing to do and never reaches the property, and a dry run writes no pom, so an -rc cut had nothing to inspect. The check is the branch that ran rather than the file that changed, and the release-script workflow now asserts all three: a final cut moves it, a pre-release and the post-release pass leave it alone. The move lives in its own step, called only for a final tag, and the README assets follow it there — a pre-release leaves the previews and the property on the last published version and stages neither. It also stands outside Update-PomVersion's early return: a cut interrupted after the version bump left the property behind, and re-running reported nothing to do. --- .github/workflows/release-script-check.yml | 23 ++++ .../ReleaseAssetStepGuardTest.java | 40 ++++++- examples/pom.xml | 10 +- scripts/cut-release.ps1 | 103 ++++++++++++------ 4 files changed, 138 insertions(+), 38 deletions(-) diff --git a/.github/workflows/release-script-check.yml b/.github/workflows/release-script-check.yml index 4eb13cf2..7ebe2e29 100644 --- a/.github/workflows/release-script-check.yml +++ b/.github/workflows/release-script-check.yml @@ -42,6 +42,29 @@ jobs: ./scripts/cut-release.ps1 -PostReleaseOnly -Branch develop -DryRun if ($LASTEXITCODE -ne 0) { throw "PostReleaseOnly dry-run exited $LASTEXITCODE" } + - name: The asset version follows the tag, and only a final tag + shell: pwsh + run: | + # records the version the committed previews + # were rendered at, and ExampleVersion accepts a released X.Y.Z and nothing else. + # So it must move on a final cut and stay put everywhere else: carried into a + # -SNAPSHOT by the post-release bump it would throw before a single preview was + # compared, and carried into an -rc it would have the previews advertise a release + # that does not exist. Assert the branch that ran, not the file it would write: + # -DryRun mutates nothing, so the notice is the only evidence either way. + $final = ./scripts/cut-release.ps1 -Version 9.9.9 -Branch develop -DryRun -SkipShowcase *>&1 | Out-String + if ($LASTEXITCODE -ne 0) { throw "final dry-run exited $LASTEXITCODE" } + if ($final -notmatch 'asset version -> 9\.9\.9') { throw 'a final cut must move the asset version' } + if ($final -notmatch 'Re-render the README assets') { throw 'a final cut must re-render the previews' } + + $rc = ./scripts/cut-release.ps1 -Version 2.1.0-rc.1 -Branch develop -DryRun -SkipShowcase *>&1 | Out-String + if ($rc -match 'asset version ->') { throw 'a pre-release cut must NOT move the asset version' } + if ($rc -notmatch 'Skipped the README assets') { throw 'a pre-release cut must leave the previews alone' } + + $post = ./scripts/cut-release.ps1 -PostReleaseOnly -Branch develop -DryRun *>&1 | Out-String + if ($post -match 'asset version ->') { throw 'the post-release bump must NOT move the asset version' } + Write-Host 'asset version: moves on a final cut, stays put on a pre-release and post-release.' + - name: Dry-run a pre-release (RC) cut shell: pwsh run: | diff --git a/core/src/test/java/com/demcha/documentation/ReleaseAssetStepGuardTest.java b/core/src/test/java/com/demcha/documentation/ReleaseAssetStepGuardTest.java index dfe22619..ea1805e1 100644 --- a/core/src/test/java/com/demcha/documentation/ReleaseAssetStepGuardTest.java +++ b/core/src/test/java/com/demcha/documentation/ReleaseAssetStepGuardTest.java @@ -42,7 +42,45 @@ void theReleaseScriptRefreshesTheCommittedPreviewsAndCommitsThem() throws IOExce .describedAs("cut-release.ps1 no longer moves the version the previews record; the " + "drift gate would then compare a release's previews at the version before " + "it, and pass") - .contains("graphcompose.examples.assetVersion"); + .contains("function Update-AssetVersion"); + } + + /** + * The version the previews record moves on a final cut and on nothing else. + * + *

{@code ExampleVersion} accepts a released {@code X.Y.Z} and rejects everything else, so + * the property cannot ride along with the generic pom bump: the post-release step carries the + * train to {@code X.Y.(Z+1)-SNAPSHOT}, which would throw before a single preview was compared, + * and a pre-release cut carries {@code X.Y.Z-rc.N}, whose qualifier-stripped form names a + * release that does not exist yet — the previews would advertise it.

+ * + *

The three modes are exercised for real in {@code release-script-check.yml}. What this + * pins is the wiring that makes those outcomes structural rather than incidental: the generic + * bump does not touch the property, and the step that does sits inside the final-release + * branch.

+ */ + @Test + void onlyAFinalCutMovesTheVersionThePreviewsRecord() throws IOException { + String script = Files.readString(SCRIPT); + + int genericBump = script.indexOf("function Update-PomVersion"); + int assetBump = script.indexOf("function Update-AssetVersion"); + assertThat(genericBump).describedAs("Update-PomVersion is gone").isNotNegative(); + assertThat(assetBump).describedAs("Update-AssetVersion is gone").isNotNegative(); + assertThat(script.substring(genericBump, assetBump)) + .describedAs("the generic pom bump touches the asset version again — it runs for " + + "the post-release SNAPSHOT and for a pre-release, and both values are " + + "ones the examples module refuses") + .doesNotContain("assetVersion"); + + int finalBranch = script.indexOf("if ($isFinalRelease) {", script.indexOf("Step 4 ")); + int call = script.indexOf("Update-AssetVersion (Join-Path"); + assertThat(finalBranch).describedAs("the final-release branch around Step 4 is gone") + .isNotNegative(); + assertThat(call) + .describedAs("the asset version is moved outside the final-release branch, so a " + + "pre-release cut would move it too") + .isGreaterThan(finalBranch); } @Test diff --git a/examples/pom.xml b/examples/pom.xml index 468f043d..90715395 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -29,12 +29,10 @@ only compare like with like because the surefire configuration below pins the display version to this value. - Nothing moves it yet. Until cut-release.ps1 re-renders the previews and - bumps this in the same commit as the tag, a release leaves the previews - at the version below while the showcase site goes out at the new one — - and the drift gate, comparing both sides at this value, stays green - through it. That step is the reason this property exists; it is not - written yet. + cut-release.ps1 moves it on a final cut, in the same commit as the tag and + next to the step that re-renders the previews at that version. A + pre-release cut leaves both alone: the version they would carry is this + one's qualifier stripped, which names a release that does not exist yet. --> 2.1.0 diff --git a/scripts/cut-release.ps1 b/scripts/cut-release.ps1 index f29b58d4..6d0df292 100644 --- a/scripts/cut-release.ps1 +++ b/scripts/cut-release.ps1 @@ -216,20 +216,6 @@ function Update-PomVersion($pomPath, $newVersion) { return } - # 3. property (examples/pom.xml only). - # It records the version the committed previews under assets/readme were - # rendered at, and CommittedAssetDriftTest renders at it to compare like - # with like. It moves here, in the same commit as the tag, because the - # previews are re-rendered at the same version a few steps later; leaving - # it behind would put the gate on the old version and hide the drift it - # exists to catch. - $assetRegex = [regex]'[\w\.\-]+' - $assetNew = "$newVersion" - if ($assetRegex.IsMatch($content)) { - $content = $assetRegex.Replace($content, $assetNew, 1) - Note "bumped : $pomPath -> $newVersion" - } - if ($DryRun) { Write-Host " [DRY RUN] Bump $pomPath -> $newVersion" -ForegroundColor Yellow } else { @@ -237,6 +223,46 @@ function Update-PomVersion($pomPath, $newVersion) { } } +function Update-AssetVersion($pomPath, $newVersion) { + # Moves — the version the committed previews + # under assets/readme were rendered at. CommittedAssetDriftTest renders at it to + # compare like with like, and ExampleVersion accepts a released X.Y.Z and nothing + # else, so this is deliberately NOT part of Update-PomVersion: + # + # * -PostReleaseOnly bumps the train to X.Y.(Z+1)-SNAPSHOT. Carrying the property + # along would hand surefire a -SNAPSHOT display version, and the examples module + # would throw before comparing a single preview. + # * a pre-release cut sets X.Y.Z-rc.N, which the same check rejects — and whose + # qualifier-stripped form names a final version that does not exist yet, so the + # previews would advertise an unpublished release. + # + # Only a final cut moves it, in the same commit as the tag, next to the step that + # re-renders the previews at that version. + # + # Independent of Update-PomVersion's early return as well: a cut interrupted after + # the version bump leaves the poms on the new version and this property behind, and + # re-running has to be able to finish the job rather than report nothing to do. + $content = [System.IO.File]::ReadAllText($pomPath) + $assetRegex = [regex]'[\w\.\-]+' + $match = $assetRegex.Match($content) + if (-not $match.Success) { + Note "no in $pomPath — nothing to move" + return + } + if ($match.Value -eq "$newVersion") { + Note "asset version already $newVersion" + return + } + if ($DryRun) { + Write-Host " [DRY RUN] asset version -> $newVersion" -ForegroundColor Yellow + return + } + $content = $assetRegex.Replace($content, + "$newVersion", 1) + [System.IO.File]::WriteAllText($pomPath, $content) + Note "asset version -> $newVersion" +} + function Get-NextSnapshotVersion($version) { # A final release X.Y.Z opens the next patch development line X.Y.(Z+1)-SNAPSHOT. # Pre-release versions (rc / beta / alpha) stay on their own cycle, so return @@ -745,7 +771,7 @@ function Render-ReadmeBanner { # banner.properties). The `compile` is REQUIRED: banner.properties is filtered # at examples-compile time, so the examples module must be recompiled AFTER the # Step-1 version bump — otherwise the banner would carry the previous release - # version. Runs after Run-ShowcaseSync, which already installed the bumped root + # version. Runs after Build-ExampleCatalogue, which already installed the bumped root # artifact into the local m2 cache so the examples module resolves it. Write-Host " > Re-render the version-stamped README hero banner" -ForegroundColor Cyan $banner = Join-Path $repoRoot 'assets/readme/repository_showcase_render.png' @@ -1065,20 +1091,33 @@ try { Step 3 "Skipped showcase GH_BASE flip (-SkipShowcase)" } - # The README assets are re-rendered on every cut. -SkipShowcase is about the - # published site under web/; these files ship in the repository, and a preview - # left at the previous release is what CommittedAssetDriftTest fails the next - # build on. It also has to happen before Step 5, which is where that gate runs. - Step 4 "Re-render the README assets at $Version" + # The catalogue is built either way: the site is copied out of it, and so are the + # previews. -SkipShowcase is about the published tree under web/, not about this. + Step 4 "Build the example catalogue at $Version" Build-ExampleCatalogue - Refresh-CommittedPreviews - Render-ReadmeBanner + + # The README assets follow the tag, so only a final cut moves them. On a + # pre-release the version they would carry is the qualifier-stripped one — a + # release that does not exist yet — so they stay on the last published version + # along with the property that records it. + # + # -SkipShowcase does not skip this: these files ship in the repository, and a + # preview left at the previous release is what CommittedAssetDriftTest fails the + # next build on. It has to happen before Step 5, which is where that gate runs. + if ($isFinalRelease) { + Step "4b" "Re-render the README assets at $Version" + Update-AssetVersion (Join-Path $repoRoot 'examples/pom.xml') $Version + Refresh-CommittedPreviews + Render-ReadmeBanner + } else { + Step "4b" "Skipped the README assets (pre-release cut: they stay on the last published version)" + } if (-not $SkipShowcase) { - Step "4b" "Regenerate web/examples.json with $tag links" + Step "4c" "Regenerate web/examples.json with $tag links" Sync-ShowcaseSite } else { - Step "4b" "Skipped web/showcase sync (-SkipShowcase)" + Step "4c" "Skipped web/showcase sync (-SkipShowcase)" } if (-not $SkipVerify) { @@ -1181,13 +1220,15 @@ try { $commitFiles += $moduleReadme } } - # The README assets are re-rendered on every cut, -SkipShowcase or not: that - # flag is about the published site, and a preview left behind is what the drift - # gate fails the next build on. - $commitFiles += @( - 'assets/readme/examples', - 'assets/readme/repository_showcase_render.png' - ) + # The README assets ride along whenever they were re-rendered — every final cut, + # -SkipShowcase or not, since that flag is about the published site and these ship + # in the repository. A pre-release leaves them alone, so it stages nothing here. + if ($isFinalRelease) { + $commitFiles += @( + 'assets/readme/examples', + 'assets/readme/repository_showcase_render.png' + ) + } if (-not $SkipShowcase) { $commitFiles += @( 'examples/src/main/java/com/demcha/examples/support/ShowcaseMetadata.java', From 1069f4c32f3b5b529318f77ac61197c1c5a4f1cd Mon Sep 17 00:00:00 2001 From: DemchaAV Date: Sun, 2 Aug 2026 23:09:55 +0100 Subject: [PATCH 3/3] fix(release-script): refuse two rendered documents under one name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The committed folder is flat, so a file name is the whole address. Two rendered documents sharing one left whichever the directory walk reached first deciding what a preview was refreshed from — the same silence CommittedAssetDriftTest refuses, and it refuses it a step later, so an ordinary cut would have caught this at verify. Not a cut run with -SkipVerify: there the wrong document ships under the right name, and nothing says so. --- scripts/cut-release.ps1 | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/cut-release.ps1 b/scripts/cut-release.ps1 index 6d0df292..ecde5d2a 100644 --- a/scripts/cut-release.ps1 +++ b/scripts/cut-release.ps1 @@ -741,11 +741,18 @@ function Refresh-CommittedPreviews { return } + # The committed folder is flat, so a name is the whole address. Two rendered + # documents sharing one would leave whichever the walk reached first deciding what + # a preview gets refreshed from — the same silence CommittedAssetDriftTest refuses, + # and the reason to refuse it here too: with -SkipVerify that gate never runs, and + # the wrong document would be committed under the right name. $rendered = @{} foreach ($file in Get-ChildItem -Path $generated -File -Recurse) { - if (-not $rendered.ContainsKey($file.Name)) { - $rendered[$file.Name] = $file.FullName + if ($rendered.ContainsKey($file.Name)) { + throw ("Refresh-CommittedPreviews: two rendered documents share the name " + + "$($file.Name) ($($rendered[$file.Name]) and $($file.FullName)).") } + $rendered[$file.Name] = $file.FullName } $missing = @()