Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 203 additions & 55 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
on:
push:
tags:
- 'v*' # Triggers on version tags like v1.0.0, v1.2.3, etc.
workflow_dispatch: # Allows manual trigger from GitHub UI
- 'v*' # Tag push: build + GitHub release only (no marketplace publish)
workflow_dispatch: # Manual trigger: full pipeline including marketplace publish
inputs:
create_tag:
description: 'Create tag from package.json version'
required: false
default: 'true'
default: true
type: boolean
publish_marketplace:
description: 'Publish to VS Code Marketplace after creating the release'
required: false
default: true
type: boolean

permissions:
Expand All @@ -20,6 +25,10 @@
permissions:
contents: write
runs-on: ubuntu-latest
outputs:
version: ${{ steps.extract_version.outputs.tag_version }}
tag_name: ${{ steps.tag_name.outputs.tag_name }}
vsix_file: ${{ steps.vsix_filename.outputs.vsix_file }}

steps:
- name: Harden the runner (Audit all outbound calls)
Expand All @@ -38,7 +47,7 @@

- name: Determine trigger type
id: trigger_type
run: |

Check failure on line 50 in .github/workflows/release.yml

View workflow job for this annotation

GitHub Actions / run-actionlint

shellcheck reported issue in this script: SC2086:info:5:29: Double quote to prevent globbing and word splitting

Check failure on line 50 in .github/workflows/release.yml

View workflow job for this annotation

GitHub Actions / run-actionlint

shellcheck reported issue in this script: SC2086:info:2:28: Double quote to prevent globbing and word splitting
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "is_manual=true" >> $GITHUB_OUTPUT
echo "Triggered manually via workflow_dispatch"
Expand All @@ -49,46 +58,11 @@

- name: Extract version from package.json
id: package_version
run: |

Check failure on line 61 in .github/workflows/release.yml

View workflow job for this annotation

GitHub Actions / run-actionlint

shellcheck reported issue in this script: SC2086:info:2:44: Double quote to prevent globbing and word splitting
PACKAGE_VERSION=$(node -p "require('./package.json').version")
echo "package_version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT
echo "Package version: $PACKAGE_VERSION"

- name: Create tag for manual trigger
if: steps.trigger_type.outputs.is_manual == 'true' && inputs.create_tag
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="v${{ steps.package_version.outputs.package_version }}"
echo "Creating tag: $VERSION"

# Check if tag already exists on remote using exit code
if git ls-remote --exit-code --tags origin "refs/tags/$VERSION" >/dev/null 2>&1; then
echo "❌ Tag $VERSION already exists on remote!"
echo "Please update the version in package.json or delete the existing tag."
exit 1
fi

# Create and push the tag
git config --local user.name "github-actions[bot]"
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "$VERSION" -m "Release $VERSION"
git push origin "$VERSION"

# Verify tag was created successfully on remote
echo "Verifying tag was created on remote..."
for i in {1..5}; do
if git ls-remote --exit-code --tags origin "refs/tags/$VERSION" >/dev/null 2>&1; then
echo "✅ Tag $VERSION created and verified on remote"
exit 0
fi
echo "Waiting for tag to propagate (attempt $i/5)..."
sleep 2
done

echo "❌ Failed to verify tag creation on remote"
exit 1

- name: Extract version from tag
id: extract_version
run: |
Expand Down Expand Up @@ -123,6 +97,116 @@
fi
echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT
echo "Tag name: $TAG_NAME"

- name: Generate release notes
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG_NAME="${{ steps.tag_name.outputs.tag_name }}"
VERSION="${{ steps.extract_version.outputs.tag_version }}"
echo "Generating release notes for $TAG_NAME..."

# Use GitHub API to auto-generate notes from merged PRs since last release
if gh api repos/${{ github.repository }}/releases/generate-notes \
-f tag_name="${TAG_NAME}" \
--jq '.body' > /tmp/release_notes.md 2>/tmp/generate_notes_err.txt && [ -s /tmp/release_notes.md ]; then
echo "✅ Generated release notes from merged PRs"
else
echo "Release ${VERSION}" > /tmp/release_notes.md
echo "⚠️ Could not auto-generate notes, using fallback"
if [ -s /tmp/generate_notes_err.txt ]; then
echo " Error: $(cat /tmp/generate_notes_err.txt)"
fi
fi

echo "--- Release notes preview ---"
cat /tmp/release_notes.md
echo "---"

- name: Update CHANGELOG.md for VSIX packaging
run: |
VERSION="${{ steps.extract_version.outputs.tag_version }}"
node -e "
const fs = require('fs');
const version = process.argv[1];
const notes = fs.readFileSync('/tmp/release_notes.md', 'utf8').trim();
let changelog = fs.readFileSync('CHANGELOG.md', 'utf8');
const marker = '## [Unreleased]';
const idx = changelog.indexOf(marker);
if (idx >= 0) {
const insertAt = changelog.indexOf('\n', idx) + 1;
const section = '\n## [' + version + ']\n\n' + notes + '\n';
changelog = changelog.slice(0, insertAt) + section + changelog.slice(insertAt);
}
fs.writeFileSync('CHANGELOG.md', changelog);
console.log('✅ Updated CHANGELOG.md with v' + version + ' notes for VSIX packaging');
" "$VERSION"

- name: Create changelog branch and commit
if: steps.trigger_type.outputs.is_manual == 'true'
id: changelog_branch
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ steps.extract_version.outputs.tag_version }}"
BRANCH="changelog/v${VERSION}"
echo "branch=$BRANCH" >> $GITHUB_OUTPUT

git config --local user.name "github-actions[bot]"
git config --local user.email "github-actions[bot]@users.noreply.github.com"

# Check if branch already exists on remote
if git ls-remote --exit-code --heads origin "refs/heads/$BRANCH" >/dev/null 2>&1; then
echo "❌ Branch $BRANCH already exists on remote!"
echo "Delete it or bump the version before re-running."
exit 1
fi

if git diff --quiet CHANGELOG.md; then
echo "ℹ️ No changes to CHANGELOG.md, skipping branch + commit"
exit 0
fi

git checkout -b "$BRANCH"
git add CHANGELOG.md
git commit -m "docs: update CHANGELOG.md for v${VERSION}"
git push origin "$BRANCH"
echo "✅ Pushed CHANGELOG.md update to $BRANCH"

- name: Create tag for manual trigger
if: steps.trigger_type.outputs.is_manual == 'true' && inputs.create_tag == true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="v${{ steps.package_version.outputs.package_version }}"
echo "Creating tag: $VERSION"

# Check if tag already exists on remote using exit code
if git ls-remote --exit-code --tags origin "refs/tags/$VERSION" >/dev/null 2>&1; then
echo "❌ Tag $VERSION already exists on remote!"
echo "Please update the version in package.json or delete the existing tag."
exit 1
fi

# Create and push the tag
git config --local user.name "github-actions[bot]"
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "$VERSION" -m "Release $VERSION"
git push origin "$VERSION"

# Verify tag was created successfully on remote
echo "Verifying tag was created on remote..."
for i in {1..5}; do
if git ls-remote --exit-code --tags origin "refs/tags/$VERSION" >/dev/null 2>&1; then
echo "✅ Tag $VERSION created and verified on remote"
exit 0
fi
echo "Waiting for tag to propagate (attempt $i/5)..."
sleep 2
done

echo "❌ Failed to verify tag creation on remote"
exit 1

- name: Install dependencies
run: npm ci
Expand Down Expand Up @@ -158,22 +242,13 @@
VSIX_FILE=$(ls *.vsix | head -n 1)
echo "vsix_file=$VSIX_FILE" >> $GITHUB_OUTPUT
echo "VSIX file: $VSIX_FILE"

- name: Generate release notes
run: |
# Extract the latest changes from CHANGELOG.md if it has been updated
# Write to a file to avoid shell interpretation issues with special characters
if grep -q "## \[.*\]" CHANGELOG.md; then
# Try to extract the latest version section from changelog
NOTES=$(sed -n '/## \[.*\]/,/## \[.*\]/p' CHANGELOG.md | head -n -1 | tail -n +2)
if [ -n "$NOTES" ]; then
echo "$NOTES" > /tmp/release_notes.md
else
echo "Release ${{ steps.extract_version.outputs.tag_version }}" > /tmp/release_notes.md
fi
else
echo "Release ${{ steps.extract_version.outputs.tag_version }}" > /tmp/release_notes.md
fi

- name: Upload VSIX as workflow artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: vsix-package
path: ./${{ steps.vsix_filename.outputs.vsix_file }}
retention-days: 90

- name: Create Release
id: create_release
Expand All @@ -185,7 +260,7 @@
set -o pipefail
echo "Creating release for tag: ${{ steps.tag_name.outputs.tag_name }}"

# Create release with notes from file and upload VSIX file
# Create release with auto-generated notes and upload VSIX file
gh release create "${{ steps.tag_name.outputs.tag_name }}" \
--title "Release ${{ steps.extract_version.outputs.tag_version }}" \
--notes-file /tmp/release_notes.md \
Expand Down Expand Up @@ -217,3 +292,76 @@
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
exit 1
fi

publish:
needs: release
if: github.event_name == 'workflow_dispatch' && inputs.publish_marketplace
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
with:
egress-policy: audit

- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '20.x'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Download VSIX from release
id: download_vsix
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG_NAME="${{ needs.release.outputs.tag_name }}"
echo "Downloading VSIX from release $TAG_NAME..."
gh release download "$TAG_NAME" \
--repo "${{ github.repository }}" \
--pattern "*.vsix" \
--dir .
VSIX_FILE=$(ls *.vsix | head -n 1)
echo "Downloaded: $VSIX_FILE"
echo "vsix_file=$VSIX_FILE" >> $GITHUB_OUTPUT

- name: Publish to VS Code Marketplace
id: publish
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
run: |
if [ -z "$VSCE_PAT" ]; then
echo "❌ VSCE_PAT secret is not configured."
echo " Add it at: Settings → Secrets and variables → Actions"
echo " Create a PAT at https://dev.azure.com with 'Marketplace (Publish)' scope"
exit 1
fi

echo "Publishing ${{ steps.download_vsix.outputs.vsix_file }} to VS Code Marketplace..."
npx vsce publish \
--packagePath "${{ steps.download_vsix.outputs.vsix_file }}" \
--pat "$VSCE_PAT"

- name: Publish Summary
if: always()
run: |
echo "# 🚀 VS Code Marketplace" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.publish.outcome }}" == "success" ]; then
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
else
echo "❌ Failed to publish v${{ needs.release.outputs.version }} to marketplace." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Ensure the \`VSCE_PAT\` secret is configured with a valid Azure DevOps PAT" >> $GITHUB_STEP_SUMMARY
echo "with the \`Marketplace (Publish)\` scope for all accessible organizations." >> $GITHUB_STEP_SUMMARY
fi


42 changes: 12 additions & 30 deletions .github/workflows/sync-release-notes.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
name: Sync Release Notes

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

permissions:
contents: read
Expand All @@ -12,7 +12,8 @@ jobs:
sync-release-notes:
runs-on: ubuntu-latest
permissions:
contents: write # Need write permission to update CHANGELOG.md
contents: write
pull-requests: write

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

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

- name: Check for changes
id: changes
Expand All @@ -47,24 +46,6 @@ jobs:
echo "changed=true" >> $GITHUB_OUTPUT
echo "Changes detected in CHANGELOG.md"
fi

- name: Ensure on main branch
if: steps.changes.outputs.changed == 'true'
run: |
git checkout -b update-changelog

- name: Commit and push changes
if: steps.changes.outputs.changed == 'true'
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add CHANGELOG.md
git commit -m "docs: sync CHANGELOG.md with GitHub release notes

This commit automatically updates the CHANGELOG.md file to match
the release notes from GitHub releases, ensuring consistency
between local documentation and published releases."
git push -u origin update-changelog

- name: Create Pull Request
if: steps.changes.outputs.changed == 'true'
Expand All @@ -73,14 +54,15 @@ jobs:
branch: update-changelog
title: "docs: sync CHANGELOG.md with GitHub release notes"
body: |
This pull request updates the CHANGELOG.md file to reflect the latest
GitHub release notes, ensuring that the changelog is always up to date
with the published releases.
Automatically syncs CHANGELOG.md with the latest GitHub release notes.

Triggered by: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
commit-message: "docs: sync CHANGELOG.md with GitHub release notes"

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