Skip to content

Commit e444fdf

Browse files
Copilotrajbos
andcommitted
feat: add automated release notes synchronization
- Add GitHub workflow to sync CHANGELOG.md with GitHub releases - Add Node.js script for manual synchronization - Support both GitHub CLI and API token authentication - Include test mode for local development - Add npm scripts for easy usage - Update documentation with sync instructions Co-authored-by: rajbos <6085745+rajbos@users.noreply.github.com>
1 parent 3919a72 commit e444fdf

5 files changed

Lines changed: 472 additions & 1 deletion

File tree

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
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

CHANGELOG.md.backup

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Change Log
2+
3+
All notable changes to the "copilot-token-tracker" extension will be documented in this file.
4+
5+
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
6+
7+
## [Unreleased]
8+
9+
- Automated VSIX build and release workflow
10+
11+
## [0.0.1] - Initial Release
12+
13+
- Initial release
14+
- Real-time token tracking with status bar display
15+
- Automatic updates every 5 minutes
16+
- Click to refresh functionality
17+
- Smart estimation using character-based analysis
18+
- Detailed view with comprehensive statistics

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,25 @@ The release workflow will:
9999

100100
**Note**: The workflow will fail if the tag version doesn't match the version in `package.json`.
101101

102+
### Syncing Release Notes
103+
104+
To keep the local `CHANGELOG.md` file synchronized with GitHub release notes:
105+
106+
**Manual Sync:**
107+
```bash
108+
npm run sync-changelog
109+
```
110+
111+
**Automatic Sync:**
112+
The project includes a GitHub workflow that automatically updates `CHANGELOG.md` whenever:
113+
- A new release is published
114+
- An existing release is edited
115+
- The workflow is manually triggered
116+
117+
**Test the Sync:**
118+
```bash
119+
npm run sync-changelog:test
120+
```
121+
122+
This ensures that the local changelog always reflects the latest release information from GitHub, preventing the documentation from becoming outdated.
123+

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@
4848
"pretest": "npm run compile-tests && npm run compile && npm run lint",
4949
"check-types": "tsc --noEmit",
5050
"lint": "eslint src",
51-
"test": "vscode-test"
51+
"test": "vscode-test",
52+
"sync-changelog": "node scripts/sync-changelog.js",
53+
"sync-changelog:test": "node scripts/sync-changelog.js --test"
5254
},
5355
"devDependencies": {
5456
"@types/mocha": "^10.0.10",

0 commit comments

Comments
 (0)