Extensions - Release #32
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - 'v*' # Triggers on version tags like v1.0.0, v1.2.3, etc. | |
| workflow_dispatch: # Allows manual trigger from GitHub UI | |
| inputs: | |
| create_tag: | |
| description: 'Create tag from package.json version' | |
| required: false | |
| default: 'true' | |
| type: boolean | |
| permissions: | |
| contents: read | |
| jobs: | |
| release: | |
| permissions: | |
| contents: write | |
| runs-on: ubuntu-latest | |
| 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: Determine trigger type | |
| id: trigger_type | |
| run: | | |
| if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then | |
| echo "is_manual=true" >> $GITHUB_OUTPUT | |
| echo "Triggered manually via workflow_dispatch" | |
| else | |
| echo "is_manual=false" >> $GITHUB_OUTPUT | |
| echo "Triggered by tag push" | |
| fi | |
| - name: Extract version from package.json | |
| id: package_version | |
| run: | | |
| 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: | | |
| if [ "${{ steps.trigger_type.outputs.is_manual }}" == "true" ]; then | |
| # For manual triggers, use package.json version | |
| TAG_VERSION="${{ steps.package_version.outputs.package_version }}" | |
| else | |
| # For tag triggers, extract from the tag | |
| TAG_VERSION=${GITHUB_REF#refs/tags/v} | |
| fi | |
| echo "tag_version=$TAG_VERSION" >> $GITHUB_OUTPUT | |
| echo "Release version: $TAG_VERSION" | |
| - name: Verify version consistency | |
| run: | | |
| if [ "${{ steps.extract_version.outputs.tag_version }}" != "${{ steps.package_version.outputs.package_version }}" ]; then | |
| echo "❌ Version mismatch!" | |
| echo "Tag version: ${{ steps.extract_version.outputs.tag_version }}" | |
| echo "Package.json version: ${{ steps.package_version.outputs.package_version }}" | |
| echo "Please ensure the tag version matches the version in package.json" | |
| exit 1 | |
| fi | |
| echo "✅ Version check passed: ${{ steps.extract_version.outputs.tag_version }}" | |
| - name: Determine tag name | |
| id: tag_name | |
| run: | | |
| if [ "${{ steps.trigger_type.outputs.is_manual }}" == "true" ]; then | |
| TAG_NAME="v${{ steps.extract_version.outputs.tag_version }}" | |
| else | |
| TAG_NAME="${{ github.ref_name }}" | |
| fi | |
| echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT | |
| echo "Tag name: $TAG_NAME" | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Run linting | |
| run: npm run lint | |
| - name: Run type checking | |
| run: npm run check-types | |
| - name: Compile extension | |
| run: npm run compile | |
| - name: Build production package | |
| run: npm run package | |
| - name: Compile tests | |
| run: npm run compile-tests | |
| - name: Run tests | |
| uses: coactions/setup-xvfb@b6b4fcfb9f5a895edadc3bc76318fae0ac17c8b3 # v1.0.1 | |
| with: | |
| run: npm test | |
| options: -screen 0 1024x768x24 | |
| continue-on-error: false # Fail the release if tests fail | |
| - name: Create VSIX package | |
| run: npx vsce package | |
| - name: Get VSIX filename | |
| id: vsix_filename | |
| run: | | |
| 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: Create Release | |
| id: create_release | |
| shell: bash | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| continue-on-error: true | |
| run: | | |
| set -o pipefail | |
| echo "Creating release for tag: ${{ steps.tag_name.outputs.tag_name }}" | |
| # Create release with notes from file 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 \ | |
| ./${{ steps.vsix_filename.outputs.vsix_file }} 2>&1 | tee /tmp/release_output.txt | |
| - name: Release Summary | |
| if: always() | |
| run: | | |
| if [ "${{ steps.create_release.outcome }}" == "success" ]; then | |
| echo "# ✅ Release Created Successfully" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "🎉 Release ${{ steps.extract_version.outputs.tag_version }} created successfully!" >> $GITHUB_STEP_SUMMARY | |
| echo "📦 VSIX package: ${{ steps.vsix_filename.outputs.vsix_file }}" >> $GITHUB_STEP_SUMMARY | |
| echo "🔗 [Release URL](https://github.com/${{ github.repository }}/releases/tag/${{ steps.tag_name.outputs.tag_name }})" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "# ❌ Release Creation Failed" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "**Version:** ${{ steps.extract_version.outputs.tag_version }}" >> $GITHUB_STEP_SUMMARY | |
| echo "**Tag:** ${{ steps.tag_name.outputs.tag_name }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "## Error Details" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "\`\`\`" >> $GITHUB_STEP_SUMMARY | |
| if [ -f /tmp/release_output.txt ]; then | |
| cat /tmp/release_output.txt >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "No error details captured" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "\`\`\`" >> $GITHUB_STEP_SUMMARY | |
| exit 1 | |
| fi |