1+ name : Sync Release Notes
2+
3+ on :
4+ workflow_dispatch : # Manual trigger
5+ release :
6+ types : [published, edited] # Automatic trigger when releases are published or edited
7+
8+ jobs :
9+ sync-release-notes :
10+ runs-on : ubuntu-latest
11+ permissions :
12+ contents : write # Need write permission to update CHANGELOG.md
13+
14+ steps :
15+ - name : Harden the runner (Audit all outbound calls)
16+ uses : step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
17+ with :
18+ egress-policy : audit
19+
20+ - name : Checkout code
21+ uses : actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
22+ with :
23+ fetch-depth : 0 # Fetch all history so we can work with all releases
24+
25+ - name : Setup Node.js
26+ uses : actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
27+ with :
28+ node-version : ' 20.x'
29+
30+ - name : Sync GitHub release notes to CHANGELOG.md
31+ env :
32+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
33+ run : |
34+ # Create a script to fetch GitHub releases and update CHANGELOG.md
35+ cat > sync_changelog.js << 'EOF'
36+ const { execSync } = require('child_process');
37+ const fs = require('fs');
38+
39+ async function syncReleaseNotes() {
40+ try {
41+ console.log('Fetching GitHub releases...');
42+
43+ // Fetch all releases using GitHub CLI
44+ const releasesJson = execSync('gh release list --json tagName,name,body,createdAt,isPrerelease --limit 50', { encoding: 'utf8' });
45+ const releases = JSON.parse(releasesJson);
46+
47+ console.log(`Found ${releases.length} releases`);
48+
49+ // Sort releases by creation date (newest first)
50+ releases.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
51+
52+ // Read current CHANGELOG.md
53+ let changelog = '';
54+ if (fs.existsSync('CHANGELOG.md')) {
55+ changelog = fs.readFileSync('CHANGELOG.md', 'utf8');
56+ }
57+
58+ // Extract the header and unreleased section
59+ const lines = changelog.split('\n');
60+ const headerEndIndex = lines.findIndex(line => line.startsWith('## [Unreleased]'));
61+ const unreleasedEndIndex = lines.findIndex((line, index) =>
62+ index > headerEndIndex && line.startsWith('## [') && !line.includes('Unreleased')
63+ );
64+
65+ let header = '';
66+ let unreleasedSection = '';
67+
68+ if (headerEndIndex >= 0) {
69+ header = lines.slice(0, headerEndIndex + 1).join('\n');
70+ if (unreleasedEndIndex >= 0) {
71+ unreleasedSection = lines.slice(headerEndIndex + 1, unreleasedEndIndex).join('\n');
72+ } else {
73+ // Take everything after unreleased header until we find a release or end
74+ const restOfFile = lines.slice(headerEndIndex + 1);
75+ const nextReleaseIndex = restOfFile.findIndex(line => line.startsWith('## [') && !line.includes('Unreleased'));
76+ if (nextReleaseIndex >= 0) {
77+ unreleasedSection = restOfFile.slice(0, nextReleaseIndex).join('\n');
78+ } else {
79+ unreleasedSection = restOfFile.join('\n');
80+ }
81+ }
82+ } else {
83+ // Create basic header if none exists
84+ header = '# Change Log\n\nAll notable changes to the "copilot-token-tracker" extension will be documented in this file.\n\nCheck [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.\n\n## [Unreleased]';
85+ unreleasedSection = '\n';
86+ }
87+
88+ // Build new changelog content
89+ let newChangelog = header + unreleasedSection + '\n';
90+
91+ // Add releases
92+ for (const release of releases) {
93+ const version = release.tagName.startsWith('v') ? release.tagName.substring(1) : release.tagName;
94+ const releaseType = release.isPrerelease ? ' - Pre-release' : '';
95+
96+ newChangelog += `## [${version}]${releaseType}\n\n`;
97+
98+ if (release.body && release.body.trim()) {
99+ // Clean up the release body
100+ let body = release.body.trim();
101+
102+ // Remove any "Full Changelog" links at the end
103+ body = body.replace(/\*\*Full Changelog\*\*:.*$/gm, '').trim();
104+
105+ // Ensure bullet points are properly formatted
106+ const bodyLines = body.split('\n').map(line => {
107+ line = line.trim();
108+ if (line && !line.startsWith('-') && !line.startsWith('*') && !line.startsWith('#')) {
109+ return `- ${line}`;
110+ }
111+ return line;
112+ }).filter(line => line.length > 0);
113+
114+ newChangelog += bodyLines.join('\n') + '\n\n';
115+ } else {
116+ newChangelog += `- Release ${version}\n\n`;
117+ }
118+ }
119+
120+ // Write the new changelog
121+ fs.writeFileSync('CHANGELOG.md', newChangelog.trim() + '\n');
122+ console.log('CHANGELOG.md updated successfully!');
123+
124+ // Show what changed
125+ try {
126+ const diff = execSync('git diff CHANGELOG.md', { encoding: 'utf8' });
127+ if (diff.trim()) {
128+ console.log('\nChanges made to CHANGELOG.md:');
129+ console.log(diff);
130+ } else {
131+ console.log('No changes needed - CHANGELOG.md is already up to date');
132+ }
133+ } catch (error) {
134+ console.log('Could not show diff, but file was updated');
135+ }
136+
137+ } catch (error) {
138+ console.error('Error syncing release notes:', error);
139+ process.exit(1);
140+ }
141+ }
142+
143+ syncReleaseNotes();
144+ EOF
145+
146+ # Run the sync script
147+ node sync_changelog.js
148+
149+ - name : Check for changes
150+ id : changes
151+ run : |
152+ if git diff --quiet CHANGELOG.md; then
153+ echo "changed=false" >> $GITHUB_OUTPUT
154+ echo "No changes detected in CHANGELOG.md"
155+ else
156+ echo "changed=true" >> $GITHUB_OUTPUT
157+ echo "Changes detected in CHANGELOG.md"
158+ fi
159+
160+ - name : Commit and push changes
161+ if : steps.changes.outputs.changed == 'true'
162+ run : |
163+ git config --local user.email "action@github.com"
164+ git config --local user.name "GitHub Action"
165+ git add CHANGELOG.md
166+ git commit -m "docs: sync CHANGELOG.md with GitHub release notes
167+
168+ This commit automatically updates the CHANGELOG.md file to match
169+ the release notes from GitHub releases, ensuring consistency
170+ between local documentation and published releases."
171+ git push
172+
173+ - name : Summary
174+ run : |
175+ if [ "${{ steps.changes.outputs.changed }}" == "true" ]; then
176+ echo "✅ CHANGELOG.md has been successfully updated with GitHub release notes"
177+ else
178+ echo "ℹ️ CHANGELOG.md was already up to date with GitHub release notes"
179+ fi
0 commit comments