Files
bifrost/.github/workflows/scripts/push-mintlify-changelog.sh
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

287 lines
10 KiB
Bash
Executable File

#!/usr/bin/env bash
VERSION=$1
if [ -z "$VERSION" ]; then
echo "Usage: $0 <version>"
echo "Example: $0 1.2.0"
exit 1
fi
VERSION="v$VERSION"
# Check if this page already exists in docs/changelogs/
if [ -f "docs/changelogs/$VERSION.mdx" ]; then
echo "✅ Changelog for $VERSION already exists"
exit 0
fi
# Source changelog utilities
source "$(dirname "$0")/changelog-utils.sh"
# Get current date
CURRENT_DATE=$(date +"%Y-%m-%d")
# Preparing changelog file
CHANGELOG_BODY="---
title: \"$VERSION\"
description: \"$VERSION changelog - $CURRENT_DATE\"
---
<Tabs>
<Tab title=\"NPX\">
\`\`\`bash
npx -y @maximhq/bifrost --transport-version $VERSION
\`\`\`
</Tab>
<Tab title=\"Docker\">
\`\`\`bash
docker pull maximhq/bifrost:$VERSION
docker run -p 8080:8080 maximhq/bifrost:$VERSION
\`\`\`
</Tab>
</Tabs>
"
# Array to track cleaned changelog files
CLEANED_CHANGELOG_FILES=()
# Helper to append a section if changelog file exists and is non-empty
append_section () {
label=$1
path=$2
if [ -f "$path" ]; then
# Get changelog content
content=$(get_file_content "$path")
# If changelog is empty, skip
if [ -z "$content" ]; then
echo "❌ Changelog is empty"
return
fi
# Remove /changelog.md from the path and add /version
version_file_path="${path%/changelog.md}/version"
# Get version content
version_body=$(get_file_content "$version_file_path")
# Build the changelog section
CHANGELOG_BODY+=$'\n'"<Update label=\"$label\" description=\"$version_body\">"$'\n'"$content"$'\n\n'"</Update>"
# Clear the changelog file after processing
printf '' > "$path"
# Track this file for git commit
CLEANED_CHANGELOG_FILES+=("$path")
fi
}
# HTTP changelog
append_section "Bifrost(HTTP)" transports/changelog.md
# Core changelog
append_section "Core" core/changelog.md
# Framework changelog
append_section "Framework" framework/changelog.md
# Plugins changelogs
for plugin in plugins/*; do
name=$(basename "$plugin")
append_section "$name" "$plugin/changelog.md"
done
# Write to file
mkdir -p docs/changelogs
echo "$CHANGELOG_BODY" > docs/changelogs/$VERSION.mdx
# Update docs.json to include this new changelog route in the Changelogs tab pages array
# Uses month-based grouping: current month at top level, older months in groups
# Automatically reorganizes when month changes
route="changelogs/$VERSION"
if ! grep -q "\"$route\"" docs/docs.json; then
node -e "
const fs = require('fs');
const docs = JSON.parse(fs.readFileSync('docs/docs.json', 'utf8'));
// Semantic version comparison function
// Extracts version from route/filename and compares in descending order (newest first)
function compareVersionsDesc(a, b) {
// Extract route string from string or object
const routeA = typeof a === 'string' ? a : '';
const routeB = typeof b === 'string' ? b : '';
// Extract version from route (e.g., 'changelogs/v1.3.34' -> 'v1.3.34')
const versionA = routeA.split('/').pop() || '';
const versionB = routeB.split('/').pop() || '';
// Remove 'v' prefix and split into parts
const partsA = versionA.replace(/^v/, '').split(/[.-]/).map(p => {
const num = parseInt(p, 10);
return isNaN(num) ? p : num;
});
const partsB = versionB.replace(/^v/, '').split(/[.-]/).map(p => {
const num = parseInt(p, 10);
return isNaN(num) ? p : num;
});
// Compare each part (major, minor, patch, pre-release, etc.)
const maxLength = Math.max(partsA.length, partsB.length);
for (let i = 0; i < maxLength; i++) {
// Release vs prerelease: release is newer (no suffix > has suffix)
if (partsA[i] === undefined && partsB[i] !== undefined) {
return -1; // A (release) comes first in descending order
}
if (partsB[i] === undefined && partsA[i] !== undefined) {
return 1; // B (release) comes first in descending order
}
const partA = partsA[i];
const partB = partsB[i];
// If both are numbers, compare numerically
if (typeof partA === 'number' && typeof partB === 'number') {
if (partA !== partB) {
return partB - partA; // Descending order
}
} else {
// Handle prerelease strings with numeric suffixes (e.g., 'prerelease10')
const strA = String(partA);
const strB = String(partB);
const matchA = strA.match(/^([a-zA-Z]+)(\\d+)$/);
const matchB = strB.match(/^([a-zA-Z]+)(\\d+)$/);
if (matchA && matchB && matchA[1] === matchB[1]) {
// Same prefix, compare numbers numerically
const numA = parseInt(matchA[2], 10);
const numB = parseInt(matchB[2], 10);
if (numA !== numB) {
return numB - numA; // Descending order
}
} else if (strA !== strB) {
return strB.localeCompare(strA); // Descending order
}
}
}
return 0; // Equal
}
// Sort a pages array by semver (descending)
function sortPagesBySemver(pages) {
return pages.slice().sort(compareVersionsDesc);
}
// Get current month/year
const releaseDate = new Date('$CURRENT_DATE');
const currentDate = new Date();
const releaseMonthYear = releaseDate.toLocaleDateString('en-US', { year: 'numeric', month: 'long' });
const currentMonthYear = currentDate.toLocaleDateString('en-US', { year: 'numeric', month: 'long' });
// Find the Changelogs tab
const changelogsTab = docs.navigation.tabs.find(tab => tab.tab === 'Changelogs');
if (!changelogsTab) {
console.error('Changelogs tab not found');
process.exit(1);
}
// Find the Open Source menu item
const openSourceItem = changelogsTab.menu?.find(item => item.item === 'Open Source');
if (!openSourceItem) {
console.error('Open Source menu item not found in Changelogs tab');
process.exit(1);
}
// Get all top-level entries and existing groups
const topLevelEntries = openSourceItem.pages.filter(p => typeof p === 'string');
const existingGroups = openSourceItem.pages.filter(p => typeof p === 'object');
// Check if we need to group existing top-level entries
if (topLevelEntries.length > 0) {
// Get the month of the first top-level entry (they should all be from same month)
const firstEntryPath = topLevelEntries[0].replace('changelogs/', '') + '.mdx';
const firstEntryFile = 'docs/changelogs/' + firstEntryPath;
let topLevelMonth = null;
try {
const content = fs.readFileSync(firstEntryFile, 'utf8');
const descMatch = content.match(/description:\\s*\"[^\"]*?(\\d{4}-\\d{2}-\\d{2})[^\"]*\"/);
if (descMatch) {
const entryDate = new Date(descMatch[1]);
topLevelMonth = entryDate.toLocaleDateString('en-US', { year: 'numeric', month: 'long' });
}
} catch (e) {
console.log(\`Warning: Could not read entry file \${firstEntryFile}: \${e.message}\`);
}
// Only group if the month has changed
if (topLevelMonth && topLevelMonth !== releaseMonthYear) {
console.log(\`📦 Month changed from \${topLevelMonth} to \${releaseMonthYear}\`);
console.log(\`📦 Grouping \${topLevelEntries.length} top-level entries into \${topLevelMonth} group...\`);
// Create a group for all existing top-level entries
const previousMonthGroup = {
group: topLevelMonth,
pages: sortPagesBySemver(topLevelEntries)
};
// Add this group at the top of existing groups
existingGroups.unshift(previousMonthGroup);
console.log(\`✅ Created \${topLevelMonth} group with \${topLevelEntries.length} entries (sorted)\`);
// Clear top-level entries (they're now in the group)
openSourceItem.pages = existingGroups;
} else {
console.log(\`📋 Same month (\${releaseMonthYear}), keeping existing top-level entries\`);
// Keep existing structure (top-level entries + groups)
openSourceItem.pages = [...topLevelEntries, ...existingGroups];
}
}
const newRoute = '$route';
// Add the new changelog at the top level
openSourceItem.pages.unshift(newRoute);
console.log(\`✅ Added \${newRoute} to top level\`);
// Sort the top-level pages array by semver
const topLevelPages = openSourceItem.pages.filter(p => typeof p === 'string');
const groupPages = openSourceItem.pages.filter(p => typeof p === 'object');
if (topLevelPages.length > 0) {
const sortedTopLevel = sortPagesBySemver(topLevelPages);
openSourceItem.pages = [...sortedTopLevel, ...groupPages];
console.log(\`✅ Sorted \${topLevelPages.length} top-level pages by semver\`);
}
// Sort each group's pages by semver
for (const group of groupPages) {
if (group.pages && Array.isArray(group.pages)) {
group.pages = sortPagesBySemver(group.pages);
}
}
fs.writeFileSync('docs/docs.json', JSON.stringify(docs, null, 2) + '\n');
console.log('✅ Updated docs.json');
"
fi
# Pulling again before committing
CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
if [ "$CURRENT_BRANCH" = "HEAD" ]; then
# In detached HEAD state (common in CI), use GITHUB_REF_NAME or default to main
CURRENT_BRANCH="${GITHUB_REF_NAME:-main}"
fi
echo "Pulling latest changes from origin/$CURRENT_BRANCH..."
if ! git pull origin "$CURRENT_BRANCH"; then
echo "❌ Error: git pull origin $CURRENT_BRANCH failed"
exit 1
fi
# Commit and push changes
git add docs/changelogs/$VERSION.mdx
git add docs/docs.json
# Add all cleaned changelog files
for file in "${CLEANED_CHANGELOG_FILES[@]}"; do
git add "$file"
done
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git commit -m "Adds changelog for $VERSION --skip-ci"
git push origin "$CURRENT_BRANCH"