上传文件至「scripts」

This commit is contained in:
2026-08-19 23:12:17 +00:00
parent 7a2af2646c
commit 2e54c63cae
+303
View File
@@ -0,0 +1,303 @@
<#
.SYNOPSIS
Detects and installs the tools needed to build and develop YMhut Box on Windows.
The script is intentionally standalone: it only changes machine/tool caches and
dependency directories (node_modules, NuGet, Go modules, and Cargo), not source code.
#>
[CmdletBinding()]
param(
[switch] $SkipMachineTools,
[switch] $SkipRestore,
[switch] $SkipWebView2,
[switch] $SkipVisualStudio,
[switch] $NoPrompt
)
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
$Root = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
$NuGetConfig = Join-Path $Root 'NuGet.Config'
function Write-Step([string] $Message) { Write-Host ([Environment]::NewLine + '==> ' + $Message) -ForegroundColor Cyan }
function Write-Ok([string] $Message) { Write-Host (' [OK] ' + $Message) -ForegroundColor Green }
function Write-Info([string] $Message) { Write-Host (' [INFO] ' + $Message) -ForegroundColor DarkGray }
function Get-CommandPath([string] $Name) {
$command = Get-Command $Name -ErrorAction SilentlyContinue
if ($command) { return $command.Source }
return $null
}
function Refresh-ProcessPath {
$machinePath = [Environment]::GetEnvironmentVariable('Path', 'Machine')
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
if ($machinePath -or $userPath) { $env:Path = (($machinePath, $userPath) -join ';') }
}
function Invoke-External([string] $FilePath, [string[]] $Arguments, [string] $FailureMessage) {
& $FilePath @Arguments
if ($LASTEXITCODE -ne 0) { throw ($FailureMessage + ' (exit code ' + $LASTEXITCODE + ').') }
}
function Confirm-Install([string] $Label) {
if ($NoPrompt) { return $true }
$answer = Read-Host ('Install ' + $Label + ' now? [Y/n]')
return [string]::IsNullOrWhiteSpace($answer) -or $answer -match '^(?i)y(es)?$'
}
function Install-WingetPackage([string] $Id, [string] $Label, [string[]] $ExtraArguments = @()) {
if (-not (Get-CommandPath 'winget.exe')) {
throw ('winget.exe is required to download ' + $Label + '. Install App Installer from Microsoft Store, then rerun this script.')
}
if (-not (Confirm-Install $Label)) { throw ('Installation of ' + $Label + ' was declined.') }
$arguments = @('install', '--id', $Id, '--exact', '--source', 'winget',
'--accept-source-agreements', '--accept-package-agreements', '--silent') + $ExtraArguments
Write-Info ('Downloading/installing ' + $Label + ' (' + $Id + ') via winget...')
Invoke-External 'winget.exe' $arguments ('Unable to install ' + $Label)
}
function Ensure-WingetPackage([string] $CommandName, [string] $Id, [string] $Label, [string[]] $ExtraArguments = @()) {
$path = Get-CommandPath $CommandName
if ($path) { Write-Ok ($Label + ' found at ' + $path); return $path }
if ($SkipMachineTools) { throw ($Label + ' is missing and -SkipMachineTools was specified.') }
Install-WingetPackage $Id $Label $ExtraArguments
Refresh-ProcessPath
$path = Get-CommandPath $CommandName
if (-not $path) { throw ($Label + ' was installed, but ' + $CommandName + ' is still not available in PATH. Open a new terminal and rerun.') }
Write-Ok ($Label + ' installed at ' + $path)
return $path
}
function Ensure-DotNet10 {
$dotnet = Get-CommandPath 'dotnet.exe'
$hasNet10 = $false
if ($dotnet) {
$sdkLines = & $dotnet '--list-sdks' 2>$null
$hasNet10 = @($sdkLines | Where-Object { $_ -match '^10\.\d+\.\d+' }).Count -gt 0
}
if (-not $hasNet10) {
if ($SkipMachineTools) { throw '.NET 10 SDK is missing and -SkipMachineTools was specified.' }
Install-WingetPackage 'Microsoft.DotNet.SDK.10' '.NET 10 SDK'
$dotnet = Get-CommandPath 'dotnet.exe'
if (-not $dotnet) { throw '.NET 10 SDK was installed, but dotnet.exe is not available in PATH. Open a new terminal and rerun.' }
}
Write-Ok ('.NET 10 SDK found at ' + $dotnet)
return $dotnet
}
function Ensure-DotNetWorkload([string] $DotNet, [string] $Workload) {
$installed = & $DotNet 'workload' 'list' 2>$null
if (@($installed | Where-Object { $_ -match ('^\s*' + [regex]::Escape($Workload) + '\s') }).Count -gt 0) {
Write-Ok ('.NET workload ' + $Workload + ' already installed')
return
}
if ($SkipMachineTools) { throw ('.NET workload ' + $Workload + ' is missing and -SkipMachineTools was specified.') }
if (-not (Confirm-Install ('.NET workload ' + $Workload))) { throw ('Installation of .NET workload ' + $Workload + ' was declined.') }
Invoke-External $DotNet @('workload', 'install', $Workload, '--skip-manifest-update') ('Unable to install .NET workload ' + $Workload)
}
function Ensure-Go125 {
$go = Get-CommandPath 'go.exe'
$hasGo125 = $false
if ($go) {
$version = (& $go 'version' 2>$null) -join ''
$hasGo125 = $version -match '\bgo(1\.(2[5-9]|[3-9][0-9])|[2-9][0-9]+)\.'
}
if (-not $hasGo125) {
if ($SkipMachineTools) { throw 'Go 1.25 or newer is missing and -SkipMachineTools was specified.' }
Install-WingetPackage 'GoLang.Go' 'Go 1.25 or newer'
Refresh-ProcessPath
$go = Get-CommandPath 'go.exe'
if (-not $go) { throw 'Go was installed, but go.exe is not available in PATH. Open a new terminal and rerun.' }
}
Write-Ok ('Go 1.25 or newer found at ' + $go)
return $go
}
function Ensure-Node20 {
$node = Get-CommandPath 'node.exe'
$hasNode20 = $false
if ($node) {
$version = (& $node '--version' 2>$null) -join ''
$hasNode20 = $version -match '^v(2[0-9]|[3-9][0-9]|[1-9][0-9]{2,})\.'
}
if (-not $hasNode20) {
if ($SkipMachineTools) { throw 'Node.js 20 or newer is missing and -SkipMachineTools was specified.' }
Install-WingetPackage 'OpenJS.NodeJS.LTS' 'Node.js 20 or newer'
Refresh-ProcessPath
$node = Get-CommandPath 'node.exe'
if (-not $node) { throw 'Node.js was installed, but node.exe is not available in PATH. Open a new terminal and rerun.' }
}
Write-Ok ('Node.js 20 or newer found at ' + $node)
return $node
}
function Ensure-Python310 {
$python = Get-CommandPath 'python.exe'
$hasPython310 = $false
if ($python) {
$version = (& $python '--version' 2>&1) -join ''
$hasPython310 = $version -match '^Python (3\.(1[0-9]|[2-9][0-9])|[4-9]\.[0-9]+)\.'
}
if (-not $hasPython310) {
if ($SkipMachineTools) { throw 'Python 3.10 or newer is missing and -SkipMachineTools was specified.' }
Install-WingetPackage 'Python.Python.3.13' 'Python 3.10 or newer'
Refresh-ProcessPath
$python = Get-CommandPath 'python.exe'
if (-not $python) { throw 'Python was installed, but python.exe is not available in PATH. Open a new terminal and rerun.' }
}
Write-Ok ('Python 3.10 or newer found at ' + $python)
return $python
}
function Test-WebView2Runtime {
$roots = @(
'HKLM:\SOFTWARE\Microsoft\EdgeUpdate\Clients',
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients'
)
foreach ($root in $roots) {
if (-not (Test-Path $root)) { continue }
foreach ($key in (Get-ChildItem $root -ErrorAction SilentlyContinue)) {
$properties = Get-ItemProperty $key.PSPath -ErrorAction SilentlyContinue
if ($properties.name -match 'WebView2' -and $properties.pv) {
return [string]$properties.pv
}
}
}
return $null
}
function Ensure-WebView2 {
if ($SkipWebView2) { Write-Info 'Skipping WebView2 runtime check.'; return }
$version = Test-WebView2Runtime
if ($version) { Write-Ok ('WebView2 Runtime ' + $version); return }
if ($SkipMachineTools) { throw 'WebView2 Runtime is missing and -SkipMachineTools was specified.' }
Install-WingetPackage 'Microsoft.EdgeWebView2Runtime' 'Microsoft Edge WebView2 Runtime'
$version = Test-WebView2Runtime
if (-not $version) { Write-Info 'WebView2 installer completed; restart Windows or rerun if the runtime is not detected.' }
}
function Ensure-VisualStudioBuildTools {
if ($SkipVisualStudio) { Write-Info 'Skipping Visual Studio Build Tools check.'; return }
$programFilesX86 = [Environment]::GetEnvironmentVariable('ProgramFiles(x86)')
$vswhere = Join-Path $programFilesX86 'Microsoft Visual Studio\Installer\vswhere.exe'
if (-not (Test-Path $vswhere)) {
if ($SkipMachineTools) { throw 'Visual Studio Build Tools are missing and -SkipMachineTools was specified.' }
Install-WingetPackage 'Microsoft.VisualStudio.2022.BuildTools' 'Visual Studio 2022 Build Tools' @(
'--override',
'--wait --passive --norestart --add Microsoft.VisualStudio.Workload.ManagedDesktopBuildTools --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.Windows10SDK.19041'
)
if (-not (Test-Path $vswhere)) { Write-Info 'Build Tools installation finished; rerun this script to verify workloads.'; return }
}
$installation = & $vswhere -latest -products '*' -requires Microsoft.VisualStudio.Component.Windows10SDK.19041 -property installationPath 2>$null
if ($installation) { Write-Ok ('Visual Studio Build Tools with Windows SDK found at ' + $installation) }
else { Write-Info 'Visual Studio is present, but workload verification was inconclusive; dotnet restore will provide the final check.' }
}
function Ensure-PythonModule([string] $Python, [string] $Module, [string] $Package) {
& $Python -c ('import ' + $Module) 2>$null
if ($LASTEXITCODE -eq 0) { Write-Ok ('Python module ' + $Module + ' found'); return }
if ($SkipMachineTools) { throw ('Python module ' + $Module + ' is missing and -SkipMachineTools was specified.') }
if (-not (Confirm-Install ('Python package ' + $Package))) { throw ('Installation of Python package ' + $Package + ' was declined.') }
Invoke-External $Python @('-m', 'pip', 'install', '--user', $Package) ('Unable to install Python package ' + $Package)
}
Write-Step 'Checking Windows prerequisites'
if ($env:OS -ne 'Windows_NT') { throw 'This bootstrap script supports Windows only.' }
$dotnet = Ensure-DotNet10
$node = Ensure-Node20
$npm = Ensure-WingetPackage 'npm.cmd' 'OpenJS.NodeJS.LTS' 'npm (from Node.js LTS)'
$go = Ensure-Go125
$python = Ensure-Python310
$rustup = Ensure-WingetPackage 'rustup.exe' 'Rustlang.Rustup' 'Rustup'
Ensure-WebView2
Ensure-VisualStudioBuildTools
Write-Step 'Checking language toolchains'
Ensure-DotNetWorkload $dotnet 'maui'
if ($rustup) {
Invoke-External $rustup @('toolchain', 'install', 'stable-msvc') 'Rust toolchain setup failed'
Invoke-External $rustup @('default', 'stable-msvc') 'Unable to select the MSVC Rust toolchain'
Refresh-ProcessPath
$cargo = Get-CommandPath 'cargo.exe'
if ($cargo) {
if (-not (Get-CommandPath 'wasm-pack.exe')) {
Invoke-External $cargo @('install', 'wasm-pack', '--locked') 'Unable to install wasm-pack'
} else {
Write-Ok 'wasm-pack already installed'
}
$targets = & $rustup 'target' 'list' '--installed' 2>$null
if (@($targets | Where-Object { $_ -eq 'wasm32-unknown-unknown' }).Count -eq 0) {
Invoke-External $rustup @('target', 'add', 'wasm32-unknown-unknown') 'Unable to install the wasm32 target'
} else {
Write-Ok 'wasm32-unknown-unknown target already installed'
}
Write-Ok 'Rust MSVC toolchain, wasm32 target, and wasm-pack ready'
}
}
Ensure-PythonModule $python 'PIL' 'Pillow'
Write-Step 'Installing JavaScript dependencies'
$npmProjects = @(
'server/unified-management/web/admin',
'server/unified-management/web/portal',
'server/unified-management/web/setup',
'src/YMhut.Box.PluginTauriHost'
) | ForEach-Object { Join-Path $Root $_ }
foreach ($project in $npmProjects) {
if (-not (Test-Path (Join-Path $project 'package.json'))) { continue }
Push-Location $project
try {
if (Test-Path (Join-Path $project 'package-lock.json')) {
Invoke-External $npm @('ci', '--no-audit', '--fund=false') ('npm ci failed in ' + $project)
} else {
Invoke-External $npm @('install', '--no-audit', '--fund=false', '--package-lock=false') ('npm install failed in ' + $project)
}
Write-Ok ('JavaScript dependencies installed in ' + $project)
} finally { Pop-Location }
}
$musicApi = Join-Path $Root 'third_party/MoeKoeMusic/api'
if (Test-Path (Join-Path $musicApi 'pnpm-lock.yaml')) {
Write-Info 'Installing the locked music API dependencies through Corepack.'
$corepack = Get-CommandPath 'corepack.cmd'
if (-not $corepack) { Refresh-ProcessPath; $corepack = Get-CommandPath 'corepack.cmd' }
if (-not $corepack) { throw 'corepack.cmd was not found after Node.js installation.' }
Push-Location $musicApi
try { Invoke-External $corepack @('pnpm', 'install', '--frozen-lockfile') 'pnpm install failed for the music API' } finally { Pop-Location }
}
if (-not $SkipRestore) {
Write-Step 'Restoring .NET, Go, and Rust dependencies'
Invoke-External $dotnet @('restore', (Join-Path $Root 'YMhut.Box.Native.sln'), '--configfile', $NuGetConfig) 'dotnet restore failed'
$goModule = Join-Path $Root 'server/unified-management'
if (Test-Path (Join-Path $goModule 'go.mod')) {
Push-Location $goModule
try { Invoke-External $go @('mod', 'download') 'Go module download failed' } finally { Pop-Location }
}
$cargoToml = Join-Path $Root 'server/unified-management/web/wasm/Cargo.toml'
if (Test-Path $cargoToml) {
$cargoCache = Join-Path $Root '.cache\cargo-wasm'
New-Item -ItemType Directory -Force -Path $cargoCache | Out-Null
$cargoManifest = Join-Path $cargoCache 'Cargo.toml'
$cargoSource = Join-Path $cargoCache 'src'
New-Item -ItemType Directory -Force -Path $cargoSource | Out-Null
Copy-Item -LiteralPath $cargoToml -Destination $cargoManifest -Force
Copy-Item -Path (Join-Path (Split-Path $cargoToml) 'src\*') -Destination $cargoSource -Recurse -Force
Push-Location $cargoCache
try {
$env:CARGO_TARGET_DIR = Join-Path $Root '.cache\cargo-target'
Invoke-External 'cargo.exe' @('fetch') 'WASM Cargo dependency download failed'
} finally {
Remove-Item -LiteralPath $cargoManifest -Force -ErrorAction SilentlyContinue
Pop-Location
}
}
}
Write-Host ([Environment]::NewLine + 'Dependency setup completed.') -ForegroundColor Green