millwright 2.2.1

A unified ML framework for Rust — proven Rust crates, assembled into one machine.
Documentation
param(
    [Parameter(Mandatory = $true, Position = 0)]
    [ValidatePattern('^\d+\.\d+\.\d+$')]
    [string]$Version
)

$ErrorActionPreference = 'Stop'
$root = (git rev-parse --show-toplevel).Trim()
Set-Location -LiteralPath $root

# Codex, Git Bash, and other Unix-flavoured Windows shells can put GNU
# `link.exe` ahead of Microsoft's linker. Import the official developer-shell
# environment before invoking Cargo so both link.exe and cl.exe are correct.
$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
if (Test-Path -LiteralPath $vswhere) {
    $installation = (& $vswhere -latest -products * -property installationPath).Trim()
    if ($installation) {
        $vsDevCmd = Join-Path $installation 'Common7\Tools\VsDevCmd.bat'
        if (Test-Path -LiteralPath $vsDevCmd) {
            & cmd.exe /d /s /c "`"$vsDevCmd`" -no_logo -arch=x64 && set" |
                ForEach-Object {
                    if ($_ -match '^([^=]+)=(.*)$') {
                        [Environment]::SetEnvironmentVariable($matches[1], $matches[2], 'Process')
                    }
                }
        }
    }
}

if (git status --porcelain) { throw 'working tree is not clean' }
if ((git branch --show-current).Trim() -ne 'main') { throw 'not on main' }
$tag = "v$Version"
git rev-parse --verify --quiet "refs/tags/$tag" 2>$null
if ($LASTEXITCODE -eq 0) { throw "tag $tag already exists" }

$releaseFiles = @('Cargo.toml', 'Cargo.lock', 'pyproject.toml', 'CHANGELOG.md', 'index.html', 'guide.html', 'docs/index.html', 'tests/python_smoke.py')
$committed = $false
try {
    python scripts/sync-version.py $Version
    $date = Get-Date -Format 'yyyy-MM-dd'
    $changelog = Get-Content -Raw CHANGELOG.md
    $changelog = $changelog -replace '## \[Unreleased\]', "## [Unreleased]`n`n## [$Version] - $date"
    [IO.File]::WriteAllText((Join-Path $root 'CHANGELOG.md'), $changelog.Replace("`r`n", "`n"))

    cargo build --locked
    if ($LASTEXITCODE) { throw 'cargo build failed' }
    cargo publish --dry-run --locked --allow-dirty
    if ($LASTEXITCODE) { throw 'cargo publish dry-run failed' }

    git add -- $releaseFiles
    git commit -m "Release $Version"
    $committed = $true
    git tag $tag

    $reply = Read-Host "Push main + $tag now? This publishes to crates.io and PyPI. [y/N]"
    if ($reply -in @('y', 'Y')) {
        git push origin main $tag
    } else {
        Write-Host "Not pushed. When ready: git push origin main $tag"
    }
} catch {
    if (-not $committed) { git restore --staged --worktree -- $releaseFiles 2>$null }
    throw
}