Files
sbroenne__mcp-server-excel/scripts/Sync-PublishedPluginRepo.ps1
Stefan Broenner b2eab4ca91 Migrate plugins to Agent Plugins 1.0 (#776)
Build plugins from canonical source templates, validate portable manifests and MCP configuration, synchronize complete Agent Skill directories, and scope Excel E2E to runtime-impacting changes.

Tests: 23 targeted SkillGeneration tests passed; Release build completed with 0 warnings and 0 errors; Excel E2E path classification passed for 12 cases; MCPB and Agent Skills packaging passed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Co-authored-by: Stefan Brönner <sbroenne@openclaw.fritz.box>
Copilot-Session: 858e5fa0-f0ef-490f-a154-00c470943904
2026-08-16 10:11:38 +02:00

219 lines
7.5 KiB
PowerShell

<#
.SYNOPSIS
Synchronizes the published Copilot plugin repo to the canonical marketplace layout.
.DESCRIPTION
Copies built plugin artifacts into the published marketplace repo, applies any
source-owned root overlay content, writes the canonical marketplace manifest to
.github/plugin/marketplace.json, and removes the legacy root marketplace.json.
The published repo is wrapper/bootstrap-only. Self-contained Windows runtimes
remain in the main repo GitHub Releases and are acquired by plugin-local
bootstrap logic on first invocation.
#>
param(
[Parameter(Mandatory = $true)]
[string]$PublishedRepoDir,
[Parameter(Mandatory = $true)]
[string]$BuiltPluginsDir,
[Parameter(Mandatory = $true)]
[string]$Version
)
$ErrorActionPreference = "Stop"
$RepoRoot = Split-Path -Parent $PSScriptRoot
$RootOverlayDir = Join-Path $RepoRoot ".github\plugins\marketplace-repo"
$PublishedRepoDir = (Resolve-Path $PublishedRepoDir).Path
$BuiltPluginsDir = (Resolve-Path $BuiltPluginsDir).Path
$AgentPluginSchema = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json"
function Copy-DirectoryFiles {
param(
[Parameter(Mandatory = $true)]
[string]$SourceDir,
[Parameter(Mandatory = $true)]
[string]$DestinationDir
)
Get-ChildItem -Path $SourceDir -Recurse -File -Force | ForEach-Object {
$relativePath = $_.FullName.Substring($SourceDir.Length).TrimStart('\', '/')
$destinationPath = Join-Path $DestinationDir $relativePath
$destinationParent = Split-Path -Parent $destinationPath
if (-not (Test-Path $destinationParent)) {
New-Item -ItemType Directory -Path $destinationParent -Force | Out-Null
}
Copy-Item -Path $_.FullName -Destination $destinationPath -Force
}
}
function Get-PluginSkillPaths {
param(
[Parameter(Mandatory = $true)]
[string]$PluginRoot,
[Parameter(Mandatory = $true)]
[string]$PluginName
)
$skillRoot = Join-Path $PluginRoot "skills"
if (-not (Test-Path $skillRoot)) {
return @()
}
return @(Get-ChildItem -Path $skillRoot -Directory |
Where-Object { Test-Path (Join-Path $_.FullName "SKILL.md") } |
Sort-Object Name |
ForEach-Object { "./plugins/$PluginName/skills/$($_.Name)" })
}
function Write-Utf8NoBomJson {
param(
[Parameter(Mandatory = $true)]
[string]$Path,
[Parameter(Mandatory = $true)]
$Object
)
$parent = Split-Path -Parent $Path
if (-not (Test-Path $parent)) {
New-Item -ItemType Directory -Path $parent -Force | Out-Null
}
$json = $Object | ConvertTo-Json -Depth 10
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
[System.IO.File]::WriteAllText($Path, "$json`n", $utf8NoBom)
}
function Assert-AgentPluginManifest {
param(
[Parameter(Mandatory = $true)]
[string]$PluginName,
[Parameter(Mandatory = $true)]
[string]$PluginJsonPath,
[Parameter(Mandatory = $true)]
$PluginJson
)
$allowedFields = @('$schema', "name", "version", "description", "author", "homepage", "repository", "license", "keywords", "extensions")
foreach ($property in $PluginJson.PSObject.Properties) {
if ($property.Name -notin $allowedFields) {
throw "$PluginJsonPath contains unsupported Agent Plugins 1.0 field '$($property.Name)'."
}
}
if ($PluginJson.'$schema' -ne $AgentPluginSchema) {
throw "$PluginJsonPath must target $AgentPluginSchema."
}
if ($PluginJson.name -ne $PluginName) {
throw "$PluginJsonPath has name '$($PluginJson.name)' but expected '$PluginName'."
}
if ($PluginJson.repository -isnot [string]) {
throw "$PluginJsonPath repository must be a string."
}
}
Write-Host "Synchronizing published plugin repo..." -ForegroundColor Cyan
Write-Host " Published repo: $PublishedRepoDir" -ForegroundColor DarkGray
Write-Host " Built plugins: $BuiltPluginsDir" -ForegroundColor DarkGray
Write-Host " Version: $Version" -ForegroundColor DarkGray
$builtPluginNames = @("excel-mcp", "excel-cli")
$pluginMetadata = @()
foreach ($pluginName in $builtPluginNames) {
$sourcePluginDir = Join-Path $BuiltPluginsDir $pluginName
$pluginJsonPath = Join-Path $sourcePluginDir "plugin.json"
if (-not (Test-Path $pluginJsonPath)) {
throw "Built plugin manifest not found: $pluginJsonPath"
}
$pluginJson = Get-Content $pluginJsonPath -Raw | ConvertFrom-Json
Assert-AgentPluginManifest -PluginName $pluginName -PluginJsonPath $pluginJsonPath -PluginJson $pluginJson
if ($pluginJson.version -ne $Version) {
throw "$pluginJsonPath resolved version '$($pluginJson.version)' but expected '$Version'."
}
$legacyCopilotHelper = Join-Path $sourcePluginDir "bin\install-global.ps1"
if (Test-Path $legacyCopilotHelper) {
throw "Copilot-only files must be placed under com.github.copilot/: $legacyCopilotHelper"
}
$legacyMcpPath = Join-Path $sourcePluginDir ".mcp.json"
if (Test-Path $legacyMcpPath) {
throw "Legacy MCP configuration is not permitted in Agent Plugins 1.0 packages: $legacyMcpPath"
}
$versionTxtPath = Join-Path $sourcePluginDir "version.txt"
if (Test-Path $versionTxtPath) {
$resolvedVersionTxt = (Get-Content $versionTxtPath -Raw).Trim()
if ($resolvedVersionTxt -ne $Version) {
throw "$versionTxtPath resolved version '$resolvedVersionTxt' but expected '$Version'."
}
}
$pluginMetadata += [ordered]@{
name = $pluginJson.name
source = "./plugins/$pluginName"
description = $pluginJson.description
version = $Version
author = $pluginJson.author
homepage = $pluginJson.homepage
repository = $pluginJson.repository
license = $pluginJson.license
keywords = @($pluginJson.keywords)
skills = @(Get-PluginSkillPaths -PluginRoot $sourcePluginDir -PluginName $pluginName)
}
}
if (Test-Path $RootOverlayDir) {
Write-Host "Applying source-owned published-repo overlay..." -ForegroundColor Cyan
Copy-DirectoryFiles -SourceDir $RootOverlayDir -DestinationDir $PublishedRepoDir
}
foreach ($pluginName in $builtPluginNames) {
$sourcePluginDir = Join-Path $BuiltPluginsDir $pluginName
$destinationPluginDir = Join-Path $PublishedRepoDir "plugins\$pluginName"
if (Test-Path $destinationPluginDir) {
Remove-Item -Path $destinationPluginDir -Recurse -Force
}
Copy-Item -Path $sourcePluginDir -Destination $destinationPluginDir -Recurse -Force
}
$canonicalManifestPath = Join-Path $PublishedRepoDir ".github\plugin\marketplace.json"
$legacyManifestPath = Join-Path $PublishedRepoDir "marketplace.json"
$canonicalManifest = [ordered]@{
name = "mcp-server-excel-plugins"
metadata = [ordered]@{
description = "Windows-only GitHub Copilot CLI plugins for Excel automation with ExcelMcp."
version = "1.0.0"
}
owner = [ordered]@{
name = "Stefan Brönner"
email = "3026464+sbroenne@users.noreply.github.com"
}
plugins = $pluginMetadata
}
Write-Host "Writing canonical marketplace manifest..." -ForegroundColor Cyan
Write-Utf8NoBomJson -Path $canonicalManifestPath -Object $canonicalManifest
if (Test-Path $legacyManifestPath) {
Write-Host "Removing legacy root marketplace manifest..." -ForegroundColor Cyan
Remove-Item -Path $legacyManifestPath -Force
}
Write-Host "Published plugin repo synchronization complete." -ForegroundColor Green