name: Release
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version:
description: 'Version to release (e.g., 0.1.0)'
required: true
type: string
prerelease:
description: 'Mark as pre-release'
required: false
type: boolean
default: false
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
validate:
name: Validate Release
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
is_prerelease: ${{ steps.version.outputs.is_prerelease }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Extract version
id: version
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
VERSION="${{ github.event.inputs.version }}"
IS_PRERELEASE="${{ github.event.inputs.prerelease }}"
else
VERSION=${GITHUB_REF#refs/tags/v}
# Check if version contains pre-release identifiers
if [[ $VERSION =~ (alpha|beta|rc) ]]; then
IS_PRERELEASE=true
else
IS_PRERELEASE=false
fi
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "is_prerelease=$IS_PRERELEASE" >> $GITHUB_OUTPUT
echo "Release version: $VERSION (prerelease: $IS_PRERELEASE)"
- name: Validate version format
run: |
if [[ ! "${{ steps.version.outputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+(\.[0-9]+)?)?$ ]]; then
echo "Invalid version format: ${{ steps.version.outputs.version }}"
exit 1
fi
test:
name: Test Before Release
runs-on: ${{ matrix.os }}
needs: validate
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Cache cargo
uses: Swatinem/rust-cache@v2
- name: Run tests
run: cargo test --lib --verbose
- name: Build release
run: cargo build --release --verbose
build_binaries:
name: Build Release Binaries
runs-on: ${{ matrix.os }}
needs: [validate, test]
strategy:
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
artifact_name: rez-lsp-server-linux-x64
asset_name: rez-lsp-server-linux-x64
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
artifact_name: rez-lsp-server-linux-arm64
asset_name: rez-lsp-server-linux-arm64
cross: true
- os: windows-latest
target: x86_64-pc-windows-msvc
artifact_name: rez-lsp-server-windows-x64.exe
asset_name: rez-lsp-server-windows-x64.exe
- os: windows-latest
target: aarch64-pc-windows-msvc
artifact_name: rez-lsp-server-windows-arm64.exe
asset_name: rez-lsp-server-windows-arm64.exe
- os: macos-latest
target: x86_64-apple-darwin
artifact_name: rez-lsp-server-macos-x64
asset_name: rez-lsp-server-macos-x64
- os: macos-latest
target: aarch64-apple-darwin
artifact_name: rez-lsp-server-macos-arm64
asset_name: rez-lsp-server-macos-arm64
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Cache cargo
uses: Swatinem/rust-cache@v2
with:
shared-key: ${{ matrix.target }}
- name: Install cross-compilation tools
if: matrix.cross == true
run: |
cargo install cross --git https://github.com/cross-rs/cross
- name: Setup cross-compilation for ARM64 Linux
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu
- name: Build binary (native)
if: matrix.cross != true
run: cargo build --release --target ${{ matrix.target }}
- name: Build binary (cross-compile)
if: matrix.cross == true
run: cross build --release --target ${{ matrix.target }}
- name: Prepare artifact (Unix)
if: matrix.os != 'windows-latest'
run: |
cp target/${{ matrix.target }}/release/rez-lsp-server rez-lsp-server
chmod +x rez-lsp-server
tar -czf ${{ matrix.artifact_name }}.tar.gz rez-lsp-server
- name: Prepare artifact (Windows)
if: matrix.os == 'windows-latest'
run: |
copy target\${{ matrix.target }}\release\rez-lsp-server.exe rez-lsp-server.exe
7z a ${{ matrix.artifact_name }}.zip rez-lsp-server.exe
- name: Upload binary artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.asset_name }}
path: |
${{ matrix.artifact_name }}.tar.gz
${{ matrix.artifact_name }}.zip
retention-days: 30
build_vscode_extension:
name: Build VSCode Extension (${{ matrix.code-target }})
runs-on: ubuntu-latest
needs: [validate, build_binaries]
strategy:
matrix:
include:
- code-target: win32-x64
server-target: x86_64-pc-windows-msvc
server-name: rez-lsp-server.exe
- code-target: linux-x64
server-target: x86_64-unknown-linux-gnu
server-name: rez-lsp-server
- code-target: darwin-x64
server-target: x86_64-apple-darwin
server-name: rez-lsp-server
- code-target: darwin-arm64
server-target: aarch64-apple-darwin
server-name: rez-lsp-server
outputs:
extension_path: ${{ steps.package.outputs.extension_path }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup vx
uses: loonghao/vx@main
with:
tools: node
- name: Set artifact name
id: artifact_name
run: |
case "${{ matrix.code-target }}" in
"win32-x64")
echo "artifact_name=rez-lsp-server-windows-x64.exe" >> $GITHUB_OUTPUT
;;
"linux-x64")
echo "artifact_name=rez-lsp-server-linux-x64" >> $GITHUB_OUTPUT
;;
"darwin-x64")
echo "artifact_name=rez-lsp-server-macos-x64" >> $GITHUB_OUTPUT
;;
"darwin-arm64")
echo "artifact_name=rez-lsp-server-macos-arm64" >> $GITHUB_OUTPUT
;;
*)
echo "❌ Unknown code target: ${{ matrix.code-target }}"
exit 1
;;
esac
echo "🎯 Using artifact: $(cat $GITHUB_OUTPUT | grep artifact_name | cut -d'=' -f2)"
- name: Download server binary
uses: actions/download-artifact@v4
with:
name: ${{ steps.artifact_name.outputs.artifact_name }}
path: ./server-artifacts
- name: Extract and prepare server binary
run: |
echo "🔍 Debugging artifact download for ${{ matrix.code-target }}"
echo "Expected server binary name: ${{ matrix.server-name }}"
echo "Server artifacts directory contents:"
ls -la ./server-artifacts/
mkdir -p vscode-extension/server temp
if [[ "${{ matrix.code-target }}" == "win32-x64" ]]; then
echo "📦 Extracting Windows binary from ZIP..."
unzip -l server-artifacts/*.zip
unzip server-artifacts/*.zip -d temp/
echo "Contents of temp directory:"
ls -la temp/
cp temp/rez-lsp-server.exe vscode-extension/server/rez-lsp-server.exe
else
echo "📦 Extracting Unix binary from tar.gz..."
tar -tzf server-artifacts/*.tar.gz
tar -xzf server-artifacts/*.tar.gz -C temp/
echo "Contents of temp directory:"
ls -la temp/
cp temp/rez-lsp-server vscode-extension/server/rez-lsp-server
chmod +x vscode-extension/server/rez-lsp-server
fi
echo "✅ Final server directory contents:"
ls -la vscode-extension/server/
- name: Update extension version
working-directory: vscode-extension
run: |
npm version ${{ needs.validate.outputs.version }} --no-git-tag-version
- name: Install extension dependencies
working-directory: vscode-extension
run: npm install
- name: Compile extension
working-directory: vscode-extension
run: npm run compile
- name: Validate server binary
run: |
echo "🔍 Validating server binary before packaging..."
if [[ "${{ matrix.code-target }}" == "win32-x64" ]]; then
SERVER_PATH="vscode-extension/server/rez-lsp-server.exe"
else
SERVER_PATH="vscode-extension/server/rez-lsp-server"
fi
if [[ ! -f "$SERVER_PATH" ]]; then
echo "❌ Server binary not found at: $SERVER_PATH"
echo "Available files in server directory:"
ls -la vscode-extension/server/ || echo "Server directory does not exist"
exit 1
fi
echo "✅ Server binary found: $SERVER_PATH"
echo "📊 Binary size: $(du -h "$SERVER_PATH" | cut -f1)"
# Test if binary is executable (Unix only)
if [[ "${{ matrix.code-target }}" != "win32-x64" ]]; then
if [[ ! -x "$SERVER_PATH" ]]; then
echo "❌ Server binary is not executable"
exit 1
fi
echo "✅ Server binary is executable"
fi
- name: Package extension
id: package
working-directory: vscode-extension
run: |
echo "📦 Installing vsce..."
npm install -g @vscode/vsce
EXTENSION_FILE="rez-lsp-extension-${{ matrix.code-target }}-${{ needs.validate.outputs.version }}.vsix"
echo "🎯 Target extension file: $EXTENSION_FILE"
echo "🏷️ Target platform: ${{ matrix.code-target }}"
echo "📋 Version: ${{ needs.validate.outputs.version }}"
echo "🔄 Pre-release: ${{ needs.validate.outputs.is_prerelease }}"
# Validate package.json before packaging
echo "🔍 Validating package.json..."
node -e "
const pkg = require('./package.json');
console.log('Package name:', pkg.name);
console.log('Package version:', pkg.version);
console.log('Package publisher:', pkg.publisher);
if (!pkg.name || !pkg.version || !pkg.publisher) {
console.error('❌ Missing required package.json fields');
process.exit(1);
}
console.log('✅ package.json validation passed');
"
echo "📦 Packaging extension..."
if [[ "${{ needs.validate.outputs.is_prerelease }}" == "true" ]]; then
vsce package --target ${{ matrix.code-target }} --pre-release --out "$EXTENSION_FILE" --verbose
else
vsce package --target ${{ matrix.code-target }} --out "$EXTENSION_FILE" --verbose
fi
# Validate the generated package
if [[ ! -f "$EXTENSION_FILE" ]]; then
echo "❌ Extension package was not created: $EXTENSION_FILE"
exit 1
fi
echo "✅ Extension packaged successfully: $EXTENSION_FILE"
echo "📊 Package size: $(du -h "$EXTENSION_FILE" | cut -f1)"
echo "extension_path=vscode-extension/$EXTENSION_FILE" >> $GITHUB_OUTPUT
- name: Upload extension artifact
uses: actions/upload-artifact@v4
with:
name: vscode-extension-${{ matrix.code-target }}
path: ${{ steps.package.outputs.extension_path }}
retention-days: 30
publish_vscode_extension:
name: Publish VSCode Extension
runs-on: ubuntu-latest
needs: [validate, build_vscode_extension]
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && !contains(github.event.head_commit.message, '[skip publish]')
environment: vscode-marketplace
steps:
- name: Setup vx
uses: loonghao/vx@main
with:
tools: node
- name: Install vsce and ovsx
run: |
npm install -g @vscode/vsce ovsx
- name: Download all extension artifacts
uses: actions/download-artifact@v4
with:
path: ./extensions
- name: Check extension versions
run: |
echo "🔍 Checking extension versions before publishing..."
for vsix_file in extensions/*.vsix; do
if [[ -f "$vsix_file" ]]; then
echo "📦 Checking: $vsix_file"
# Extract package.json from VSIX to check version
unzip -q "$vsix_file" extension/package.json -d temp_extract/
VERSION=$(node -p "require('./temp_extract/extension/package.json').version")
NAME=$(node -p "require('./temp_extract/extension/package.json').name")
echo "Extension: $NAME, Version: $VERSION"
rm -rf temp_extract/
fi
done
- name: Publish to Visual Studio Marketplace
run: |
echo "🚀 Publishing to Visual Studio Marketplace..."
for vsix_file in extensions/*.vsix; do
if [[ -f "$vsix_file" ]]; then
echo "📦 Publishing: $vsix_file"
if vsce publish --packagePath "$vsix_file" --pat "${{ secrets.VSCODE_MARKETPLACE_TOKEN }}"; then
echo "✅ Successfully published to VS Marketplace: $vsix_file"
else
echo "⚠️ Failed to publish to VS Marketplace: $vsix_file (may already exist)"
fi
fi
done
env:
VSCODE_MARKETPLACE_TOKEN: ${{ secrets.VSCODE_MARKETPLACE_TOKEN }}
continue-on-error: true
- name: Publish to Open VSX Registry
run: |
echo "🚀 Publishing to Open VSX Registry..."
for vsix_file in extensions/*.vsix; do
if [[ -f "$vsix_file" ]]; then
echo "📦 Publishing: $vsix_file"
if ovsx publish "$vsix_file" --pat "${{ secrets.OPEN_VSX_TOKEN }}"; then
echo "✅ Successfully published to Open VSX: $vsix_file"
else
echo "⚠️ Failed to publish to Open VSX: $vsix_file (may already exist)"
fi
fi
done
env:
OPEN_VSX_TOKEN: ${{ secrets.OPEN_VSX_TOKEN }}
continue-on-error: true
create_github_release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs: [validate, build_binaries, build_vscode_extension]
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: ./artifacts
- name: Prepare release assets
run: |
mkdir -p release-assets
# Copy server binaries
find artifacts -name "*.tar.gz" -o -name "*.zip" | while read file; do
cp "$file" release-assets/
done
# Copy VSCode extensions
find artifacts -name "*.vsix" | while read file; do
cp "$file" release-assets/
done
ls -la release-assets/
- name: Generate release notes
id: release_notes
run: |
VERSION="${{ needs.validate.outputs.version }}"
# Extract changelog for this version
if [[ -f CHANGELOG.md ]]; then
# Extract section between this version and the next
awk "/## \[$VERSION\]/,/## \[/{if(/## \[/ && !/## \[$VERSION\]/) exit; print}" CHANGELOG.md > release_notes.md
else
echo "Release $VERSION" > release_notes.md
echo "" >> release_notes.md
echo "See [CHANGELOG.md](CHANGELOG.md) for details." >> release_notes.md
fi
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.validate.outputs.version }}
name: Release v${{ needs.validate.outputs.version }}
body_path: release_notes.md
prerelease: ${{ needs.validate.outputs.is_prerelease }}
files: release-assets/*
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}