Skip to content

Commit 24e9592

Browse files
Copilotrajbos
andcommitted
Automate release pipeline: one-trigger workflow with marketplace publish, changelog updates, and proper release notes
- Rewrite release.yml to generate notes from merged PRs (fixes empty release notes) - Update CHANGELOG.md before VSIX packaging (fixes always-out-of-date changelog) - Add publish job for automated VS Code Marketplace publishing via VSCE_PAT secret - Add update-changelog job to create PR with synced CHANGELOG.md after release - Add publish_marketplace workflow input and upload VSIX as workflow artifact - Update sync-release-notes.yml to manual-only (avoid duplicate PRs) - Update CONTRIBUTING.md with simplified one-step release process - Update publish.ps1 header to mark as manual fallback - Update pre-release.js checklist to reflect new streamlined process Co-authored-by: rajbos <6085745+rajbos@users.noreply.github.com>
1 parent ed743c1 commit 24e9592

File tree

5 files changed

+247
-114
lines changed

5 files changed

+247
-114
lines changed

.github/workflows/release.yml

Lines changed: 194 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@ name: Release
33
on:
44
push:
55
tags:
6-
- 'v*' # Triggers on version tags like v1.0.0, v1.2.3, etc.
7-
workflow_dispatch: # Allows manual trigger from GitHub UI
6+
- 'v*' # Tag push: build + GitHub release only (no marketplace publish)
7+
workflow_dispatch: # Manual trigger: full pipeline including marketplace publish
88
inputs:
99
create_tag:
1010
description: 'Create tag from package.json version'
1111
required: false
1212
default: 'true'
1313
type: boolean
14+
publish_marketplace:
15+
description: 'Publish to VS Code Marketplace after creating the release'
16+
required: false
17+
default: 'true'
18+
type: boolean
1419

1520
permissions:
1621
contents: read
@@ -20,6 +25,10 @@ jobs:
2025
permissions:
2126
contents: write
2227
runs-on: ubuntu-latest
28+
outputs:
29+
version: ${{ steps.extract_version.outputs.tag_version }}
30+
tag_name: ${{ steps.tag_name.outputs.tag_name }}
31+
vsix_file: ${{ steps.vsix_filename.outputs.vsix_file }}
2332

2433
steps:
2534
- name: Harden the runner (Audit all outbound calls)
@@ -123,6 +132,47 @@ jobs:
123132
fi
124133
echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT
125134
echo "Tag name: $TAG_NAME"
135+
136+
- name: Generate release notes
137+
env:
138+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
139+
run: |
140+
TAG_NAME="${{ steps.tag_name.outputs.tag_name }}"
141+
VERSION="${{ steps.extract_version.outputs.tag_version }}"
142+
echo "Generating release notes for $TAG_NAME..."
143+
144+
# Use GitHub API to auto-generate notes from merged PRs since last release
145+
if gh api repos/${{ github.repository }}/releases/generate-notes \
146+
-f tag_name="${TAG_NAME}" \
147+
--jq '.body' > /tmp/release_notes.md 2>/dev/null && [ -s /tmp/release_notes.md ]; then
148+
echo "✅ Generated release notes from merged PRs"
149+
else
150+
echo "Release ${VERSION}" > /tmp/release_notes.md
151+
echo "⚠️ Could not auto-generate notes, using fallback"
152+
fi
153+
154+
echo "--- Release notes preview ---"
155+
cat /tmp/release_notes.md
156+
echo "---"
157+
158+
- name: Update CHANGELOG.md for VSIX packaging
159+
run: |
160+
VERSION="${{ steps.extract_version.outputs.tag_version }}"
161+
node -e "
162+
const fs = require('fs');
163+
const version = process.argv[1];
164+
const notes = fs.readFileSync('/tmp/release_notes.md', 'utf8').trim();
165+
let changelog = fs.readFileSync('CHANGELOG.md', 'utf8');
166+
const marker = '## [Unreleased]';
167+
const idx = changelog.indexOf(marker);
168+
if (idx >= 0) {
169+
const insertAt = changelog.indexOf('\n', idx) + 1;
170+
const section = '\n## [' + version + ']\n\n' + notes + '\n';
171+
changelog = changelog.slice(0, insertAt) + section + changelog.slice(insertAt);
172+
}
173+
fs.writeFileSync('CHANGELOG.md', changelog);
174+
console.log('✅ Updated CHANGELOG.md with v' + version + ' notes for VSIX packaging');
175+
" "$VERSION"
126176
127177
- name: Install dependencies
128178
run: npm ci
@@ -158,22 +208,13 @@ jobs:
158208
VSIX_FILE=$(ls *.vsix | head -n 1)
159209
echo "vsix_file=$VSIX_FILE" >> $GITHUB_OUTPUT
160210
echo "VSIX file: $VSIX_FILE"
161-
162-
- name: Generate release notes
163-
run: |
164-
# Extract the latest changes from CHANGELOG.md if it has been updated
165-
# Write to a file to avoid shell interpretation issues with special characters
166-
if grep -q "## \[.*\]" CHANGELOG.md; then
167-
# Try to extract the latest version section from changelog
168-
NOTES=$(sed -n '/## \[.*\]/,/## \[.*\]/p' CHANGELOG.md | head -n -1 | tail -n +2)
169-
if [ -n "$NOTES" ]; then
170-
echo "$NOTES" > /tmp/release_notes.md
171-
else
172-
echo "Release ${{ steps.extract_version.outputs.tag_version }}" > /tmp/release_notes.md
173-
fi
174-
else
175-
echo "Release ${{ steps.extract_version.outputs.tag_version }}" > /tmp/release_notes.md
176-
fi
211+
212+
- name: Upload VSIX as workflow artifact
213+
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
214+
with:
215+
name: vsix-package
216+
path: ./${{ steps.vsix_filename.outputs.vsix_file }}
217+
retention-days: 90
177218

178219
- name: Create Release
179220
id: create_release
@@ -185,7 +226,7 @@ jobs:
185226
set -o pipefail
186227
echo "Creating release for tag: ${{ steps.tag_name.outputs.tag_name }}"
187228
188-
# Create release with notes from file and upload VSIX file
229+
# Create release with auto-generated notes and upload VSIX file
189230
gh release create "${{ steps.tag_name.outputs.tag_name }}" \
190231
--title "Release ${{ steps.extract_version.outputs.tag_version }}" \
191232
--notes-file /tmp/release_notes.md \
@@ -217,3 +258,137 @@ jobs:
217258
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
218259
exit 1
219260
fi
261+
262+
publish:
263+
needs: release
264+
if: github.event_name == 'workflow_dispatch' && inputs.publish_marketplace
265+
runs-on: ubuntu-latest
266+
permissions:
267+
contents: read
268+
269+
steps:
270+
- name: Harden the runner (Audit all outbound calls)
271+
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
272+
with:
273+
egress-policy: audit
274+
275+
- name: Checkout code
276+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
277+
278+
- name: Setup Node.js
279+
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
280+
with:
281+
node-version: '20.x'
282+
cache: 'npm'
283+
284+
- name: Install dependencies
285+
run: npm ci
286+
287+
- name: Download VSIX from release
288+
id: download_vsix
289+
env:
290+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
291+
run: |
292+
TAG_NAME="${{ needs.release.outputs.tag_name }}"
293+
echo "Downloading VSIX from release $TAG_NAME..."
294+
gh release download "$TAG_NAME" \
295+
--repo "${{ github.repository }}" \
296+
--pattern "*.vsix" \
297+
--dir .
298+
VSIX_FILE=$(ls *.vsix | head -n 1)
299+
echo "Downloaded: $VSIX_FILE"
300+
echo "vsix_file=$VSIX_FILE" >> $GITHUB_OUTPUT
301+
302+
- name: Publish to VS Code Marketplace
303+
id: publish
304+
env:
305+
VSCE_PAT: ${{ secrets.VSCE_PAT }}
306+
run: |
307+
if [ -z "$VSCE_PAT" ]; then
308+
echo "❌ VSCE_PAT secret is not configured."
309+
echo " Add it at: Settings → Secrets and variables → Actions"
310+
echo " Create a PAT at https://dev.azure.com with 'Marketplace (Publish)' scope"
311+
exit 1
312+
fi
313+
314+
echo "Publishing ${{ steps.download_vsix.outputs.vsix_file }} to VS Code Marketplace..."
315+
npx vsce publish \
316+
--packagePath "${{ steps.download_vsix.outputs.vsix_file }}" \
317+
--pat "$VSCE_PAT"
318+
319+
- name: Publish Summary
320+
if: always()
321+
run: |
322+
echo "# 🚀 VS Code Marketplace" >> $GITHUB_STEP_SUMMARY
323+
echo "" >> $GITHUB_STEP_SUMMARY
324+
if [ "${{ steps.publish.outcome }}" == "success" ]; then
325+
echo "✅ Extension v${{ needs.release.outputs.version }} published to the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=RobBos.copilot-token-tracker)" >> $GITHUB_STEP_SUMMARY
326+
else
327+
echo "❌ Failed to publish v${{ needs.release.outputs.version }} to marketplace." >> $GITHUB_STEP_SUMMARY
328+
echo "" >> $GITHUB_STEP_SUMMARY
329+
echo "Ensure the \`VSCE_PAT\` secret is configured with a valid Azure DevOps PAT" >> $GITHUB_STEP_SUMMARY
330+
echo "with the \`Marketplace (Publish)\` scope for all accessible organizations." >> $GITHUB_STEP_SUMMARY
331+
fi
332+
333+
update-changelog:
334+
needs: release
335+
if: always() && needs.release.result == 'success'
336+
runs-on: ubuntu-latest
337+
permissions:
338+
contents: write
339+
pull-requests: write
340+
341+
steps:
342+
- name: Harden the runner (Audit all outbound calls)
343+
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
344+
with:
345+
egress-policy: audit
346+
347+
- name: Checkout code
348+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
349+
with:
350+
fetch-depth: 0
351+
352+
- name: Setup Node.js
353+
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
354+
with:
355+
node-version: '20.x'
356+
357+
- name: Sync CHANGELOG.md from GitHub releases
358+
env:
359+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
360+
run: node scripts/sync-changelog.js
361+
362+
- name: Check for changes
363+
id: changes
364+
run: |
365+
if git diff --quiet CHANGELOG.md; then
366+
echo "changed=false" >> $GITHUB_OUTPUT
367+
echo "ℹ️ No changes needed"
368+
else
369+
echo "changed=true" >> $GITHUB_OUTPUT
370+
echo "Changes detected in CHANGELOG.md"
371+
fi
372+
373+
- name: Create Pull Request
374+
if: steps.changes.outputs.changed == 'true'
375+
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
376+
with:
377+
branch: update-changelog
378+
title: "docs: sync CHANGELOG.md with v${{ needs.release.outputs.version }} release notes"
379+
body: |
380+
Automatically syncs CHANGELOG.md with the latest GitHub release notes.
381+
382+
Triggered by release workflow: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
383+
commit-message: "docs: sync CHANGELOG.md with GitHub release notes"
384+
385+
- name: Changelog Summary
386+
if: always()
387+
run: |
388+
echo "# 📝 Changelog Update" >> $GITHUB_STEP_SUMMARY
389+
echo "" >> $GITHUB_STEP_SUMMARY
390+
if [ "${{ steps.changes.outputs.changed }}" == "true" ]; then
391+
echo "A pull request has been created to update CHANGELOG.md." >> $GITHUB_STEP_SUMMARY
392+
else
393+
echo "CHANGELOG.md is already up to date." >> $GITHUB_STEP_SUMMARY
394+
fi
Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
name: Sync Release Notes
22

3+
# Manual-only trigger — automated changelog sync is handled by the Release workflow.
4+
# Use this to re-sync CHANGELOG.md from GitHub releases at any time.
35
on:
4-
workflow_dispatch: # Manual trigger
5-
release:
6-
types: [published, edited] # Automatic trigger when releases are published or edited
6+
workflow_dispatch:
77

88
permissions:
99
contents: read
@@ -12,7 +12,8 @@ jobs:
1212
sync-release-notes:
1313
runs-on: ubuntu-latest
1414
permissions:
15-
contents: write # Need write permission to update CHANGELOG.md
15+
contents: write
16+
pull-requests: write
1617

1718
steps:
1819
- name: Harden the runner (Audit all outbound calls)
@@ -23,7 +24,7 @@ jobs:
2324
- name: Checkout code
2425
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2526
with:
26-
fetch-depth: 0 # Fetch all history so we can work with all releases
27+
fetch-depth: 0
2728

2829
- name: Setup Node.js
2930
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
@@ -33,9 +34,7 @@ jobs:
3334
- name: Sync GitHub release notes to CHANGELOG.md
3435
env:
3536
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
36-
run: |
37-
# Run the sync script from the scripts directory
38-
node scripts/sync-changelog.js
37+
run: node scripts/sync-changelog.js
3938

4039
- name: Check for changes
4140
id: changes
@@ -47,24 +46,6 @@ jobs:
4746
echo "changed=true" >> $GITHUB_OUTPUT
4847
echo "Changes detected in CHANGELOG.md"
4948
fi
50-
51-
- name: Ensure on main branch
52-
if: steps.changes.outputs.changed == 'true'
53-
run: |
54-
git checkout -b update-changelog
55-
56-
- name: Commit and push changes
57-
if: steps.changes.outputs.changed == 'true'
58-
run: |
59-
git config --local user.email "action@github.com"
60-
git config --local user.name "GitHub Action"
61-
git add CHANGELOG.md
62-
git commit -m "docs: sync CHANGELOG.md with GitHub release notes
63-
64-
This commit automatically updates the CHANGELOG.md file to match
65-
the release notes from GitHub releases, ensuring consistency
66-
between local documentation and published releases."
67-
git push -u origin update-changelog
6849
6950
- name: Create Pull Request
7051
if: steps.changes.outputs.changed == 'true'
@@ -73,14 +54,15 @@ jobs:
7354
branch: update-changelog
7455
title: "docs: sync CHANGELOG.md with GitHub release notes"
7556
body: |
76-
This pull request updates the CHANGELOG.md file to reflect the latest
77-
GitHub release notes, ensuring that the changelog is always up to date
78-
with the published releases.
57+
Automatically syncs CHANGELOG.md with the latest GitHub release notes.
58+
59+
Triggered by: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
60+
commit-message: "docs: sync CHANGELOG.md with GitHub release notes"
7961

8062
- name: Summary
8163
run: |
8264
if [ "${{ steps.changes.outputs.changed }}" == "true" ]; then
83-
echo "✅ CHANGELOG.md has been successfully updated with GitHub release notes"
65+
echo "✅ A PR has been created to update CHANGELOG.md with GitHub release notes"
8466
else
8567
echo "ℹ️ CHANGELOG.md was already up to date with GitHub release notes"
8668
fi

0 commit comments

Comments
 (0)