enviroment-check.ps1 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. # Environment check — portable nodejs/node, python/py, root node_modules
  2. [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
  3. $OutputEncoding = [System.Text.Encoding]::UTF8
  4. chcp 65001 | Out-Null
  5. $scriptRoot = $PSScriptRoot
  6. if (-not $scriptRoot) { $scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path }
  7. if (-not $scriptRoot) { $scriptRoot = (Get-Location).Path }
  8. # Mirror source dir to dest (exFAT-safe; avoids Remove-Item on deep npm trees with junctions)
  9. function Sync-DirRobocopyMirror {
  10. param(
  11. [string]$SourceDir,
  12. [string]$DestDir
  13. )
  14. if (-not (Test-Path -LiteralPath $SourceDir)) { return $false }
  15. New-Item -ItemType Directory -Force -Path $DestDir | Out-Null
  16. & robocopy.exe $SourceDir $DestDir /MIR /R:2 /W:1 /NFL /NDL /NJH /NJS /nc /ns /np | Out-Null
  17. $rc = $LASTEXITCODE
  18. return ($rc -lt 8)
  19. }
  20. # exFAT: Git dubious ownership
  21. $repoGitDir = Join-Path $scriptRoot '.git'
  22. if ((Test-Path $repoGitDir) -and (Get-Command git -ErrorAction SilentlyContinue)) {
  23. $driveLetter = $scriptRoot.Substring(0, 2)
  24. if ($driveLetter -match '^[A-Za-z]:$') {
  25. $volInfo = & cmd.exe /c "fsutil fsinfo volumeinfo $driveLetter 2>nul" | Out-String
  26. if ($volInfo -match 'exFAT') {
  27. $rootForGit = ($scriptRoot.TrimEnd('\')) -replace '\\', '/'
  28. $listed = @(git config --global --get-all safe.directory 2>$null)
  29. $gitSafeListed = $false
  30. foreach ($entry in $listed) {
  31. if ($entry -eq $rootForGit) { $gitSafeListed = $true; break }
  32. }
  33. if (-not $gitSafeListed) {
  34. Write-Host '[exFAT] Git: registering safe.directory...' -ForegroundColor Yellow
  35. & git config --global --add safe.directory $rootForGit
  36. }
  37. }
  38. }
  39. }
  40. $nodeExeBootstrap = Join-Path $scriptRoot 'nodejs\node\node.exe'
  41. $configJs = Join-Path $scriptRoot 'config.js'
  42. if ((Test-Path $nodeExeBootstrap) -and (Test-Path $configJs)) {
  43. Push-Location $scriptRoot
  44. $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
  45. Pop-Location
  46. if ($cfgJson) {
  47. $cfg = $cfgJson | ConvertFrom-Json
  48. $nodeDir = $cfg.nodeDir
  49. $npmCmd = $cfg.npmCmdPath
  50. $npmCliPath = $cfg.npmCliPath
  51. if ($cfg.pythonDir) { $pythonEmbedRoot = $cfg.pythonDir }
  52. }
  53. }
  54. if (-not $nodeDir) { $nodeDir = Join-Path $scriptRoot 'nodejs\node' }
  55. if (-not $npmCliPath) { $npmCliPath = Join-Path $nodeDir 'node_modules\npm\bin\npm-cli.js' }
  56. if (-not $npmCmd) { $npmCmd = Join-Path $nodeDir 'npm.cmd' }
  57. $nodeExe = Join-Path $nodeDir 'node.exe'
  58. if (-not (Test-Path $nodeExe)) { $nodeExe = $null }
  59. if (-not (Test-Path $npmCmd)) { $npmCmd = $null }
  60. if (-not $pythonEmbedRoot) { $pythonEmbedRoot = Join-Path $scriptRoot 'python\py' }
  61. $pythonExe = Join-Path $pythonEmbedRoot 'python.exe'
  62. if (-not (Test-Path $pythonExe)) { $pythonExe = Join-Path $pythonEmbedRoot 'python' }
  63. Write-Host ''
  64. Write-Host 'Checking development environment...' -ForegroundColor Cyan
  65. Write-Host '================================' -ForegroundColor Cyan
  66. Write-Host ''
  67. Write-Host 'Checking Node.js (nodejs/node)...' -ForegroundColor Yellow
  68. if (-not $nodeExe) {
  69. Write-Host ('[X] Local Node not found. Put Node in: ' + $nodeDir) -ForegroundColor Red
  70. exit 1
  71. }
  72. $nodeVersion = & $nodeExe --version 2>$null
  73. if (-not $nodeVersion) {
  74. Write-Host '[X] Local node.exe failed' -ForegroundColor Red
  75. exit 1
  76. }
  77. Write-Host ('[OK] Node.js: ' + $nodeVersion) -ForegroundColor Green
  78. Write-Host ''
  79. Write-Host 'Checking npm (local)...' -ForegroundColor Yellow
  80. $npmCliPath = Join-Path $nodeDir 'node_modules\npm\bin\npm-cli.js'
  81. $npmCmd = Join-Path $nodeDir 'npm.cmd'
  82. if (-not (Test-Path $npmCmd)) { $npmCmd = $null }
  83. $nodeModulesPath = Join-Path $nodeDir 'node_modules'
  84. $npmBackupDir = Join-Path $scriptRoot 'nodejs\backup'
  85. $npmBackupNpmDir = Join-Path $npmBackupDir 'npm'
  86. New-Item -ItemType Directory -Force -Path $npmBackupDir | Out-Null
  87. $npmHealthy = $false
  88. if ((Test-Path $npmCliPath) -and $npmCmd) {
  89. $npmVersion = & $npmCmd --version 2>$null
  90. if ($npmVersion) {
  91. $npmHealthy = $true
  92. Write-Host ('[OK] npm: ' + $npmVersion) -ForegroundColor Green
  93. }
  94. }
  95. if (-not $npmHealthy) {
  96. Write-Host '[WARN] npm missing or broken; restoring from nodejs\backup\npm (robocopy)...' -ForegroundColor Yellow
  97. if (Test-Path $npmBackupNpmDir) {
  98. New-Item -ItemType Directory -Force -Path $nodeModulesPath | Out-Null
  99. $nodeNpmDir = Join-Path $nodeModulesPath 'npm'
  100. if (-not (Sync-DirRobocopyMirror -SourceDir $npmBackupNpmDir -DestDir $nodeNpmDir)) {
  101. Write-Host '[WARN] npm restore from nodejs\backup\npm failed (robocopy exit >= 8 or locks)' -ForegroundColor Yellow
  102. }
  103. if ((Test-Path $npmCliPath) -and $npmCmd) {
  104. $npmVersion = & $npmCmd --version 2>$null
  105. if ($npmVersion) { $npmHealthy = $true; Write-Host ('[OK] npm restored from backup: ' + $npmVersion) -ForegroundColor Green }
  106. }
  107. }
  108. }
  109. if (-not $npmHealthy) {
  110. $bootstrapNpmJs = Join-Path $scriptRoot 'nodejs\bootstrap-npm.js'
  111. if (Test-Path $bootstrapNpmJs) {
  112. Write-Host 'npm still missing or broken; running bootstrap-npm.js (copy from nodejs\backup\npm first; registry only if backup incomplete)...' -ForegroundColor Yellow
  113. & $nodeExe $bootstrapNpmJs
  114. if ($LASTEXITCODE -ne 0) { Write-Host '[X] bootstrap-npm.js failed' -ForegroundColor Red; exit 1 }
  115. } else {
  116. $installNpmBat = Join-Path $nodeDir 'install-npm.bat'
  117. if (-not (Test-Path $installNpmBat)) {
  118. Write-Host ('[X] Missing nodejs\bootstrap-npm.js and install-npm.bat under nodejs\node') -ForegroundColor Red
  119. exit 1
  120. }
  121. Write-Host 'Running install-npm.bat (runs bootstrap-npm.js)...' -ForegroundColor Yellow
  122. cmd /c "`"$installNpmBat`" /q"
  123. if ($LASTEXITCODE -ne 0) { Write-Host '[X] npm installation failed' -ForegroundColor Red; exit 1 }
  124. }
  125. if (-not (Test-Path $npmCliPath)) { Write-Host '[X] npm-cli.js still missing' -ForegroundColor Red; exit 1 }
  126. $npmHealthy = $true
  127. if ($npmCmd -and (Test-Path $npmCmd)) {
  128. $npmVersion = & $npmCmd --version 2>$null
  129. if ($npmVersion) { Write-Host ('[OK] npm: ' + $npmVersion) -ForegroundColor Green }
  130. else { Write-Host '[OK] npm installed to node_modules' -ForegroundColor Green }
  131. } else {
  132. Write-Host '[OK] npm installed to node_modules' -ForegroundColor Green
  133. }
  134. }
  135. if ($npmHealthy -and (Test-Path (Join-Path $nodeModulesPath 'npm'))) {
  136. $liveNpmDir = Join-Path $nodeModulesPath 'npm'
  137. if (Sync-DirRobocopyMirror -SourceDir $liveNpmDir -DestDir $npmBackupNpmDir) {
  138. Write-Host ('[OK] npm backup (robocopy mirror): ' + $npmBackupNpmDir) -ForegroundColor Green
  139. } else {
  140. Write-Host '[WARN] npm backup sync failed (robocopy exit >= 8); path or file locks?' -ForegroundColor Yellow
  141. }
  142. }
  143. if ($npmCmd) {
  144. Push-Location $scriptRoot
  145. & $npmCmd config set registry https://registry.npmmirror.com 2>$null
  146. Pop-Location
  147. }
  148. Write-Host ''
  149. Write-Host 'Project root npm dependencies...' -ForegroundColor Yellow
  150. $rootVite = Join-Path $scriptRoot 'node_modules\vite\package.json'
  151. if (-not (Test-Path $rootVite)) {
  152. Write-Host 'Running npm install at repo root...' -ForegroundColor Yellow
  153. Push-Location $scriptRoot
  154. $npmInstallOut = @(& $npmCmd install --no-audit --no-fund --loglevel=error 2>&1 | ForEach-Object { "$_" })
  155. $npmInstallExit = $LASTEXITCODE
  156. Pop-Location
  157. $installLogText = $npmInstallOut -join "`n"
  158. $npmBundleBroken = ($installLogText -match 'Cannot find module') -and (
  159. $installLogText -match 'node_modules[\\/]npm[\\/]' -or $installLogText -match 'graceful-fs'
  160. )
  161. $retryInstallOut = $null
  162. if ((-not (Test-Path $rootVite)) -and $npmBundleBroken) {
  163. $bootstrapJs = Join-Path $scriptRoot 'nodejs\bootstrap-npm.js'
  164. Write-Host 'Local npm is missing built-in modules; repairing via nodejs/bootstrap-npm.js (backup first, then registry if needed)...' -ForegroundColor Yellow
  165. if (-not (Test-Path $bootstrapJs)) {
  166. Write-Host '[X] Missing nodejs\bootstrap-npm.js; cannot repair npm' -ForegroundColor Red
  167. exit 1
  168. }
  169. & $nodeExe $bootstrapJs
  170. if ($LASTEXITCODE -ne 0) {
  171. Write-Host '[X] npm bootstrap (reinstall) failed' -ForegroundColor Red
  172. exit 1
  173. }
  174. if (-not (Test-Path $npmCliPath)) {
  175. Write-Host '[X] npm-cli.js still missing after bootstrap' -ForegroundColor Red
  176. exit 1
  177. }
  178. Push-Location $scriptRoot
  179. & $npmCmd config set registry https://registry.npmmirror.com 2>$null
  180. $retryInstallOut = @(& $npmCmd install --no-audit --no-fund --loglevel=error 2>&1 | ForEach-Object { "$_" })
  181. Pop-Location
  182. $liveNpmDir = Join-Path $nodeModulesPath 'npm'
  183. if ((Test-Path $liveNpmDir) -and (Sync-DirRobocopyMirror -SourceDir $liveNpmDir -DestDir $npmBackupNpmDir)) {
  184. Write-Host ('[OK] npm backup (robocopy mirror): ' + $npmBackupNpmDir) -ForegroundColor Green
  185. }
  186. }
  187. if (-not (Test-Path $rootVite)) {
  188. if ($retryInstallOut) {
  189. Write-Host ($retryInstallOut -join "`n")
  190. } elseif ($npmInstallExit -ne 0 -and $installLogText) {
  191. Write-Host $installLogText
  192. }
  193. Write-Host '[X] Root npm install did not install vite' -ForegroundColor Red
  194. exit 1
  195. }
  196. }
  197. Write-Host '[OK] Root node_modules ready' -ForegroundColor Green
  198. if (-not (Test-Path $npmCliPath)) {
  199. Write-Host '[X] npm-cli.js missing after install' -ForegroundColor Red
  200. exit 1
  201. }
  202. Write-Host ''
  203. Write-Host 'Checking Python (python/py)...' -ForegroundColor Yellow
  204. if (-not (Test-Path $pythonExe)) {
  205. Write-Host ('[X] Python not found: ' + $pythonExe) -ForegroundColor Red
  206. exit 1
  207. }
  208. Write-Host '[OK] python found' -ForegroundColor Green
  209. Write-Host ''
  210. Write-Host 'Embedded Python toolchain (python/backup)...' -ForegroundColor Yellow
  211. $embSitePackages = Join-Path $pythonEmbedRoot 'Lib\site-packages'
  212. $bakSitePackages = Join-Path $scriptRoot 'python\backup\site-packages'
  213. $pypiIndexUrl = 'https://pypi.tuna.tsinghua.edu.cn/simple'
  214. $pypiTrustedHost = 'pypi.tuna.tsinghua.edu.cn'
  215. New-Item -ItemType Directory -Force -Path (Join-Path $scriptRoot 'python\backup') | Out-Null
  216. New-Item -ItemType Directory -Force -Path $bakSitePackages | Out-Null
  217. $requiredPipFiles = @(
  218. (Join-Path $embSitePackages 'pip\__main__.py'),
  219. (Join-Path $embSitePackages 'pip\_internal\cli\main.py')
  220. )
  221. $pipOk = @($requiredPipFiles | Where-Object { -not (Test-Path $_) }).Count -eq 0
  222. $getPipScript = Join-Path $scriptRoot 'python\get-pip.py'
  223. if (-not $pipOk) {
  224. 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
  225. Write-Host 'Copying site-packages from python\backup\site-packages into embedded Lib\site-packages...' -ForegroundColor Yellow
  226. Get-ChildItem -LiteralPath $bakSitePackages -ErrorAction SilentlyContinue | ForEach-Object {
  227. Copy-Item -LiteralPath $_.FullName -Destination $embSitePackages -Recurse -Force
  228. }
  229. $pipOk = @($requiredPipFiles | Where-Object { -not (Test-Path $_) }).Count -eq 0
  230. }
  231. if (-not $pipOk -and (Test-Path $getPipScript)) {
  232. Write-Host 'pip still missing or broken after backup copy; running get-pip.py (network download)...' -ForegroundColor Yellow
  233. & $pythonExe $getPipScript --force-reinstall -i $pypiIndexUrl --trusted-host $pypiTrustedHost --no-warn-script-location
  234. if ($LASTEXITCODE -ne 0) { Write-Host '[WARN] get-pip.py exited with an error' -ForegroundColor Yellow }
  235. $pipOk = @($requiredPipFiles | Where-Object { -not (Test-Path $_) }).Count -eq 0
  236. }
  237. if ($pipOk) {
  238. Write-Host '[OK] pip ready' -ForegroundColor Green
  239. } else {
  240. 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
  241. }
  242. Write-Host ''
  243. Write-Host 'Python packages (python/python-enviroment-install.py)...' -ForegroundColor Yellow
  244. $pyInstallScript = Join-Path $scriptRoot 'python\python-enviroment-install.py'
  245. if (-not (Test-Path $pyInstallScript)) {
  246. Write-Host ('[X] Missing ' + $pyInstallScript) -ForegroundColor Red
  247. exit 1
  248. }
  249. & $pythonExe $pyInstallScript
  250. if ($LASTEXITCODE -ne 0) {
  251. Write-Host '[X] Python dependency install failed' -ForegroundColor Red
  252. exit 1
  253. }
  254. Write-Host ''
  255. Write-Host '================================' -ForegroundColor Cyan
  256. Write-Host 'Environment check completed!' -ForegroundColor Green