上传文件至「scripts」
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user