diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml index ffd47c8..d961bd8 100644 --- a/.mokogitea/workflows/auto-release.yml +++ b/.mokogitea/workflows/auto-release.yml @@ -261,6 +261,18 @@ jobs: # Step 5 (updates.xml) moved after Step 8 to include SHA-256 checksum + - name: "Step 4b: Promote and prune CHANGELOG" + if: >- + steps.version.outputs.skip != 'true' && + steps.check.outputs.already_released != 'true' + run: | + VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" + MOKO_API="/tmp/moko-platform-api/cli" + if [ -f "CHANGELOG.md" ]; then + php ${MOKO_API}/changelog_promote.php --path . --version "$VERSION" 2>&1 || true + php ${MOKO_API}/changelog_prune.php --path . --keep 5 2>&1 || true + fi + - name: Commit release changes if: >- steps.version.outputs.skip != 'true' && diff --git a/bin/moko b/bin/moko index 4a4c33b..1e501c2 100644 --- a/bin/moko +++ b/bin/moko @@ -132,6 +132,10 @@ const COMMAND_MAP = [ 'release:mirror' => 'cli/release_mirror.php', 'release:package' => 'cli/release_package.php', + // Changelog + 'changelog:promote' => 'cli/changelog_promote.php', + 'changelog:prune' => 'cli/changelog_prune.php', + // Version management 'version:read' => 'cli/version_read.php', 'version:bump' => 'cli/version_bump.php', diff --git a/cli/changelog_prune.php b/cli/changelog_prune.php new file mode 100644 index 0000000..40a4ec6 --- /dev/null +++ b/cli/changelog_prune.php @@ -0,0 +1,134 @@ +#!/usr/bin/env php + + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * FILE INFORMATION + * DEFGROUP: moko-platform.CLI + * INGROUP: moko-platform + * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform + * PATH: /cli/changelog_prune.php + * BRIEF: Prune old CHANGELOG.md entries — keeps [Unreleased] + last N releases + * + * Usage: + * php changelog_prune.php --path /repo --keep 5 + * php changelog_prune.php --path /repo --keep 3 --dry-run + */ + +declare(strict_types=1); + +$path = '.'; +$keep = 5; +$dryRun = false; + +foreach ($argv as $i => $arg) { + if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1]; + if ($arg === '--keep' && isset($argv[$i + 1])) $keep = (int)$argv[$i + 1]; + if ($arg === '--dry-run') $dryRun = true; + if ($arg === '--help') { + echo "changelog_prune — Keep [Unreleased] + last N versioned entries\n\n"; + echo "Usage: php changelog_prune.php --path . --keep 5 [--dry-run]\n\n"; + echo "Options:\n"; + echo " --path Repository path (default: .)\n"; + echo " --keep Number of versioned releases to keep (default: 5)\n"; + echo " --dry-run Preview without writing\n"; + exit(0); + } +} + +$changelog = realpath($path) . '/CHANGELOG.md'; +if (!file_exists($changelog)) { + fwrite(STDERR, "No CHANGELOG.md found at {$path}\n"); + exit(1); +} + +$content = file_get_contents($changelog); +$lines = explode("\n", $content); + +// Split into sections by ## headings +$sections = []; +$current = []; +$currentHeading = null; + +foreach ($lines as $line) { + if (preg_match('/^## /', $line)) { + if ($currentHeading !== null) { + $sections[] = ['heading' => $currentHeading, 'lines' => $current]; + } + $currentHeading = $line; + $current = [$line]; + } else { + $current[] = $line; + } +} +if ($currentHeading !== null) { + $sections[] = ['heading' => $currentHeading, 'lines' => $current]; +} + +// Find the header (everything before the first ## section) +$header = []; +$contentLines = explode("\n", $content); +foreach ($contentLines as $line) { + if (preg_match('/^## /', $line)) { + break; + } + $header[] = $line; +} + +// Separate [Unreleased] from versioned sections +$unreleased = null; +$versioned = []; + +foreach ($sections as $section) { + if (preg_match('/\[Unreleased\]/i', $section['heading'])) { + $unreleased = $section; + } else { + $versioned[] = $section; + } +} + +$totalVersioned = count($versioned); +$pruned = $totalVersioned - $keep; + +if ($pruned <= 0) { + echo "CHANGELOG has {$totalVersioned} versioned entries — nothing to prune (keeping {$keep})\n"; + exit(0); +} + +// Keep only the first N versioned sections +$keptVersioned = array_slice($versioned, 0, $keep); +$droppedVersioned = array_slice($versioned, $keep); + +// Report +echo "CHANGELOG: {$totalVersioned} versioned entries found\n"; +echo " Keeping: {$keep} most recent\n"; +echo " Pruning: {$pruned} old entries\n"; + +foreach ($droppedVersioned as $section) { + $heading = trim($section['heading']); + echo " - {$heading}\n"; +} + +if ($dryRun) { + echo "\n(dry-run) No changes written\n"; + exit(0); +} + +// Rebuild the file +$output = implode("\n", $header); + +if ($unreleased !== null) { + $output .= implode("\n", $unreleased['lines']) . "\n"; +} + +foreach ($keptVersioned as $section) { + $output .= implode("\n", $section['lines']) . "\n"; +} + +// Clean up excessive blank lines at end +$output = rtrim($output) . "\n"; + +file_put_contents($changelog, $output); +echo "\nCHANGELOG pruned: removed {$pruned} old entries\n"; +exit(0);