# Environment check — portable nodejs/node, python/py, root node_modules [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 $OutputEncoding = [System.Text.Encoding]::UTF8 chcp 65001 | Out-Null $scriptRoot = $PSScriptRoot if (-not $scriptRoot) { $scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path } if (-not $scriptRoot) { $scriptRoot = (Get-Location).Path } # Mirror source dir to dest (exFAT-safe; avoids Remove-Item on deep npm trees with junctions) function Sync-DirRobocopyMirror { param( [string]$SourceDir, [string]$DestDir ) if (-not (Test-Path -LiteralPath $SourceDir)) { return $false } New-Item -ItemType Directory -Force -Path $DestDir | Out-Null & robocopy.exe $SourceDir $DestDir /MIR /R:2 /W:1 /NFL /NDL /NJH /NJS /nc /ns /np | Out-Null $rc = $LASTEXITCODE return ($rc -lt 8) } # exFAT: Git dubious ownership $repoGitDir = Join-Path $scriptRoot '.git' if ((Test-Path $repoGitDir) -and (Get-Command git -ErrorAction SilentlyContinue)) { $driveLetter = $scriptRoot.Substring(0, 2) if ($driveLetter -match '^[A-Za-z]:$') { $volInfo = & cmd.exe /c "fsutil fsinfo volumeinfo $driveLetter 2>nul" | Out-String if ($volInfo -match 'exFAT') { $rootForGit = ($scriptRoot.TrimEnd('\')) -replace '\\', '/' $listed = @(git config --global --get-all safe.directory 2>$null) $gitSafeListed = $false foreach ($entry in $listed) { if ($entry -eq $rootForGit) { $gitSafeListed = $true; break } } if (-not $gitSafeListed) { Write-Host '[exFAT] Git: registering safe.directory...' -ForegroundColor Yellow & git config --global --add safe.directory $rootForGit } } } } $nodeExeBootstrap = Join-Path $scriptRoot 'nodejs\node\node.exe' $configJs = Join-Path $scriptRoot 'config.js' if ((Test-Path $nodeExeBootstrap) -and (Test-Path $configJs)) { Push-Location $scriptRoot $cfgJson = & $nodeExeBootstrap '-e' "const c=require('./config.js');console.log(JSON.stringify({nodeDir:c.nodeDir,npmCmdPath:c.npmCmdPath,npmCliPath:c.npmCliPath,pythonDir:c.pythonDir||(c.pythonPath&&c.pythonPath.path)}))" 2>$null Pop-Location if ($cfgJson) { $cfg = $cfgJson | ConvertFrom-Json $nodeDir = $cfg.nodeDir $npmCmd = $cfg.npmCmdPath $npmCliPath = $cfg.npmCliPath if ($cfg.pythonDir) { $pythonEmbedRoot = $cfg.pythonDir } } } if (-not $nodeDir) { $nodeDir = Join-Path $scriptRoot 'nodejs\node' } if (-not $npmCliPath) { $npmCliPath = Join-Path $nodeDir 'node_modules\npm\bin\npm-cli.js' } if (-not $npmCmd) { $npmCmd = Join-Path $nodeDir 'npm.cmd' } $nodeExe = Join-Path $nodeDir 'node.exe' if (-not (Test-Path $nodeExe)) { $nodeExe = $null } if (-not (Test-Path $npmCmd)) { $npmCmd = $null } if (-not $pythonEmbedRoot) { $pythonEmbedRoot = Join-Path $scriptRoot 'python\py' } $pythonExe = Join-Path $pythonEmbedRoot 'python.exe' if (-not (Test-Path $pythonExe)) { $pythonExe = Join-Path $pythonEmbedRoot 'python' } Write-Host '' Write-Host 'Checking development environment...' -ForegroundColor Cyan Write-Host '================================' -ForegroundColor Cyan Write-Host '' Write-Host 'Checking Node.js (nodejs/node)...' -ForegroundColor Yellow if (-not $nodeExe) { Write-Host ('[X] Local Node not found. Put Node in: ' + $nodeDir) -ForegroundColor Red exit 1 } $nodeVersion = & $nodeExe --version 2>$null if (-not $nodeVersion) { Write-Host '[X] Local node.exe failed' -ForegroundColor Red exit 1 } Write-Host ('[OK] Node.js: ' + $nodeVersion) -ForegroundColor Green Write-Host '' Write-Host 'Checking npm (local)...' -ForegroundColor Yellow $npmCliPath = Join-Path $nodeDir 'node_modules\npm\bin\npm-cli.js' $npmCmd = Join-Path $nodeDir 'npm.cmd' if (-not (Test-Path $npmCmd)) { $npmCmd = $null } $nodeModulesPath = Join-Path $nodeDir 'node_modules' $npmBackupDir = Join-Path $scriptRoot 'nodejs\backup' $npmBackupNpmDir = Join-Path $npmBackupDir 'npm' New-Item -ItemType Directory -Force -Path $npmBackupDir | Out-Null $npmHealthy = $false if ((Test-Path $npmCliPath) -and $npmCmd) { $npmVersion = & $npmCmd --version 2>$null if ($npmVersion) { $npmHealthy = $true Write-Host ('[OK] npm: ' + $npmVersion) -ForegroundColor Green } } if (-not $npmHealthy) { Write-Host '[WARN] npm missing or broken; restoring from nodejs\backup\npm (robocopy)...' -ForegroundColor Yellow if (Test-Path $npmBackupNpmDir) { New-Item -ItemType Directory -Force -Path $nodeModulesPath | Out-Null $nodeNpmDir = Join-Path $nodeModulesPath 'npm' if (-not (Sync-DirRobocopyMirror -SourceDir $npmBackupNpmDir -DestDir $nodeNpmDir)) { Write-Host '[WARN] npm restore from nodejs\backup\npm failed (robocopy exit >= 8 or locks)' -ForegroundColor Yellow } if ((Test-Path $npmCliPath) -and $npmCmd) { $npmVersion = & $npmCmd --version 2>$null if ($npmVersion) { $npmHealthy = $true; Write-Host ('[OK] npm restored from backup: ' + $npmVersion) -ForegroundColor Green } } } } if (-not $npmHealthy) { $bootstrapNpmJs = Join-Path $scriptRoot 'nodejs\bootstrap-npm.js' if (Test-Path $bootstrapNpmJs) { Write-Host 'npm still missing or broken; running bootstrap-npm.js (copy from nodejs\backup\npm first; registry only if backup incomplete)...' -ForegroundColor Yellow & $nodeExe $bootstrapNpmJs if ($LASTEXITCODE -ne 0) { Write-Host '[X] bootstrap-npm.js failed' -ForegroundColor Red; exit 1 } } else { $installNpmBat = Join-Path $nodeDir 'install-npm.bat' if (-not (Test-Path $installNpmBat)) { Write-Host ('[X] Missing nodejs\bootstrap-npm.js and install-npm.bat under nodejs\node') -ForegroundColor Red exit 1 } Write-Host 'Running install-npm.bat (runs bootstrap-npm.js)...' -ForegroundColor Yellow cmd /c "`"$installNpmBat`" /q" if ($LASTEXITCODE -ne 0) { Write-Host '[X] npm installation failed' -ForegroundColor Red; exit 1 } } if (-not (Test-Path $npmCliPath)) { Write-Host '[X] npm-cli.js still missing' -ForegroundColor Red; exit 1 } $npmHealthy = $true if ($npmCmd -and (Test-Path $npmCmd)) { $npmVersion = & $npmCmd --version 2>$null if ($npmVersion) { Write-Host ('[OK] npm: ' + $npmVersion) -ForegroundColor Green } else { Write-Host '[OK] npm installed to node_modules' -ForegroundColor Green } } else { Write-Host '[OK] npm installed to node_modules' -ForegroundColor Green } } if ($npmHealthy -and (Test-Path (Join-Path $nodeModulesPath 'npm'))) { $liveNpmDir = Join-Path $nodeModulesPath 'npm' if (Sync-DirRobocopyMirror -SourceDir $liveNpmDir -DestDir $npmBackupNpmDir) { Write-Host ('[OK] npm backup (robocopy mirror): ' + $npmBackupNpmDir) -ForegroundColor Green } else { Write-Host '[WARN] npm backup sync failed (robocopy exit >= 8); path or file locks?' -ForegroundColor Yellow } } if ($npmCmd) { Push-Location $scriptRoot & $npmCmd config set registry https://registry.npmmirror.com 2>$null Pop-Location } Write-Host '' Write-Host 'Project root npm dependencies...' -ForegroundColor Yellow $rootVite = Join-Path $scriptRoot 'node_modules\vite\package.json' if (-not (Test-Path $rootVite)) { Write-Host 'Running npm install at repo root...' -ForegroundColor Yellow Push-Location $scriptRoot $npmInstallOut = @(& $npmCmd install --no-audit --no-fund --loglevel=error 2>&1 | ForEach-Object { "$_" }) $npmInstallExit = $LASTEXITCODE Pop-Location $installLogText = $npmInstallOut -join "`n" $npmBundleBroken = ($installLogText -match 'Cannot find module') -and ( $installLogText -match 'node_modules[\\/]npm[\\/]' -or $installLogText -match 'graceful-fs' ) $retryInstallOut = $null if ((-not (Test-Path $rootVite)) -and $npmBundleBroken) { $bootstrapJs = Join-Path $scriptRoot 'nodejs\bootstrap-npm.js' Write-Host 'Local npm is missing built-in modules; repairing via nodejs/bootstrap-npm.js (backup first, then registry if needed)...' -ForegroundColor Yellow if (-not (Test-Path $bootstrapJs)) { Write-Host '[X] Missing nodejs\bootstrap-npm.js; cannot repair npm' -ForegroundColor Red exit 1 } & $nodeExe $bootstrapJs if ($LASTEXITCODE -ne 0) { Write-Host '[X] npm bootstrap (reinstall) failed' -ForegroundColor Red exit 1 } if (-not (Test-Path $npmCliPath)) { Write-Host '[X] npm-cli.js still missing after bootstrap' -ForegroundColor Red exit 1 } Push-Location $scriptRoot & $npmCmd config set registry https://registry.npmmirror.com 2>$null $retryInstallOut = @(& $npmCmd install --no-audit --no-fund --loglevel=error 2>&1 | ForEach-Object { "$_" }) Pop-Location $liveNpmDir = Join-Path $nodeModulesPath 'npm' if ((Test-Path $liveNpmDir) -and (Sync-DirRobocopyMirror -SourceDir $liveNpmDir -DestDir $npmBackupNpmDir)) { Write-Host ('[OK] npm backup (robocopy mirror): ' + $npmBackupNpmDir) -ForegroundColor Green } } if (-not (Test-Path $rootVite)) { if ($retryInstallOut) { Write-Host ($retryInstallOut -join "`n") } elseif ($npmInstallExit -ne 0 -and $installLogText) { Write-Host $installLogText } Write-Host '[X] Root npm install did not install vite' -ForegroundColor Red exit 1 } } Write-Host '[OK] Root node_modules ready' -ForegroundColor Green if (-not (Test-Path $npmCliPath)) { Write-Host '[X] npm-cli.js missing after install' -ForegroundColor Red exit 1 } Write-Host '' Write-Host 'Checking Python (python/py)...' -ForegroundColor Yellow if (-not (Test-Path $pythonExe)) { Write-Host ('[X] Python not found: ' + $pythonExe) -ForegroundColor Red exit 1 } Write-Host '[OK] python found' -ForegroundColor Green Write-Host '' Write-Host 'Embedded Python toolchain (python/backup)...' -ForegroundColor Yellow $embSitePackages = Join-Path $pythonEmbedRoot 'Lib\site-packages' $bakSitePackages = Join-Path $scriptRoot 'python\backup\site-packages' $pypiIndexUrl = 'https://pypi.tuna.tsinghua.edu.cn/simple' $pypiTrustedHost = 'pypi.tuna.tsinghua.edu.cn' New-Item -ItemType Directory -Force -Path (Join-Path $scriptRoot 'python\backup') | Out-Null New-Item -ItemType Directory -Force -Path $bakSitePackages | Out-Null $requiredPipFiles = @( (Join-Path $embSitePackages 'pip\__main__.py'), (Join-Path $embSitePackages 'pip\_internal\cli\main.py') ) $pipOk = @($requiredPipFiles | Where-Object { -not (Test-Path $_) }).Count -eq 0 $getPipScript = Join-Path $scriptRoot 'python\get-pip.py' if (-not $pipOk) { Write-Host '[i] pip repair order: (1) copy from python\backup\site-packages (2) get-pip.py from mirror only if backup cannot restore pip' -ForegroundColor DarkCyan Write-Host 'Copying site-packages from python\backup\site-packages into embedded Lib\site-packages...' -ForegroundColor Yellow Get-ChildItem -LiteralPath $bakSitePackages -ErrorAction SilentlyContinue | ForEach-Object { Copy-Item -LiteralPath $_.FullName -Destination $embSitePackages -Recurse -Force } $pipOk = @($requiredPipFiles | Where-Object { -not (Test-Path $_) }).Count -eq 0 } if (-not $pipOk -and (Test-Path $getPipScript)) { Write-Host 'pip still missing or broken after backup copy; running get-pip.py (network download)...' -ForegroundColor Yellow & $pythonExe $getPipScript --force-reinstall -i $pypiIndexUrl --trusted-host $pypiTrustedHost --no-warn-script-location if ($LASTEXITCODE -ne 0) { Write-Host '[WARN] get-pip.py exited with an error' -ForegroundColor Yellow } $pipOk = @($requiredPipFiles | Where-Object { -not (Test-Path $_) }).Count -eq 0 } if ($pipOk) { Write-Host '[OK] pip ready' -ForegroundColor Green } else { Write-Host '[WARN] pip still incomplete after backup and get-pip.py; fill python\backup\site-packages or add python\get-pip.py' -ForegroundColor Yellow } Write-Host '' Write-Host 'Python packages (python/python-enviroment-install.py)...' -ForegroundColor Yellow $pyInstallScript = Join-Path $scriptRoot 'python\python-enviroment-install.py' if (-not (Test-Path $pyInstallScript)) { Write-Host ('[X] Missing ' + $pyInstallScript) -ForegroundColor Red exit 1 } & $pythonExe $pyInstallScript if ($LASTEXITCODE -ne 0) { Write-Host '[X] Python dependency install failed' -ForegroundColor Red exit 1 } Write-Host '' Write-Host '================================' -ForegroundColor Cyan Write-Host 'Environment check completed!' -ForegroundColor Green