Files
Maestro/scripts/sync-release-notes.mjs
Pedram Amini dbc2885ec4 ## CHANGES
- Added automatic post-release docs sync, committing updated release notes to repo 📝
- Introduced `scripts/sync-release-notes.mjs` generating docs from GitHub releases via `gh` 🔄
- Published new `docs/releases.md` page with full versioned release history 📚
- Exposed Release Notes in Mintlify navigation under Reference docs section 🧭
- Strengthened Linux x64 packaging checks to hard-fail native arch mismatches 🛡️
- Expanded CI verification to include `better_sqlite3.node` alongside `pty.node` 🧩
2025-12-29 10:37:49 -06:00

208 lines
5.7 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* Generates docs/releases.md from GitHub releases.
*
* Usage:
* node scripts/sync-release-notes.mjs
*
* Requires: gh CLI authenticated
*/
import { execFileSync } from 'child_process';
import { writeFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const projectRoot = join(__dirname, '..');
function fetchReleases() {
try {
const output = execFileSync(
'gh',
['release', 'list', '--limit', '100', '--json', 'tagName,name,publishedAt,isPrerelease'],
{ encoding: 'utf-8', cwd: projectRoot }
);
return JSON.parse(output);
} catch (error) {
console.error('Failed to fetch releases:', error.message);
process.exit(1);
}
}
function fetchReleaseBody(tagName) {
try {
const output = execFileSync(
'gh',
['release', 'view', tagName, '--json', 'body', '-q', '.body'],
{ encoding: 'utf-8', cwd: projectRoot }
);
return output.trim();
} catch {
return '';
}
}
function formatDate(isoDate) {
const date = new Date(isoDate);
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
}
function getMinorVersion(tagName) {
// v0.12.3 -> v0.12
const match = tagName.match(/^v?(\d+)\.(\d+)/);
if (match) {
return `v${match[1]}.${match[2]}`;
}
return tagName;
}
function groupReleasesByMinor(releases) {
const groups = new Map();
for (const release of releases) {
// Skip pre-releases and RC versions for the main listing
if (release.isPrerelease || release.tagName.includes('-rc') || release.tagName.includes('-RC')) {
continue;
}
const minor = getMinorVersion(release.tagName);
if (!groups.has(minor)) {
groups.set(minor, []);
}
groups.get(minor).push(release);
}
// Sort each group by version descending
for (const [, releases] of groups) {
releases.sort((a, b) => {
const aVer = a.tagName.replace(/^v/, '').split('.').map(Number);
const bVer = b.tagName.replace(/^v/, '').split('.').map(Number);
for (let i = 0; i < 3; i++) {
if ((bVer[i] || 0) !== (aVer[i] || 0)) {
return (bVer[i] || 0) - (aVer[i] || 0);
}
}
return 0;
});
}
return groups;
}
function extractTitle(releaseName) {
// "v0.12.3 | Thinking, Spec-Kits, Context Management" -> "Thinking, Spec-Kits, Context Management"
const match = releaseName.match(/\|\s*(.+)$/);
return match ? match[1].trim() : '';
}
function generateMarkdown(releases) {
const grouped = groupReleasesByMinor(releases);
// Sort groups by version descending
const sortedGroups = Array.from(grouped.entries()).sort((a, b) => {
const aVer = a[0].replace(/^v/, '').split('.').map(Number);
const bVer = b[0].replace(/^v/, '').split('.').map(Number);
for (let i = 0; i < 2; i++) {
if ((bVer[i] || 0) !== (aVer[i] || 0)) {
return (bVer[i] || 0) - (aVer[i] || 0);
}
}
return 0;
});
let md = `---
title: Release Notes
description: Version history and changelog for Maestro releases
---
# Release Notes
This page documents the version history of Maestro, including new features, improvements, and bug fixes for each release.
<Tip>
Maestro can update itself automatically! This feature was introduced in **v0.8.7** (December 16, 2025). Enable auto-updates in Settings to stay current.
</Tip>
---
`;
for (const [minor, minorReleases] of sortedGroups) {
const latest = minorReleases[0];
const title = extractTitle(latest.name);
const titleSuffix = title ? ` - ${title}` : '';
md += `## ${minor}.x${titleSuffix}\n\n`;
md += `**Latest: ${latest.tagName}** | Released ${formatDate(latest.publishedAt)}\n\n`;
// Fetch and include the release body for the latest in this minor
const body = fetchReleaseBody(latest.tagName);
if (body) {
// Clean up the body - remove "Full Changelog" links as they're redundant
let cleanBody = body
.replace(/\*\*Full Changelog\*\*:.*$/gm, '')
.replace(/## What's Changed[\s\S]*?(?=##|$)/g, '') // Remove "What's Changed" sections
.replace(/## New Contributors[\s\S]*?(?=##|$)/g, '') // Remove "New Contributors" sections
.trim();
// If body starts with a list, add context
if (cleanBody.startsWith('-') || cleanBody.startsWith('*')) {
md += '### Changes\n\n';
}
md += cleanBody + '\n\n';
}
// If there are patch releases, list them briefly
if (minorReleases.length > 1) {
md += '### Previous Releases in this Series\n\n';
for (let i = 1; i < minorReleases.length; i++) {
const rel = minorReleases[i];
const relTitle = extractTitle(rel.name);
md += `- **${rel.tagName}** (${formatDate(rel.publishedAt)})`;
if (relTitle) {
md += ` - ${relTitle}`;
}
md += '\n';
}
md += '\n';
}
md += '---\n\n';
}
md += `## Downloading Releases
All releases are available on the [GitHub Releases page](https://github.com/pedramamini/Maestro/releases).
Maestro is available for:
- **macOS** - Apple Silicon (arm64) and Intel (x64)
- **Windows** - x64
- **Linux** - x64 and arm64, AppImage, deb, and rpm packages
`;
return md;
}
async function main() {
console.log('Fetching releases from GitHub...');
const releases = fetchReleases();
console.log(`Found ${releases.length} releases`);
console.log('Generating markdown...');
const markdown = generateMarkdown(releases);
const outputPath = join(projectRoot, 'docs', 'releases.md');
writeFileSync(outputPath, markdown, 'utf-8');
console.log(`Written to ${outputPath}`);
}
main().catch(console.error);