summaryrefslogtreecommitdiff
path: root/scripts/Publish-Release.ps1
diff options
context:
space:
mode:
authortanaka takashi <isazaka@gmail.com>2026-08-14 18:00:29 +0900
committertanaka takashi <isazaka@gmail.com>2026-08-14 18:00:29 +0900
commita8233adff75a4726015c7c96ce5e864d4506f6b2 (patch)
tree8ce06e1bc5d605b9681259260c99a47921e65413 /scripts/Publish-Release.ps1
parente07183ee6d3e57da325213e898cdcd6687320f53 (diff)
release: prepare WinAgent Sandbox MCP v0.1.0HEADmain
Unify the public product name and repository identifier while preserving stable executable, store, AppContainer, namespace, pipe, and mutex identifiers. Make Directory.Build.props VersionPrefix the application-version source of truth, use SDK-generated assembly metadata, and compare helper compatibility by parsed major/minor/build release identity. Rebuild the fixed winagent-sandbox-mcp release staging directory from clean defaults, emit versioned ZIP/checksum assets, verify manifests and fresh extraction, and reject local configuration, user state, and test artifacts. Document release/deployment separation and upgrade responsibilities, add the initial changelog, update operational script paths, and record the v0.1.0 verification results.
Diffstat (limited to 'scripts/Publish-Release.ps1')
-rw-r--r--scripts/Publish-Release.ps1208
1 files changed, 135 insertions, 73 deletions
diff --git a/scripts/Publish-Release.ps1 b/scripts/Publish-Release.ps1
index 3ab7bda..b34986a 100644
--- a/scripts/Publish-Release.ps1
+++ b/scripts/Publish-Release.ps1
@@ -1,5 +1,4 @@
param(
- [string]$Version = '1.0.0',
[string]$OutputRoot = 'build\Release\publish',
[switch]$SkipRuntimeCheck
)
@@ -11,96 +10,159 @@ $buildRootPath = [IO.Path]::GetFullPath((Join-Path $repository 'build'))
if (-not $outputRootPath.StartsWith($buildRootPath + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) {
throw 'Release output must remain inside the repository build directory.'
}
-$bundleName = "agent-sandbox-mcp-$Version-win-x64"
+
+$appProject = Join-Path $repository 'src\Agent.App\Agent.App.csproj'
+$adminProject = Join-Path $repository 'src\Agent.Admin\Agent.Admin.Host\Agent.Admin.Host.csproj'
+function Get-ApplicationVersion([string]$project) {
+ $value = (& dotnet msbuild $project -nologo -getProperty:Version | Out-String).Trim()
+ if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($value)) {
+ throw "Could not resolve the Application Version for $project."
+ }
+ return $value
+}
+
+$version = Get-ApplicationVersion $appProject
+$adminVersion = Get-ApplicationVersion $adminProject
+if ($version -ne $adminVersion) { throw "Main/admin Application Version mismatch: $version / $adminVersion" }
+if ($version -notmatch '^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$') {
+ throw "Application Version is not SemVer: $version"
+}
+$releaseVersion = [Version]($version -replace '[-+].*$', '')
+
+$bundleName = 'winagent-sandbox-mcp'
+$assetBase = "$bundleName-v$version-win-x64"
$bundle = Join-Path $outputRootPath $bundleName
-$zip = Join-Path $outputRootPath "$bundleName.zip"
+$zip = Join-Path $outputRootPath "$assetBase.zip"
$checksum = "$zip.sha256"
-$stagingRoot = Join-Path $outputRootPath (".staging-" + [Guid]::NewGuid().ToString('N'))
+$stagingRoot = Join-Path $outputRootPath ('.staging-' + [Guid]::NewGuid().ToString('N'))
$stagingBundle = Join-Path $stagingRoot $bundleName
+$forbiddenNames = @('agent.local.yaml', 'local', 'logs', 'state', 'applied-configurations', 'TestResults', 'artifacts', '.staging')
+
+function Assert-ReleaseIdentity([string]$path, [string]$assemblyPath, [string]$label) {
+ $info = [Diagnostics.FileVersionInfo]::GetVersionInfo($path)
+ $fileVersion = [Version]$info.FileVersion
+ if ($fileVersion.Major -ne $releaseVersion.Major -or
+ $fileVersion.Minor -ne $releaseVersion.Minor -or
+ $fileVersion.Build -ne $releaseVersion.Build) {
+ throw "$label FileVersion '$($info.FileVersion)' does not match Application Version '$version'."
+ }
+ $assemblyVersion = [Reflection.AssemblyName]::GetAssemblyName($assemblyPath).Version
+ if ($assemblyVersion.Major -ne $releaseVersion.Major -or
+ $assemblyVersion.Minor -ne $releaseVersion.Minor -or
+ $assemblyVersion.Build -ne $releaseVersion.Build) {
+ throw "$label AssemblyVersion '$assemblyVersion' does not match Application Version '$version'."
+ }
+ if ($info.ProductVersion -notmatch ('^' + [Regex]::Escape($version) + '(?:\+[0-9A-Za-z.-]+)?$')) {
+ throw "$label InformationalVersion '$($info.ProductVersion)' is not compatible with '$version'."
+ }
+ [PSCustomObject]@{
+ Label = $label
+ ApplicationVersion = $version
+ AssemblyVersion = $assemblyVersion.ToString()
+ FileVersion = $info.FileVersion
+ InformationalVersion = $info.ProductVersion
+ }
+}
+
+function Assert-NoUserState([string]$root) {
+ $forbidden = Get-ChildItem -LiteralPath $root -Force -Recurse |
+ Where-Object Name -in $forbiddenNames |
+ Select-Object -First 1
+ if ($forbidden) {
+ $relative = [IO.Path]::GetRelativePath($root, $forbidden.FullName)
+ throw "Release artifact must not contain user state, local assets, or test artifacts: $relative"
+ }
+}
New-Item -ItemType Directory -Force -Path $outputRootPath | Out-Null
if (Test-Path -LiteralPath $zip) { Remove-Item -LiteralPath $zip -Force }
if (Test-Path -LiteralPath $checksum) { Remove-Item -LiteralPath $checksum -Force }
New-Item -ItemType Directory -Path $stagingBundle | Out-Null
-$appProject = Join-Path $repository 'src\Agent.App\Agent.App.csproj'
-$adminProject = Join-Path $repository 'src\Agent.Admin\Agent.Admin.Host\Agent.Admin.Host.csproj'
-dotnet restore $appProject -r win-x64 --locked-mode
-if ($LASTEXITCODE -ne 0) { throw "win-x64 locked restore failed with exit code $LASTEXITCODE" }
-dotnet restore $adminProject -r win-x64 --locked-mode
-if ($LASTEXITCODE -ne 0) { throw "win-x64 admin locked restore failed with exit code $LASTEXITCODE" }
-dotnet publish $appProject -c Release -r win-x64 --self-contained true --no-restore --output $stagingBundle -p:Version=$Version
-if ($LASTEXITCODE -ne 0) { throw "dotnet publish failed with exit code $LASTEXITCODE" }
-$adminBundle = Join-Path $stagingBundle 'admin'
-dotnet publish $adminProject -c Release -r win-x64 --self-contained true --no-restore --output $adminBundle -p:Version=$Version
-if ($LASTEXITCODE -ne 0) { throw "admin dotnet publish failed with exit code $LASTEXITCODE" }
+try {
+ dotnet restore $appProject -r win-x64 --locked-mode
+ if ($LASTEXITCODE -ne 0) { throw "win-x64 locked restore failed with exit code $LASTEXITCODE" }
+ dotnet restore $adminProject -r win-x64 --locked-mode
+ if ($LASTEXITCODE -ne 0) { throw "win-x64 admin locked restore failed with exit code $LASTEXITCODE" }
+ dotnet publish $appProject -c Release -r win-x64 --self-contained true --no-restore --output $stagingBundle
+ if ($LASTEXITCODE -ne 0) { throw "dotnet publish failed with exit code $LASTEXITCODE" }
+ $adminBundle = Join-Path $stagingBundle 'admin'
+ dotnet publish $adminProject -c Release -r win-x64 --self-contained true --no-restore --output $adminBundle
+ if ($LASTEXITCODE -ne 0) { throw "admin dotnet publish failed with exit code $LASTEXITCODE" }
-$required = @(
- 'agent-mcp.exe', 'admin\agent-mcp-admin.exe', 'agent.yaml', 'config\locales\en.json', 'config\locales\ja.json',
- 'prompts\user-system.md', 'skills', 'README.md'
-)
-foreach ($relative in $required) {
- if (-not (Test-Path -LiteralPath (Join-Path $stagingBundle $relative))) { throw "Release Bundle is missing $relative" }
-}
-foreach ($relative in @('agent.local.yaml', 'config\agent.local.example.yaml')) {
- if (Test-Path -LiteralPath (Join-Path $stagingBundle $relative)) { throw "Release Bundle must not contain local override input: $relative" }
-}
+ $required = @(
+ 'agent-mcp.exe', 'admin\agent-mcp-admin.exe', 'agent.yaml', 'config\locales\en.json', 'config\locales\ja.json',
+ 'prompts\user-system.md', 'skills', 'README.md', 'CHANGELOG.md'
+ )
+ foreach ($relative in $required) {
+ if (-not (Test-Path -LiteralPath (Join-Path $stagingBundle $relative))) { throw "Release Bundle is missing $relative" }
+ }
+ Assert-NoUserState $stagingBundle
-$manifestPath = Join-Path $stagingBundle 'publish-manifest.sha256'
-Get-ChildItem -LiteralPath $stagingBundle -File -Recurse |
- Where-Object FullName -ne $manifestPath |
- Sort-Object FullName |
- ForEach-Object {
- $relative = [IO.Path]::GetRelativePath($stagingBundle, $_.FullName).Replace('\', '/')
- $hash = (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
- "$hash $relative"
- } | Set-Content -LiteralPath $manifestPath -Encoding utf8NoBOM
+ $mainVersionInfo = Assert-ReleaseIdentity (Join-Path $stagingBundle 'agent-mcp.exe') (Join-Path $stagingBundle 'agent-mcp.dll') 'main'
+ $adminVersionInfo = Assert-ReleaseIdentity (Join-Path $stagingBundle 'admin\agent-mcp-admin.exe') (Join-Path $stagingBundle 'admin\agent-mcp-admin.dll') 'admin'
-Compress-Archive -LiteralPath $stagingBundle -DestinationPath $zip -CompressionLevel Optimal
-$zipHash = (Get-FileHash -LiteralPath $zip -Algorithm SHA256).Hash.ToLowerInvariant()
-"$zipHash $([IO.Path]::GetFileName($zip))" | Set-Content -LiteralPath $checksum -Encoding ascii
+ $manifestPath = Join-Path $stagingBundle 'publish-manifest.sha256'
+ Get-ChildItem -LiteralPath $stagingBundle -File -Recurse |
+ Where-Object FullName -ne $manifestPath |
+ Sort-Object FullName |
+ ForEach-Object {
+ $relative = [IO.Path]::GetRelativePath($stagingBundle, $_.FullName).Replace('\', '/')
+ $hash = (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
+ "$hash $relative"
+ } | Set-Content -LiteralPath $manifestPath -Encoding utf8NoBOM
-$freshRoot = Join-Path ([IO.Path]::GetTempPath()) ("agent-mcp-release-check-" + [Guid]::NewGuid().ToString('N'))
-New-Item -ItemType Directory -Path $freshRoot | Out-Null
-try {
- Expand-Archive -LiteralPath $zip -DestinationPath $freshRoot
- $freshBundle = Join-Path $freshRoot $bundleName
- $sourceReferences = Get-ChildItem -LiteralPath $freshBundle -File -Recurse |
- Where-Object Extension -in @('.yaml', '.json', '.md', '.config') |
- Select-String -SimpleMatch -Pattern $repository
- if ($sourceReferences) { throw 'Release Bundle contains a source-tree reference.' }
- if (-not $SkipRuntimeCheck) {
- Push-Location $freshBundle
- try {
- & .\agent-mcp.exe show .\agent.yaml
- if ($LASTEXITCODE -ne 0) { throw "Bundle configuration check failed with exit code $LASTEXITCODE." }
+ Compress-Archive -LiteralPath $stagingBundle -DestinationPath $zip -CompressionLevel Optimal
+ $zipHash = (Get-FileHash -LiteralPath $zip -Algorithm SHA256).Hash.ToLowerInvariant()
+ "$zipHash $([IO.Path]::GetFileName($zip))" | Set-Content -LiteralPath $checksum -Encoding ascii
+
+ $freshRoot = Join-Path ([IO.Path]::GetTempPath()) ('winagent-sandbox-mcp-release-check-' + [Guid]::NewGuid().ToString('N'))
+ New-Item -ItemType Directory -Path $freshRoot | Out-Null
+ try {
+ Expand-Archive -LiteralPath $zip -DestinationPath $freshRoot
+ $freshItems = @(Get-ChildItem -LiteralPath $freshRoot -Force)
+ if ($freshItems.Count -ne 1 -or -not $freshItems[0].PSIsContainer -or $freshItems[0].Name -ne $bundleName) {
+ throw "ZIP must contain exactly one unversioned root directory named '$bundleName'."
+ }
+ $freshBundle = Join-Path $freshRoot $bundleName
+ Assert-NoUserState $freshBundle
+ $sourceReferences = Get-ChildItem -LiteralPath $freshBundle -File -Recurse |
+ Where-Object Extension -in @('.yaml', '.json', '.md', '.config') |
+ Select-String -SimpleMatch -Pattern $repository
+ if ($sourceReferences) { throw 'Release Bundle contains a source-tree reference.' }
+ foreach ($line in Get-Content -LiteralPath (Join-Path $freshBundle 'publish-manifest.sha256')) {
+ if ($line -notmatch '^([0-9a-f]{64}) (.+)$') { throw "Invalid manifest entry: $line" }
+ $actual = (Get-FileHash -LiteralPath (Join-Path $freshBundle $Matches[2]) -Algorithm SHA256).Hash.ToLowerInvariant()
+ if ($actual -ne $Matches[1]) { throw "Manifest hash mismatch: $($Matches[2])" }
+ }
+ if (-not $SkipRuntimeCheck) {
+ Push-Location $freshBundle
+ try {
+ & .\agent-mcp.exe show .\agent.yaml
+ if ($LASTEXITCODE -ne 0) { throw "Bundle configuration check failed with exit code $LASTEXITCODE." }
+ }
+ finally { Pop-Location }
}
- finally { Pop-Location }
}
-}
-finally {
- if (Test-Path -LiteralPath $freshRoot) { Remove-Item -LiteralPath $freshRoot -Recurse -Force }
-}
+ finally {
+ if (Test-Path -LiteralPath $freshRoot) { Remove-Item -LiteralPath $freshRoot -Recurse -Force }
+ }
-# The ZIP is reproducible defaults only. The deployment directory keeps explicitly local,
-# publish-excluded inputs while all versioned defaults and binaries are replaced.
-try {
- if (Test-Path -LiteralPath $bundle) {
- $resolvedBundle = (Resolve-Path -LiteralPath $bundle).Path
- if (-not $resolvedBundle.StartsWith($outputRootPath + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) {
- throw 'Existing deployment Bundle escaped the validated output root.'
- }
- Get-ChildItem -LiteralPath $resolvedBundle -Force |
- Where-Object Name -notin @('agent.local.yaml', 'local', 'logs', 'state') |
- Remove-Item -Recurse -Force
+ $bundleFullPath = [IO.Path]::GetFullPath($bundle)
+ if (-not $bundleFullPath.StartsWith($outputRootPath + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase) -or
+ [IO.Path]::GetFileName($bundleFullPath) -ne $bundleName) {
+ throw 'Fixed Release Bundle target escaped the validated output root.'
}
- else { New-Item -ItemType Directory -Path $bundle | Out-Null }
- Copy-Item -Path (Join-Path $stagingBundle '*') -Destination $bundle -Recurse -Force
+ if (Test-Path -LiteralPath $bundleFullPath) { Remove-Item -LiteralPath $bundleFullPath -Recurse -Force }
+ Move-Item -LiteralPath $stagingBundle -Destination $bundleFullPath
+ Assert-NoUserState $bundleFullPath
+
+ $mainVersionInfo
+ $adminVersionInfo
+ Write-Output "Bundle: $bundle"
+ Write-Output "ZIP: $zip"
+ Write-Output "SHA-256: $zipHash"
}
finally {
if (Test-Path -LiteralPath $stagingRoot) { Remove-Item -LiteralPath $stagingRoot -Recurse -Force }
}
-
-Write-Output "Bundle: $bundle"
-Write-Output "ZIP: $zip"
-Write-Output "SHA-256: $checksum"