From 2e54c63caea67129f0c54cdc4911c21f1479a1a8 Mon Sep 17 00:00:00 2001 From: admin_gitea Date: Wed, 19 Aug 2026 23:12:17 +0000 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=E3=80=8Cscripts=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/install-dependencies.ps1 | 303 +++++++++++++++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 scripts/install-dependencies.ps1 diff --git a/scripts/install-dependencies.ps1 b/scripts/install-dependencies.ps1 new file mode 100644 index 0000000..0d08edb --- /dev/null +++ b/scripts/install-dependencies.ps1 @@ -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