#!/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 */ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; use MokoEnterprise\CliFramework; class ChangelogPruneCli extends CliFramework { protected function configure(): void { $this->setDescription('Prune old CHANGELOG.md entries — keeps [Unreleased] + last N releases'); $this->addArgument('--path', 'Repository path', '.'); $this->addArgument('--keep', 'Number of versioned releases to keep', '5'); } protected function run(): int { $path = $this->getArgument('--path'); $keep = (int) $this->getArgument('--keep'); $changelog = realpath($path) . '/CHANGELOG.md'; if (!file_exists($changelog)) { $this->log('ERROR', "No CHANGELOG.md found at {$path}"); return 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"; return 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 ($this->dryRun) { echo "\n(dry-run) No changes written\n"; return 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"; return 0; } } $app = new ChangelogPruneCli(); exit($app->execute());