chore(release): prepare public source release

This commit is contained in:
MercuryToolbox Release
2026-07-18 15:41:59 +08:00
commit e365e5df4d
508 changed files with 163373 additions and 0 deletions
+356
View File
@@ -0,0 +1,356 @@
[CmdletBinding()]
param(
[ValidateSet('Debug', 'Release', 'ReleaseFast', 'ReleaseSize')]
[string]$Configuration = 'ReleaseFast',
[ValidateNotNullOrEmpty()]
[string]$OutputRoot = (Join-Path (Split-Path -Parent $PSScriptRoot) 'dist'),
[string]$PackageName,
[switch]$SkipBuild,
[switch]$SkipSlimBinaryRebuild,
[switch]$SkipPromptGeneration
)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
. (Join-Path $PSScriptRoot 'toolbox-commands.ps1')
function Invoke-StrictNative {
param(
[Parameter(Mandatory = $true)]
[string]$FilePath,
[Parameter(Mandatory = $true)]
[string[]]$ArgumentList
)
& $FilePath @ArgumentList
if ($LASTEXITCODE -ne 0) {
throw "Command failed with exit code ${LASTEXITCODE}: $FilePath $($ArgumentList -join ' ')"
}
}
function Get-RequiredCommandPath {
param(
[Parameter(Mandatory = $true)]
[string]$Name
)
$command = Get-Command -Name $Name -ErrorAction SilentlyContinue
if ($null -eq $command) {
throw "Required command not found on PATH: $Name"
}
return $command.Source
}
function Get-HostTargetTriple {
$rustcPath = Get-RequiredCommandPath -Name 'rustc'
$lines = & $rustcPath -vV
if ($LASTEXITCODE -ne 0) {
$commandLine = Format-ToolboxNativeCommand -FilePath $rustcPath -ArgumentList @('-vV')
throw "Failed to query rustc host target with command: $commandLine"
}
foreach ($line in $lines) {
if ($line -like 'host:*') {
return ($line -replace '^host:\s*', '').Trim()
}
}
throw 'rustc host target was not reported'
}
function Assert-ArchiveCreated {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
throw "Compress-Archive did not create the expected archive: $Path"
}
$archive = Get-Item -LiteralPath $Path -Force
if ($archive.Length -le 0) {
throw "Compress-Archive created an empty archive: $Path"
}
}
function Reset-Directory {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
if (Test-Path -LiteralPath $Path) {
Remove-Item -LiteralPath $Path -Recurse -Force
}
New-Item -ItemType Directory -Force -Path $Path | Out-Null
}
function Assert-SingleDirectoryName {
param(
[Parameter(Mandatory = $true)]
[string]$Name
)
if ([string]::IsNullOrWhiteSpace($Name)) {
throw 'PackageName must not be empty.'
}
if ([System.IO.Path]::IsPathRooted($Name) -or $Name -ne [System.IO.Path]::GetFileName($Name) -or $Name -in @('.', '..')) {
throw "PackageName must be a single directory name, not a path: $Name"
}
if ($Name.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars()) -ge 0) {
throw "PackageName contains invalid file name characters: $Name"
}
}
function Get-PathWithTrailingSeparator {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
$trimmed = $Path.TrimEnd([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar)
return $trimmed + [System.IO.Path]::DirectorySeparatorChar
}
function Resolve-PackageRootPath {
param(
[Parameter(Mandatory = $true)]
[string]$RootPath,
[Parameter(Mandatory = $true)]
[string]$Name
)
Assert-SingleDirectoryName -Name $Name
$resolvedRoot = [System.IO.Path]::GetFullPath($RootPath)
if (Test-Path -LiteralPath $resolvedRoot) {
$rootItem = Get-Item -LiteralPath $resolvedRoot -Force
if (($rootItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) {
throw "OutputRoot must not be a reparse point: $resolvedRoot"
}
}
$resolvedPackageRoot = [System.IO.Path]::GetFullPath((Join-Path $resolvedRoot $Name))
$rootPrefix = Get-PathWithTrailingSeparator -Path $resolvedRoot
if (-not $resolvedPackageRoot.StartsWith($rootPrefix, [System.StringComparison]::OrdinalIgnoreCase)) {
throw "Resolved package root must stay inside OutputRoot: $resolvedPackageRoot"
}
return [pscustomobject]@{
OutputRoot = $resolvedRoot
PackageRoot = $resolvedPackageRoot
}
}
function Copy-DirectoryTree {
param(
[Parameter(Mandatory = $true)]
[string]$SourcePath,
[Parameter(Mandatory = $true)]
[string]$DestinationPath
)
if (-not (Test-Path -LiteralPath $SourcePath)) {
throw "Required directory not found: $SourcePath"
}
$sourceRoot = Get-Item -LiteralPath $SourcePath -Force
if (($sourceRoot.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) {
throw "Refusing to copy reparse-point directory: $SourcePath"
}
$reparsePoints = @(Get-ChildItem -LiteralPath $SourcePath -Force -Recurse -Attributes ReparsePoint)
if ($reparsePoints.Count -gt 0) {
throw "Refusing to copy directory tree containing reparse points: $($reparsePoints[0].FullName)"
}
if (Test-Path -LiteralPath $DestinationPath) {
Remove-Item -LiteralPath $DestinationPath -Recurse -Force
}
$destinationParent = Split-Path -Parent $DestinationPath
if (-not [string]::IsNullOrWhiteSpace($destinationParent)) {
New-Item -ItemType Directory -Force -Path $destinationParent | Out-Null
}
Copy-Item -LiteralPath $SourcePath -Destination $DestinationPath -Recurse -Force
}
function Write-Sha256SumsFile {
param(
[Parameter(Mandatory = $true)]
[string]$RootPath,
[Parameter(Mandatory = $true)]
[string]$OutputPath
)
$lines = [System.Collections.Generic.List[string]]::new()
foreach ($file in (Get-ChildItem -LiteralPath $RootPath -Recurse -File | Sort-Object FullName)) {
if ($file.FullName -eq $OutputPath) {
continue
}
$relativePath = [System.IO.Path]::GetRelativePath($RootPath, $file.FullName).Replace('\', '/')
$hash = (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
$lines.Add("$hash $relativePath")
}
Set-Content -LiteralPath $OutputPath -Value $lines -Encoding utf8NoBOM
}
$workspaceRoot = Split-Path -Parent $PSScriptRoot
$profileName = Resolve-ToolboxProfileName -Configuration $Configuration
$binaryRoot = Join-Path $workspaceRoot (Join-Path 'target' $profileName)
$hostTarget = Get-HostTargetTriple
if ([string]::IsNullOrWhiteSpace($PackageName)) {
$PackageName = "MercuryToolbox-$hostTarget-$Configuration"
}
$resolvedPackagePaths = Resolve-PackageRootPath -RootPath $OutputRoot -Name $PackageName
$OutputRoot = $resolvedPackagePaths.OutputRoot
$packageRoot = $resolvedPackagePaths.PackageRoot
$zipPath = Join-Path $OutputRoot "$PackageName.zip"
$binDir = Join-Path $packageRoot 'bin'
$docsAiDir = Join-Path $packageRoot 'docs\ai'
$skillsDir = Join-Path $packageRoot 'skills'
$scriptsDir = Join-Path $packageRoot 'scripts'
$supportDir = Join-Path $packageRoot 'support'
$startHerePath = Join-Path $packageRoot 'START-HERE.txt'
Write-Host "Mercury Toolbox release packager"
Write-Host "workspace: $workspaceRoot"
Write-Host "configuration: $Configuration"
Write-Host "host target: $hostTarget"
Write-Host "output root: $OutputRoot"
Write-Host "package root: $packageRoot"
Write-Host "archive: $zipPath"
New-Item -ItemType Directory -Force -Path $OutputRoot | Out-Null
$cargoPath = Get-RequiredCommandPath -Name 'cargo'
if (-not $SkipBuild) {
Invoke-ToolboxBuild -CargoPath $cargoPath -Configuration $Configuration -SkipSlimBinaryRebuild:$SkipSlimBinaryRebuild
}
if (-not $SkipPromptGeneration) {
$promptScript = Join-Path $PSScriptRoot 'generate-ai-prompt.ps1'
$skillScript = Join-Path $PSScriptRoot 'generate-ai-skill.ps1'
$promptArgs = @{
Configuration = $Configuration
}
if ($SkipBuild) {
$promptArgs.SkipBuild = $true
}
& $promptScript @promptArgs
if ($LASTEXITCODE -ne 0) {
throw "generate-ai-prompt.ps1 exited with $LASTEXITCODE"
}
& $skillScript @promptArgs
if ($LASTEXITCODE -ne 0) {
throw "generate-ai-skill.ps1 exited with $LASTEXITCODE"
}
}
Reset-Directory -Path $packageRoot
New-Item -ItemType Directory -Force -Path $binDir, $docsAiDir, $skillsDir, $scriptsDir | Out-Null
foreach ($commandName in Get-ToolboxCommandNames) {
$binaryPath = Join-Path $binaryRoot "$commandName.exe"
if (-not (Test-Path -LiteralPath $binaryPath)) {
throw "Expected binary not found: $binaryPath"
}
Copy-Item -LiteralPath $binaryPath -Destination (Join-Path $binDir "$commandName.exe") -Force
}
$duckdbDllPath = Join-Path $binaryRoot 'deps\duckdb.dll'
if (-not (Test-Path -LiteralPath $duckdbDllPath)) {
throw "Expected DuckDB runtime not found: $duckdbDllPath"
}
Copy-Item -LiteralPath $duckdbDllPath -Destination (Join-Path $binDir 'duckdb.dll') -Force
Copy-Item -LiteralPath (Join-Path $workspaceRoot 'LICENSE') -Destination (Join-Path $packageRoot 'LICENSE') -Force
Copy-Item -LiteralPath (Join-Path $workspaceRoot 'docs\ai\mercury-toolbox-ai-prompt.md') -Destination (Join-Path $docsAiDir 'mercury-toolbox-ai-prompt.md') -Force
Copy-Item -LiteralPath (Join-Path $workspaceRoot 'docs\ai\toolbox-ai-prompt-notes.json') -Destination (Join-Path $docsAiDir 'toolbox-ai-prompt-notes.json') -Force
Copy-DirectoryTree -SourcePath (Join-Path $workspaceRoot 'skills\mercury-toolbox') -DestinationPath (Join-Path $skillsDir 'mercury-toolbox')
if (Test-Path -LiteralPath (Join-Path $workspaceRoot 'support')) {
Copy-DirectoryTree -SourcePath (Join-Path $workspaceRoot 'support') -DestinationPath $supportDir
}
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'toolbox-commands.ps1') -Destination (Join-Path $scriptsDir 'toolbox-commands.ps1') -Force
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'install-package-toolbox.ps1') -Destination (Join-Path $scriptsDir 'install-package-toolbox.ps1') -Force
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'uninstall-package-toolbox.ps1') -Destination (Join-Path $scriptsDir 'uninstall-package-toolbox.ps1') -Force
$startHereLines = @(
'Mercury Toolbox portable package',
'',
'Install from this extracted directory:',
' pwsh -NoProfile -File .\scripts\install-package-toolbox.ps1',
'',
'After install, open a new PowerShell or reload PATH:',
' $env:Path = [Environment]::GetEnvironmentVariable(''Path'',''User'') + '';'' + [Environment]::GetEnvironmentVariable(''Path'',''Machine'')',
'',
'Safe first commands:',
' fileprobe <PATH>',
' reposhape . --json | ConvertFrom-Json',
' peimports <PATH> --json | ConvertFrom-Json',
' drvshape <SYS> --json | ConvertFrom-Json',
' asmref diagnose <ASSEMBLY> --resolve-dir <DIR> --format toon',
'',
'AI and Codex assets:',
' docs\ai\mercury-toolbox-ai-prompt.md',
' skills\mercury-toolbox\SKILL.md',
' skills\mercury-toolbox\references\command-catalog.md',
'',
'Trust guided triage first. Every command is documented with an answer shape, trust basis, and next actions in the packaged skill catalog. PE tools also expose report_quality and next_actions in --json/--toon, and text output starts with answer=, trust=, and next_action= lines.',
'',
'This archive intentionally omits the repo README; use the files above as the package entry points.'
)
Set-Content -LiteralPath $startHerePath -Value $startHereLines -Encoding utf8NoBOM
$gitCommit = (& git -C $workspaceRoot rev-parse HEAD 2>$null)
if ($LASTEXITCODE -ne 0) {
$gitCommit = $null
}
$manifest = [ordered]@{
name = 'Mercury Toolbox'
package_name = $PackageName
configuration = $Configuration
profile = $profileName
host_target = $hostTarget
built_at_utc = (Get-Date).ToUniversalTime().ToString('o')
git_commit = if ([string]::IsNullOrWhiteSpace($gitCommit)) { $null } else { $gitCommit.Trim() }
command_count = (Get-ToolboxCommandNames).Count
commands = @(Get-ToolboxCommandNames)
install_script = 'scripts/install-package-toolbox.ps1'
uninstall_script = 'scripts/uninstall-package-toolbox.ps1'
ai_prompt = 'docs/ai/mercury-toolbox-ai-prompt.md'
ai_skill = 'skills/mercury-toolbox/SKILL.md'
ai_skill_catalog = 'skills/mercury-toolbox/references/command-catalog.md'
}
$manifestPath = Join-Path $packageRoot 'mercury-toolbox-package.json'
$manifest | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $manifestPath -Encoding utf8NoBOM
$hashPath = Join-Path $packageRoot 'SHA256SUMS.txt'
Write-Sha256SumsFile -RootPath $packageRoot -OutputPath $hashPath
if (Test-Path -LiteralPath $zipPath) {
Remove-Item -LiteralPath $zipPath -Force
}
Compress-Archive -LiteralPath $packageRoot -DestinationPath $zipPath -CompressionLevel Optimal
Assert-ArchiveCreated -Path $zipPath
Write-Host ''
Write-Host "Packaged commands ($((Get-ToolboxCommandNames).Count)):"
foreach ($commandName in Get-ToolboxCommandNames) {
Write-Host " - $commandName"
}
Write-Host "Portable install: $(Join-Path $PackageName 'scripts\install-package-toolbox.ps1')"
Write-Host "AI prompt: $(Join-Path $PackageName 'docs\ai\mercury-toolbox-ai-prompt.md')"
Write-Host "Codex skill: $(Join-Path $PackageName 'skills\mercury-toolbox\SKILL.md')"
Write-Host "Command catalog: $(Join-Path $PackageName 'skills\mercury-toolbox\references\command-catalog.md')"
Write-Host "Archive ready: $zipPath"