mirror of
https://github.com/sbroenne/mcp-server-excel.git
synced 2026-09-19 07:53:08 +08:00
5eafcbfc8f
Co-authored-by: Stefan Broenner <stbrnner@microsoft.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
855 lines
29 KiB
YAML
855 lines
29 KiB
YAML
name: Release All Components
|
|
|
|
# Unified release workflow for all ExcelMcp components:
|
|
# - MCP Server (standalone exe ZIP — primary) + NuGet (.NET tool — secondary)
|
|
# - CLI (standalone exe ZIP — primary) + NuGet (.NET tool — secondary)
|
|
# - VS Code Extension (VSIX + Marketplace)
|
|
# - MCPB (Claude Desktop bundle)
|
|
# - Agent Skills (ZIP package)
|
|
#
|
|
# All components are released with the same version.
|
|
# Trigger: workflow_dispatch with version bump (major/minor/patch) or custom version.
|
|
# The workflow auto-calculates the next version from the latest git tag,
|
|
# creates the git tag after successful builds, then creates the GitHub release.
|
|
#
|
|
# Required GitHub Secrets:
|
|
# - NUGET_USER: Your NuGet.org username (profile name, NOT email)
|
|
# - VSCE_TOKEN: VS Code Marketplace PAT
|
|
# - APPINSIGHTS_CONNECTION_STRING: Application Insights connection string
|
|
#
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
version_bump:
|
|
description: 'Version bump type'
|
|
required: true
|
|
type: choice
|
|
default: 'patch'
|
|
options:
|
|
- major
|
|
- minor
|
|
- patch
|
|
custom_version:
|
|
description: 'Custom version (e.g., 1.2.3, overrides version_bump)'
|
|
required: false
|
|
type: string
|
|
|
|
env:
|
|
DOTNET_VERSION: '10.0.x'
|
|
NODE_VERSION: '22'
|
|
|
|
concurrency:
|
|
group: release
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
# =============================================================================
|
|
# Job 0: Calculate Version
|
|
# =============================================================================
|
|
version:
|
|
name: Calculate Version
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
version: ${{ steps.calculate_version.outputs.version }}
|
|
tag: ${{ steps.calculate_version.outputs.tag }}
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 0 # Fetch all history and tags
|
|
|
|
- name: Calculate version
|
|
id: calculate_version
|
|
run: |
|
|
# If custom version is provided, use it
|
|
if [ -n "${{ inputs.custom_version }}" ]; then
|
|
VERSION="${{ inputs.custom_version }}"
|
|
echo "Using custom version: ${VERSION}"
|
|
else
|
|
# Get the latest tag
|
|
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
|
echo "Latest tag: ${LATEST_TAG}"
|
|
|
|
# Remove 'v' prefix if present
|
|
CURRENT_VERSION="${LATEST_TAG#v}"
|
|
echo "Current version: ${CURRENT_VERSION}"
|
|
|
|
# Parse version components
|
|
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION"
|
|
|
|
# Increment based on bump type
|
|
case "${{ inputs.version_bump }}" in
|
|
major)
|
|
MAJOR=$((MAJOR + 1))
|
|
MINOR=0
|
|
PATCH=0
|
|
;;
|
|
minor)
|
|
MINOR=$((MINOR + 1))
|
|
PATCH=0
|
|
;;
|
|
patch)
|
|
PATCH=$((PATCH + 1))
|
|
;;
|
|
esac
|
|
|
|
VERSION="${MAJOR}.${MINOR}.${PATCH}"
|
|
echo "New version: ${VERSION}"
|
|
fi
|
|
|
|
TAG="v${VERSION}"
|
|
|
|
# Check if tag already exists
|
|
if git rev-parse "$TAG" >/dev/null 2>&1; then
|
|
echo "::error::Tag $TAG already exists"
|
|
exit 1
|
|
fi
|
|
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
|
|
|
# =============================================================================
|
|
# Job 1: Prepare the changelog before packaging
|
|
# =============================================================================
|
|
prepare-release:
|
|
name: Prepare Release Changelog
|
|
needs: [version]
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
pull-requests: read
|
|
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v7
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: Install changesets CLI
|
|
run: npm ci
|
|
|
|
- name: Compile changesets into CHANGELOG.md
|
|
shell: pwsh
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
$releaseDate = Get-Date -AsUTC -Format 'yyyy-MM-dd'
|
|
Set-Content -Path release_date.txt -Value $releaseDate -NoNewline
|
|
./scripts/Build-Changelog.ps1 -Version "${{ needs.version.outputs.version }}" -Date $releaseDate
|
|
|
|
- name: Upload Prepared Changelog
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: release-changelog
|
|
path: |
|
|
CHANGELOG.md
|
|
release_notes_body.md
|
|
release_date.txt
|
|
|
|
# =============================================================================
|
|
# Job 2: Build CLI (standalone exe — primary + NuGet pack — secondary)
|
|
# =============================================================================
|
|
build-cli:
|
|
name: CLI
|
|
needs: [version]
|
|
runs-on: windows-latest
|
|
permissions:
|
|
contents: read
|
|
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- name: Setup .NET
|
|
uses: actions/setup-dotnet@v6
|
|
with:
|
|
dotnet-version: ${{ env.DOTNET_VERSION }}
|
|
|
|
- name: Set Version
|
|
run: |
|
|
$version = "${{ needs.version.outputs.version }}"
|
|
echo "VERSION=$version" >> $env:GITHUB_ENV
|
|
shell: pwsh
|
|
|
|
- name: Restore
|
|
run: dotnet restore src/ExcelMcp.CLI/ExcelMcp.CLI.csproj
|
|
|
|
- name: Build CLI
|
|
run: dotnet build src/ExcelMcp.CLI/ExcelMcp.CLI.csproj --configuration Release --no-restore -p:Version=${{ env.VERSION }} -p:AssemblyVersion=${{ env.VERSION }}.0 -p:FileVersion=${{ env.VERSION }}.0
|
|
|
|
- name: Pack CLI NuGet (secondary distribution)
|
|
run: dotnet pack src/ExcelMcp.CLI/ExcelMcp.CLI.csproj --configuration Release --no-build --output ./cli-nupkg -p:Version=${{ env.VERSION }}
|
|
|
|
- name: Publish CLI (standalone exe — primary distribution)
|
|
run: |
|
|
dotnet publish src/ExcelMcp.CLI/ExcelMcp.CLI.csproj `
|
|
--configuration Release `
|
|
--runtime win-x64 `
|
|
--self-contained true `
|
|
-p:PublishSingleFile=true `
|
|
-p:IncludeNativeLibrariesForSelfExtract=true `
|
|
-p:PublishTrimmed=false `
|
|
-p:PublishReadyToRun=false `
|
|
-p:Version=${{ env.VERSION }} `
|
|
-p:AssemblyVersion=${{ env.VERSION }}.0 `
|
|
-p:FileVersion=${{ env.VERSION }}.0 `
|
|
-p:NuGetAudit=false `
|
|
--output ./cli-publish
|
|
shell: pwsh
|
|
|
|
- name: Create CLI ZIP
|
|
run: |
|
|
$version = $env:VERSION
|
|
New-Item -ItemType Directory -Path "cli-release" -Force
|
|
Copy-Item "cli-publish/excelcli.exe" "cli-release/"
|
|
Copy-Item "README.md" "cli-release/"
|
|
Copy-Item "LICENSE" "cli-release/"
|
|
Compress-Archive -Path "cli-release/*" -DestinationPath "ExcelMcp-CLI-$version-windows.zip"
|
|
shell: pwsh
|
|
|
|
- name: Upload CLI ZIP Artifact
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: cli-zip
|
|
path: ExcelMcp-CLI-*-windows.zip
|
|
|
|
- name: Upload CLI NuGet Artifact
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: cli-nupkg
|
|
path: cli-nupkg/*.nupkg
|
|
|
|
- name: Upload CLI Build Output (for other jobs)
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: cli-build
|
|
path: cli-publish/
|
|
|
|
# =============================================================================
|
|
# Job 2: Build MCP Server (standalone exe — primary + NuGet pack — secondary)
|
|
# =============================================================================
|
|
build-mcp-server:
|
|
name: MCP Server
|
|
needs: [version]
|
|
runs-on: windows-latest
|
|
permissions:
|
|
contents: read
|
|
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- name: Setup .NET
|
|
uses: actions/setup-dotnet@v6
|
|
with:
|
|
dotnet-version: ${{ env.DOTNET_VERSION }}
|
|
|
|
- name: Set Version
|
|
run: |
|
|
$version = "${{ needs.version.outputs.version }}"
|
|
echo "VERSION=$version" >> $env:GITHUB_ENV
|
|
Write-Output "Version: $version"
|
|
shell: pwsh
|
|
|
|
- name: Update Project Versions
|
|
run: |
|
|
.\scripts\Update-McpRegistryMetadata.ps1 `
|
|
-ServerJsonPath "src\ExcelMcp.McpServer\.mcp\server.json" `
|
|
-Version $env:VERSION
|
|
shell: pwsh
|
|
|
|
- name: Restore
|
|
run: dotnet restore src/ExcelMcp.McpServer/ExcelMcp.McpServer.csproj
|
|
|
|
- name: Build
|
|
run: dotnet build src/ExcelMcp.McpServer/ExcelMcp.McpServer.csproj --configuration Release --no-restore -p:Version=${{ env.VERSION }} -p:AssemblyVersion=${{ env.VERSION }}.0 -p:FileVersion=${{ env.VERSION }}.0
|
|
env:
|
|
APPINSIGHTS_CONNECTION_STRING: ${{ secrets.APPINSIGHTS_CONNECTION_STRING }}
|
|
|
|
- name: Pack NuGet (secondary distribution)
|
|
run: dotnet pack src/ExcelMcp.McpServer/ExcelMcp.McpServer.csproj --configuration Release --no-build --output ./nupkg -p:Version=${{ env.VERSION }}
|
|
|
|
- name: Publish MCP Server (standalone exe — primary distribution)
|
|
run: |
|
|
dotnet publish src/ExcelMcp.McpServer/ExcelMcp.McpServer.csproj `
|
|
--configuration Release `
|
|
--runtime win-x64 `
|
|
--self-contained true `
|
|
-p:PublishSingleFile=true `
|
|
-p:IncludeNativeLibrariesForSelfExtract=true `
|
|
-p:PublishTrimmed=false `
|
|
-p:PublishReadyToRun=false `
|
|
-p:Version=${{ env.VERSION }} `
|
|
-p:AssemblyVersion=${{ env.VERSION }}.0 `
|
|
-p:FileVersion=${{ env.VERSION }}.0 `
|
|
-p:NuGetAudit=false `
|
|
--output ./mcp-server-publish
|
|
shell: pwsh
|
|
env:
|
|
APPINSIGHTS_CONNECTION_STRING: ${{ secrets.APPINSIGHTS_CONNECTION_STRING }}
|
|
|
|
- name: Rename exe to mcp-excel.exe
|
|
run: |
|
|
$expectedExe = "mcp-server-publish/Sbroenne.ExcelMcp.McpServer.exe"
|
|
if (Test-Path $expectedExe) {
|
|
Rename-Item $expectedExe "mcp-excel.exe"
|
|
} else {
|
|
Write-Error "Expected executable not found at: $expectedExe"
|
|
Write-Host "Contents of mcp-server-publish/:"
|
|
Get-ChildItem "mcp-server-publish/" | Select-Object Name
|
|
exit 1
|
|
}
|
|
shell: pwsh
|
|
|
|
- name: Create Release Package
|
|
run: |
|
|
$version = $env:VERSION
|
|
New-Item -ItemType Directory -Path "mcp-server-release" -Force
|
|
Copy-Item "mcp-server-publish/mcp-excel.exe" "mcp-server-release/"
|
|
Copy-Item "README.md" "mcp-server-release/"
|
|
Copy-Item "LICENSE" "mcp-server-release/"
|
|
Compress-Archive -Path "mcp-server-release/*" -DestinationPath "ExcelMcp-MCP-Server-$version-windows.zip"
|
|
shell: pwsh
|
|
|
|
- name: Upload ZIP Artifact
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: mcp-server-zip
|
|
path: ExcelMcp-MCP-Server-*-windows.zip
|
|
|
|
- name: Upload NuGet Artifact
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: mcp-server-nupkg
|
|
path: nupkg/*.nupkg
|
|
|
|
# =============================================================================
|
|
# Job 3: Build and Publish VS Code Extension
|
|
# =============================================================================
|
|
build-vscode:
|
|
name: VS Code Extension
|
|
needs: [version, prepare-release]
|
|
runs-on: windows-latest
|
|
permissions:
|
|
contents: read
|
|
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v7
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: Setup .NET
|
|
uses: actions/setup-dotnet@v6
|
|
with:
|
|
dotnet-version: ${{ env.DOTNET_VERSION }}
|
|
|
|
- name: Set Version
|
|
run: |
|
|
$version = "${{ needs.version.outputs.version }}"
|
|
echo "VERSION=$version" >> $env:GITHUB_ENV
|
|
shell: pwsh
|
|
|
|
- name: Synchronize Release Metadata
|
|
run: |
|
|
.\scripts\Update-ReleaseVersionMetadata.ps1 -Version $env:VERSION
|
|
shell: pwsh
|
|
|
|
- name: Download Prepared Changelog
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: release-changelog
|
|
path: release-changelog
|
|
|
|
- name: Sync Prepared Changelog
|
|
run: |
|
|
Copy-Item "release-changelog/CHANGELOG.md" "CHANGELOG.md" -Force
|
|
shell: pwsh
|
|
|
|
- name: Install Dependencies
|
|
run: |
|
|
cd vscode-extension
|
|
npm install
|
|
|
|
- name: Build and Package
|
|
run: |
|
|
cd vscode-extension
|
|
npm run package
|
|
env:
|
|
APPINSIGHTS_CONNECTION_STRING: ${{ secrets.APPINSIGHTS_CONNECTION_STRING }}
|
|
|
|
- name: Rename VSIX
|
|
run: |
|
|
$version = $env:VERSION
|
|
cd vscode-extension
|
|
$vsix = Get-ChildItem -Filter "*.vsix" | Select-Object -First 1
|
|
$targetName = "excelmcp-$version.vsix"
|
|
if ($vsix.Name -ne $targetName) {
|
|
Rename-Item $vsix.FullName -NewName $targetName
|
|
}
|
|
shell: pwsh
|
|
|
|
- name: Upload Artifact
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: vscode-vsix
|
|
path: vscode-extension/excelmcp-*.vsix
|
|
|
|
# =============================================================================
|
|
# Job 4: Build MCPB (Claude Desktop Bundle)
|
|
# =============================================================================
|
|
build-mcpb:
|
|
name: MCPB Bundle
|
|
needs: [version, prepare-release]
|
|
runs-on: windows-latest
|
|
permissions:
|
|
contents: read
|
|
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- name: Setup .NET
|
|
uses: actions/setup-dotnet@v6
|
|
with:
|
|
dotnet-version: ${{ env.DOTNET_VERSION }}
|
|
|
|
- name: Set Version
|
|
run: |
|
|
$version = "${{ needs.version.outputs.version }}"
|
|
echo "VERSION=$version" >> $env:GITHUB_ENV
|
|
shell: pwsh
|
|
|
|
- name: Download Prepared Changelog
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: release-changelog
|
|
path: release-changelog
|
|
|
|
- name: Sync Prepared Changelog
|
|
run: Copy-Item "release-changelog/CHANGELOG.md" "CHANGELOG.md" -Force
|
|
shell: pwsh
|
|
|
|
- name: Build MCPB Bundle
|
|
run: |
|
|
cd mcpb
|
|
.\Build-McpBundle.ps1 -Version $env:VERSION
|
|
shell: pwsh
|
|
env:
|
|
APPINSIGHTS_CONNECTION_STRING: ${{ secrets.APPINSIGHTS_CONNECTION_STRING }}
|
|
|
|
- name: Upload Artifact
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: mcpb-bundle
|
|
path: mcpb/artifacts/excel-mcp-*.mcpb
|
|
|
|
# =============================================================================
|
|
# Job 5: Build Agent Skills Packages
|
|
# =============================================================================
|
|
build-agent-skills:
|
|
name: Agent Skills
|
|
needs: [version]
|
|
runs-on: windows-latest
|
|
permissions:
|
|
contents: read
|
|
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- name: Setup .NET
|
|
uses: actions/setup-dotnet@v6
|
|
with:
|
|
global-json-file: global.json
|
|
|
|
- name: Set Version
|
|
run: |
|
|
$version = "${{ needs.version.outputs.version }}"
|
|
echo "VERSION=$version" >> $env:GITHUB_ENV
|
|
shell: pwsh
|
|
|
|
- name: Build Solution (generates SKILL.md and copies references)
|
|
run: dotnet build -c Release
|
|
shell: pwsh
|
|
|
|
- name: Create Skills Package
|
|
run: |
|
|
& .\scripts\Build-AgentSkills.ps1 -OutputDir "artifacts/skills" -Version $env:VERSION
|
|
shell: pwsh
|
|
|
|
- name: Upload Skills Artifact
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: agent-skills
|
|
path: artifacts/skills/excel-skills-*.zip
|
|
|
|
# =============================================================================
|
|
# Job 6: Wait for NuGet Propagation + MCP Registry
|
|
# =============================================================================
|
|
publish-mcp-registry:
|
|
name: MCP Registry
|
|
needs: [version, create-tag, build-mcp-server]
|
|
runs-on: windows-latest
|
|
permissions:
|
|
id-token: write
|
|
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- name: Set Version
|
|
run: |
|
|
$version = "${{ needs.version.outputs.version }}"
|
|
echo "VERSION=$version" >> $env:GITHUB_ENV
|
|
shell: pwsh
|
|
|
|
- name: Update server.json Version
|
|
run: |
|
|
.\scripts\Update-McpRegistryMetadata.ps1 `
|
|
-ServerJsonPath "src\ExcelMcp.McpServer\.mcp\server.json" `
|
|
-Version $env:VERSION
|
|
shell: pwsh
|
|
|
|
- name: Wait for NuGet Propagation
|
|
run: |
|
|
$version = $env:VERSION
|
|
$packageId = "sbroenne.excelmcp.mcpserver"
|
|
$readmeUrl = "https://api.nuget.org/v3-flatcontainer/$packageId/$version/readme"
|
|
|
|
Write-Output "Waiting for NuGet CDN propagation..."
|
|
$maxAttempts = 3
|
|
$pollInterval = 600 # 10 minutes
|
|
|
|
# First check immediately; subsequent checks wait pollInterval seconds
|
|
for ($i = 1; $i -le $maxAttempts; $i++) {
|
|
Write-Output "Attempt $i of $maxAttempts..."
|
|
try {
|
|
$response = Invoke-WebRequest -Uri $readmeUrl -UseBasicParsing -ErrorAction Stop
|
|
if ($response.StatusCode -eq 200) {
|
|
$content = [System.Text.Encoding]::UTF8.GetString($response.Content)
|
|
if ($content -match "mcp-name:\s+io\.github\.sbroenne/mcp-server-excel") {
|
|
Write-Output "README propagated successfully"
|
|
break
|
|
}
|
|
}
|
|
} catch {
|
|
Write-Output "README not yet available"
|
|
}
|
|
if ($i -lt $maxAttempts) {
|
|
Write-Output "Waiting $pollInterval seconds before retry..."
|
|
Start-Sleep -Seconds $pollInterval
|
|
}
|
|
}
|
|
shell: pwsh
|
|
|
|
- name: Install MCP Publisher
|
|
run: |
|
|
$arch = if ([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture -eq "Arm64") { "arm64" } else { "amd64" }
|
|
Invoke-WebRequest -Uri "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_windows_$arch.tar.gz" -OutFile "mcp-publisher.tar.gz"
|
|
tar xf mcp-publisher.tar.gz mcp-publisher.exe
|
|
shell: pwsh
|
|
|
|
- name: Publish to MCP Registry
|
|
run: |
|
|
./mcp-publisher.exe login github-oidc
|
|
Set-Location src/ExcelMcp.McpServer/.mcp
|
|
../../../mcp-publisher.exe publish --verbose
|
|
shell: pwsh
|
|
|
|
# =============================================================================
|
|
# Job 7: Create Git Tag
|
|
# =============================================================================
|
|
create-tag:
|
|
name: Create Git Tag
|
|
needs: [version, prepare-release, build-cli, build-mcp-server, build-vscode, build-mcpb, build-agent-skills]
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v7
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: Install changesets CLI
|
|
run: npm ci
|
|
|
|
- name: Download Prepared Changelog
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: release-changelog
|
|
path: prepared-release
|
|
|
|
- name: Compile and Verify Release Metadata
|
|
shell: pwsh
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
$releaseDate = Get-Content -Raw prepared-release/release_date.txt
|
|
./scripts/Build-Changelog.ps1 -Version "${{ needs.version.outputs.version }}" -Date $releaseDate
|
|
|
|
$generatedChangelog = (Get-FileHash CHANGELOG.md -Algorithm SHA256).Hash
|
|
$preparedChangelog = (Get-FileHash prepared-release/CHANGELOG.md -Algorithm SHA256).Hash
|
|
if ($generatedChangelog -ne $preparedChangelog) {
|
|
throw 'Generated CHANGELOG.md differs from the changelog used to build release artifacts.'
|
|
}
|
|
|
|
$generatedNotes = (Get-FileHash release_notes_body.md -Algorithm SHA256).Hash
|
|
$preparedNotes = (Get-FileHash prepared-release/release_notes_body.md -Algorithm SHA256).Hash
|
|
if ($generatedNotes -ne $preparedNotes) {
|
|
throw 'Generated release notes differ from the notes prepared before artifact builds.'
|
|
}
|
|
|
|
- name: Commit Release Metadata Update
|
|
id: release_commit
|
|
env:
|
|
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
VERSION=${{ needs.version.outputs.version }}
|
|
REPO="${{ github.repository }}"
|
|
|
|
if [ -z "${GH_TOKEN:-}" ]; then
|
|
echo "::error::RELEASE_PAT secret is not configured. Add an admin Personal Access Token with 'contents: write' as the repo secret RELEASE_PAT so release metadata can be committed back to main."
|
|
exit 1
|
|
fi
|
|
|
|
# Stage exactly the files Build-Changelog.ps1 touches.
|
|
git add -A CHANGELOG.md package.json package-lock.json Directory.Build.props \
|
|
mcpb/manifest.json src/ExcelMcp.McpServer/.mcp/server.json \
|
|
vscode-extension/package.json vscode-extension/package-lock.json .changeset
|
|
|
|
BASE_SHA=$(gh api "repos/$REPO/git/ref/heads/main" --jq '.object.sha')
|
|
if git diff --cached --quiet; then
|
|
echo "No release metadata changes to commit; tagging main@$BASE_SHA."
|
|
echo "sha=$BASE_SHA" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
|
|
# Base the metadata commit on the current main tip so the update is a
|
|
# fast-forward even when main advanced while release artifacts built.
|
|
BASE_TREE=$(gh api "repos/$REPO/git/commits/$BASE_SHA" --jq '.tree.sha')
|
|
echo "Basing release metadata commit on main@$BASE_SHA"
|
|
|
|
tree_entries='[]'
|
|
while IFS=$'\t' read -r status path; do
|
|
[ -z "$path" ] && continue
|
|
if [ "$status" = "D" ]; then
|
|
echo " delete $path"
|
|
entry=$(jq -n --arg path "$path" '{path:$path, mode:"100644", type:"blob", sha:null}')
|
|
else
|
|
echo " write $path"
|
|
git show ":$path" | base64 -w0 > blob.b64
|
|
blob_sha=$(gh api "repos/$REPO/git/blobs" --method POST \
|
|
-f encoding=base64 -F content=@blob.b64 --jq '.sha')
|
|
rm -f blob.b64
|
|
staged_sha=$(git rev-parse ":$path")
|
|
if [ "$blob_sha" != "$staged_sha" ]; then
|
|
echo "::error::Uploaded blob for $path does not match the staged content."
|
|
exit 1
|
|
fi
|
|
entry=$(jq -n --arg path "$path" --arg sha "$blob_sha" \
|
|
'{path:$path, mode:"100644", type:"blob", sha:$sha}')
|
|
fi
|
|
tree_entries=$(jq -c --argjson e "$entry" '. + [$e]' <<<"$tree_entries")
|
|
done < <(git diff --cached --name-status)
|
|
|
|
new_tree=$(jq -n --arg base "$BASE_TREE" --argjson tree "$tree_entries" \
|
|
'{base_tree:$base, tree:$tree}' \
|
|
| gh api "repos/$REPO/git/trees" --method POST --input - --jq '.sha')
|
|
|
|
new_commit=$(jq -n \
|
|
--arg msg "chore: update release metadata for v${VERSION} [skip ci]" \
|
|
--arg tree "$new_tree" --arg parent "$BASE_SHA" \
|
|
'{message:$msg, tree:$tree, parents:[$parent]}' \
|
|
| gh api "repos/$REPO/git/commits" --method POST --input - --jq '.sha')
|
|
|
|
gh api "repos/$REPO/git/refs/heads/main" --method PATCH \
|
|
-f sha="$new_commit" -F force=false >/dev/null
|
|
echo "sha=$new_commit" >> "$GITHUB_OUTPUT"
|
|
echo "Committed release metadata for v${VERSION} to main@$new_commit."
|
|
|
|
- name: Configure Git
|
|
run: |
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
|
|
- name: Create and push tag
|
|
run: |
|
|
TAG="${{ needs.version.outputs.tag }}"
|
|
RELEASE_COMMIT="${{ steps.release_commit.outputs.sha }}"
|
|
git fetch origin main
|
|
echo "Creating tag $TAG at release metadata commit $RELEASE_COMMIT"
|
|
git tag -a "$TAG" "$RELEASE_COMMIT" -m "Release $TAG"
|
|
git push origin "$TAG"
|
|
|
|
# =============================================================================
|
|
# Job 8: Publish to External Registries (after all builds succeed)
|
|
# =============================================================================
|
|
publish:
|
|
name: Publish to Registries
|
|
needs: [version, create-tag]
|
|
runs-on: windows-latest
|
|
permissions:
|
|
contents: read
|
|
id-token: write # Required for NuGet OIDC
|
|
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- name: Setup .NET
|
|
uses: actions/setup-dotnet@v6
|
|
with:
|
|
dotnet-version: ${{ env.DOTNET_VERSION }}
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v7
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
|
|
- name: Set Version
|
|
run: |
|
|
$version = "${{ needs.version.outputs.version }}"
|
|
echo "VERSION=$version" >> $env:GITHUB_ENV
|
|
shell: pwsh
|
|
|
|
- name: Download NuGet Package
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: mcp-server-nupkg
|
|
path: nupkg
|
|
|
|
- name: Download CLI NuGet Package
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: cli-nupkg
|
|
path: cli-nupkg
|
|
|
|
- name: Download VS Code VSIX
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: vscode-vsix
|
|
path: vscode-vsix
|
|
|
|
- name: NuGet Login (OIDC)
|
|
uses: NuGet/login@v1
|
|
id: nuget-login
|
|
with:
|
|
user: ${{ secrets.NUGET_USER }}
|
|
|
|
- name: Publish to NuGet.org (secondary distribution)
|
|
run: |
|
|
$version = $env:VERSION
|
|
dotnet nuget push "nupkg/Sbroenne.ExcelMcp.McpServer.$version.nupkg" `
|
|
--api-key ${{ steps.nuget-login.outputs.NUGET_API_KEY }} `
|
|
--source https://api.nuget.org/v3/index.json `
|
|
--skip-duplicate
|
|
Write-Output "Published Sbroenne.ExcelMcp.McpServer.$version to NuGet.org"
|
|
|
|
dotnet nuget push "cli-nupkg/Sbroenne.ExcelMcp.CLI.$version.nupkg" `
|
|
--api-key ${{ steps.nuget-login.outputs.NUGET_API_KEY }} `
|
|
--source https://api.nuget.org/v3/index.json `
|
|
--skip-duplicate
|
|
Write-Output "Published Sbroenne.ExcelMcp.CLI.$version to NuGet.org"
|
|
shell: pwsh
|
|
|
|
- name: Publish to VS Code Marketplace
|
|
uses: HaaLeo/publish-vscode-extension@v2
|
|
with:
|
|
pat: ${{ secrets.VSCE_TOKEN }}
|
|
registryUrl: https://marketplace.visualstudio.com
|
|
extensionFile: vscode-vsix/excelmcp-${{ env.VERSION }}.vsix
|
|
continue-on-error: true
|
|
|
|
# =============================================================================
|
|
# Job 9: Create Unified GitHub Release
|
|
# =============================================================================
|
|
create-release:
|
|
name: Create GitHub Release
|
|
needs: [version, prepare-release, create-tag, publish]
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- name: Set Version
|
|
run: |
|
|
echo "VERSION=${{ needs.version.outputs.version }}" >> $GITHUB_ENV
|
|
|
|
- name: Download All Artifacts
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
path: artifacts
|
|
|
|
- name: List Artifacts
|
|
run: |
|
|
echo "Downloaded artifacts:"
|
|
find artifacts -type f -name "*" | head -20
|
|
|
|
- name: Render Release Notes
|
|
shell: pwsh
|
|
run: |
|
|
$template = Get-Content -Raw '.github/release-notes-template.md'
|
|
$changelogBody = Get-Content -Raw artifacts/release-changelog/release_notes_body.md
|
|
$notes = $template.Replace('{{VERSION}}', $env:VERSION).Replace('{{CHANGELOG}}', $changelogBody.Trim())
|
|
Set-Content -Path release_notes.md -Value $notes -NoNewline
|
|
|
|
- name: Generate SHA-256 checksums
|
|
shell: pwsh
|
|
run: |
|
|
$expectedArchives = @(
|
|
"ExcelMcp-MCP-Server-${{ env.VERSION }}-windows.zip"
|
|
"ExcelMcp-CLI-${{ env.VERSION }}-windows.zip"
|
|
)
|
|
$archivePaths = @(
|
|
Get-ChildItem -Path artifacts/mcp-server-zip -Filter *.zip -File
|
|
Get-ChildItem -Path artifacts/cli-zip -Filter *.zip -File
|
|
)
|
|
|
|
$actualNames = @($archivePaths | ForEach-Object Name)
|
|
foreach ($expectedArchive in $expectedArchives) {
|
|
if ($expectedArchive -notin $actualNames) {
|
|
throw "Cannot publish checksums: expected runtime archive '$expectedArchive' was not downloaded."
|
|
}
|
|
}
|
|
if ($archivePaths.Count -ne $expectedArchives.Count) {
|
|
throw "Cannot publish checksums: expected exactly $($expectedArchives.Count) runtime archives, found $($archivePaths.Count)."
|
|
}
|
|
|
|
$checksumLines = foreach ($archivePath in ($archivePaths | Sort-Object Name)) {
|
|
$hash = (Get-FileHash -Path $archivePath.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
"$hash $($archivePath.Name)"
|
|
}
|
|
[System.IO.File]::WriteAllLines(
|
|
(Join-Path $PWD "SHA256SUMS"),
|
|
$checksumLines,
|
|
[System.Text.UTF8Encoding]::new($false))
|
|
|
|
- name: Create Release
|
|
run: |
|
|
VERSION=${{ env.VERSION }}
|
|
TAG=${{ needs.version.outputs.tag }}
|
|
|
|
# Collect all artifacts
|
|
ARTIFACTS=""
|
|
for f in artifacts/mcp-server-zip/*.zip; do [ -f "$f" ] && ARTIFACTS="$ARTIFACTS $f"; done
|
|
for f in artifacts/cli-zip/*.zip; do [ -f "$f" ] && ARTIFACTS="$ARTIFACTS $f"; done
|
|
for f in artifacts/vscode-vsix/*.vsix; do [ -f "$f" ] && ARTIFACTS="$ARTIFACTS $f"; done
|
|
for f in artifacts/mcpb-bundle/*.mcpb; do [ -f "$f" ] && ARTIFACTS="$ARTIFACTS $f"; done
|
|
for f in artifacts/agent-skills/*.zip; do [ -f "$f" ] && ARTIFACTS="$ARTIFACTS $f"; done
|
|
ARTIFACTS="$ARTIFACTS SHA256SUMS"
|
|
|
|
echo "Creating release with artifacts:$ARTIFACTS"
|
|
|
|
gh release create "$TAG" $ARTIFACTS \
|
|
--title "ExcelMcp $VERSION" \
|
|
--notes-file release_notes.md
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|