enviroment-check.ps1 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. # Environment check - use local nodejs/node and python/x64 only
  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. # 架构:根据 PROCESSOR_ARCHITECTURE
  9. $arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'x64' }
  10. # 从 config.js 读取路径(需先找到 node 来执行 get-node-paths.js)
  11. $nodeExeBootstrap = $null
  12. foreach ($tryNode in @(
  13. (Join-Path $scriptRoot ('nodejs\' + $arch + '\node\node.exe')),
  14. (Join-Path $scriptRoot 'nodejs\node\node.exe')
  15. )) {
  16. if (Test-Path $tryNode) { $nodeExeBootstrap = $tryNode; break }
  17. }
  18. $getPathsScript = Join-Path $scriptRoot 'configs\get-node-paths.js'
  19. if ($nodeExeBootstrap -and (Test-Path $getPathsScript)) {
  20. try {
  21. Push-Location $scriptRoot
  22. $cfgJson = & $nodeExeBootstrap $getPathsScript 2>$null
  23. if ($cfgJson) {
  24. $cfg = $cfgJson | ConvertFrom-Json
  25. $nodeDir = $cfg.nodeDir
  26. $npmCmd = $cfg.npmCmdPath
  27. $npmCliPath = $cfg.npmCliPath
  28. if ($cfg.pythonDir) { $pythonRoot = $cfg.pythonDir }
  29. if ($cfg.pythonVenvPath) { $venvPath = $cfg.pythonVenvPath }
  30. }
  31. } catch { }
  32. finally { Pop-Location }
  33. }
  34. if (-not $nodeDir) { $nodeDir = Join-Path $scriptRoot ('nodejs\' + $arch + '\node'); if (-not (Test-Path $nodeDir)) { $nodeDir = Join-Path $scriptRoot 'nodejs\node' } }
  35. if (-not $npmCliPath) { $npmCliPath = Join-Path $nodeDir 'node_modules\npm\bin\npm-cli.js' }
  36. if (-not $npmCmd) { $npmCmd = Join-Path $nodeDir 'npm.cmd' }
  37. $nodeExe = Join-Path $nodeDir 'node.exe'
  38. if (-not (Test-Path $nodeExe)) { $nodeExe = $null }
  39. if (-not (Test-Path $npmCmd)) { $npmCmd = $null }
  40. if (-not $pythonRoot) { $pythonRoot = Join-Path $scriptRoot ('python\' + $arch) }
  41. # Python 优先在 python/x64/py 下查找
  42. $pythonExe = $null
  43. foreach ($p in @(
  44. (Join-Path $pythonRoot 'py\python.exe'),
  45. (Join-Path $pythonRoot 'env\Scripts\python.exe'),
  46. (Join-Path $pythonRoot 'python.exe')
  47. )) {
  48. if (Test-Path $p) { $pythonExe = $p; break }
  49. }
  50. Write-Host ''
  51. Write-Host ('Checking development environment (local nodejs/node and python/' + $arch + ')...') -ForegroundColor Cyan
  52. Write-Host '================================' -ForegroundColor Cyan
  53. Write-Host ''
  54. Write-Host 'Checking Node.js (local nodejs/node)...' -ForegroundColor Yellow
  55. if (-not $nodeExe) {
  56. Write-Host ('[X] Local Node not found. Put Node in: ' + $nodeDir) -ForegroundColor Red
  57. exit 1
  58. }
  59. $nodeVersion = & $nodeExe --version 2>$null
  60. if ($nodeVersion) {
  61. Write-Host ('[OK] Node.js: ' + $nodeVersion) -ForegroundColor Green
  62. } else {
  63. Write-Host '[X] Local node.exe failed' -ForegroundColor Red
  64. exit 1
  65. }
  66. Write-Host ''
  67. Write-Host 'Checking npm (local)...' -ForegroundColor Yellow
  68. # 使用已验证的 nodeDir 构建 npm 路径,确保检查正确
  69. $npmCliPath = Join-Path $nodeDir 'node_modules\npm\bin\npm-cli.js'
  70. $npmCmd = Join-Path $nodeDir 'npm.cmd'
  71. if (-not (Test-Path $npmCmd)) { $npmCmd = $null }
  72. $nodeModulesPath = Join-Path $nodeDir 'node_modules'
  73. $npmBackupDir = Join-Path $scriptRoot ('nodejs\dependences\' + $arch + '\backup')
  74. $npmBackupNpmDir = Join-Path $npmBackupDir 'npm'
  75. New-Item -ItemType Directory -Force -Path $npmBackupDir | Out-Null
  76. $npmHealthy = $false
  77. if ((Test-Path $npmCliPath) -and $npmCmd) {
  78. $npmVersion = & $npmCmd --version 2>$null
  79. if ($npmVersion) {
  80. $npmHealthy = $true
  81. Write-Host ('[OK] npm: ' + $npmVersion + ' (already installed)') -ForegroundColor Green
  82. }
  83. }
  84. if (-not $npmHealthy) {
  85. Write-Host '[WARN] npm missing or broken, trying backup restore first...' -ForegroundColor Yellow
  86. if (Test-Path $npmBackupNpmDir) {
  87. New-Item -ItemType Directory -Force -Path $nodeModulesPath | Out-Null
  88. $nodeNpmDir = Join-Path $nodeModulesPath 'npm'
  89. if (Test-Path $nodeNpmDir) { Remove-Item -LiteralPath $nodeNpmDir -Recurse -Force }
  90. Copy-Item -LiteralPath $npmBackupNpmDir -Destination $nodeModulesPath -Recurse -Force
  91. if ((Test-Path $npmCliPath) -and $npmCmd) {
  92. $npmVersion = & $npmCmd --version 2>$null
  93. if ($npmVersion) {
  94. $npmHealthy = $true
  95. Write-Host ('[OK] npm restored from backup: ' + $npmVersion) -ForegroundColor Green
  96. }
  97. }
  98. }
  99. }
  100. if (-not $npmHealthy) {
  101. Write-Host '[WARN] Backup restore failed, installing/downloading npm...' -ForegroundColor Yellow
  102. $installNpmBat = Join-Path $nodeDir 'install-npm.bat'
  103. if (Test-Path $installNpmBat) {
  104. Write-Host 'Running install-npm.bat to install npm...' -ForegroundColor Yellow
  105. cmd /c "`"$installNpmBat`" /q"
  106. if ($LASTEXITCODE -ne 0) {
  107. Write-Host '[X] npm installation failed' -ForegroundColor Red
  108. exit 1
  109. }
  110. if (-not (Test-Path $npmCliPath)) {
  111. Write-Host '[X] npm-cli.js still missing after install. Check network or run install-npm.bat manually.' -ForegroundColor Red
  112. exit 1
  113. }
  114. $npmVersion = & $npmCmd --version 2>$null
  115. if (-not $npmVersion) {
  116. Write-Host '[X] npm installed but still unusable' -ForegroundColor Red
  117. exit 1
  118. }
  119. $npmHealthy = $true
  120. Write-Host ('[OK] npm installed: ' + $npmVersion) -ForegroundColor Green
  121. $generateScript = Join-Path $scriptRoot 'nodejs\dependences\generate-nodejs-dependencies.js'
  122. if (Test-Path $generateScript) {
  123. Write-Host 'Running generate-nodejs-dependencies.js...' -ForegroundColor Yellow
  124. & $nodeExe $generateScript
  125. if ($LASTEXITCODE -ne 0) {
  126. Write-Host '[WARN] generate-nodejs-dependencies.js failed (will retry later)' -ForegroundColor Yellow
  127. }
  128. }
  129. } else {
  130. Write-Host ('[X] Run ' + $installNpmBat + ' to install npm') -ForegroundColor Red
  131. exit 1
  132. }
  133. }
  134. if ($npmHealthy -and (Test-Path (Join-Path $nodeModulesPath 'npm'))) {
  135. if (Test-Path $npmBackupNpmDir) { Remove-Item -LiteralPath $npmBackupNpmDir -Recurse -Force }
  136. Copy-Item -LiteralPath (Join-Path $nodeModulesPath 'npm') -Destination $npmBackupDir -Recurse -Force
  137. Write-Host ('[OK] npm backup synced: ' + $npmBackupNpmDir) -ForegroundColor Green
  138. }
  139. # Use domestic npm registry for all subsequent npm operations
  140. if ($npmCmd) {
  141. Push-Location $scriptRoot
  142. & $npmCmd config set registry https://registry.npmmirror.com 2>$null
  143. Pop-Location
  144. }
  145. Write-Host ''
  146. Write-Host 'Checking Node.js dependencies (nodejs/node vs dependencies.txt)...' -ForegroundColor Yellow
  147. $nodeModulesPath = Join-Path $nodeDir 'node_modules'
  148. $depsTxt = Join-Path $scriptRoot ('nodejs\dependences\' + $arch + '\dependencies.txt')
  149. $nodeDependenciesScript = Join-Path $scriptRoot ('nodejs\dependences\' + $arch + '\nodejs-dependencies-install.js')
  150. # Ensure package.json deps (vite, react, etc.) are installed; updates dependencies.txt
  151. if (Test-Path $nodeDependenciesScript) {
  152. & $nodeExe $nodeDependenciesScript
  153. if ($LASTEXITCODE -ne 0) {
  154. Write-Host '[X] Node.js dependencies install failed' -ForegroundColor Red
  155. exit 1
  156. }
  157. }
  158. # npm install prunes extraneous packages; npm (not in package.json) gets removed. Reinstall if missing.
  159. if (-not (Test-Path $npmCliPath)) {
  160. $installNpmBat = Join-Path $nodeDir 'install-npm.bat'
  161. if (Test-Path $installNpmBat) {
  162. Write-Host 'npm was pruned by npm install. Reinstalling npm...' -ForegroundColor Yellow
  163. cmd /c "`"$installNpmBat`" /q"
  164. if ($LASTEXITCODE -ne 0 -or -not (Test-Path $npmCliPath)) {
  165. Write-Host '[X] npm reinstall failed' -ForegroundColor Red
  166. exit 1
  167. }
  168. Write-Host '[OK] npm restored' -ForegroundColor Green
  169. }
  170. }
  171. if (-not (Test-Path $nodeModulesPath)) {
  172. Write-Host '[X] nodejs/node/node_modules not found' -ForegroundColor Red
  173. exit 1
  174. }
  175. if (-not (Test-Path $depsTxt)) {
  176. $generateScript = Join-Path $scriptRoot 'nodejs\dependences\generate-nodejs-dependencies.js'
  177. if (Test-Path $generateScript) {
  178. Write-Host 'dependencies.txt not found. Running generate-nodejs-dependencies.js...' -ForegroundColor Yellow
  179. & $nodeExe $generateScript
  180. if ($LASTEXITCODE -ne 0 -and -not (Test-Path $depsTxt)) {
  181. Write-Host 'Generate failed (node_modules may be empty). Running nodejs-dependencies-install...' -ForegroundColor Yellow
  182. if (Test-Path $nodeDependenciesScript) {
  183. & $nodeExe $nodeDependenciesScript
  184. if ($LASTEXITCODE -ne 0) {
  185. Write-Host '[X] Failed to install dependencies and create dependencies.txt' -ForegroundColor Red
  186. exit 1
  187. }
  188. }
  189. if (-not (Test-Path $depsTxt)) {
  190. Write-Host '[X] dependencies.txt still not found: ' $depsTxt -ForegroundColor Red
  191. exit 1
  192. }
  193. }
  194. } else {
  195. Write-Host '[X] dependencies.txt not found: ' $depsTxt -ForegroundColor Red
  196. exit 1
  197. }
  198. }
  199. $requiredLines = Get-Content $depsTxt -Encoding UTF8 | Where-Object { $_.Trim() -and -not $_.Trim().StartsWith('#') }
  200. $missing = @()
  201. foreach ($line in $requiredLines) {
  202. $pkgSpec = $line.Trim().Split('==', 2)[0].Trim()
  203. if (-not $pkgSpec) { continue }
  204. if ($pkgSpec -match '^@([^/]+)/(.+)$') {
  205. $subPath = Join-Path $nodeModulesPath ('@' + $Matches[1])
  206. $pkgPath = Join-Path $subPath $Matches[2]
  207. } else {
  208. $pkgPath = Join-Path $nodeModulesPath $pkgSpec
  209. }
  210. if (-not (Test-Path $pkgPath)) { $missing += $pkgSpec }
  211. }
  212. if ($missing.Count -gt 0) {
  213. Write-Host ('[X] Missing ' + $missing.Count + ' package(s) in nodejs/node/node_modules (per dependencies.txt):') -ForegroundColor Red
  214. $missing | Select-Object -First 15 | ForEach-Object { Write-Host ' - ' $_ -ForegroundColor Red }
  215. if ($missing.Count -gt 15) { Write-Host (' ... and ' + ($missing.Count - 15) + ' more') -ForegroundColor Red }
  216. if (Test-Path $nodeDependenciesScript) {
  217. Write-Host 'Running nodejs-dependencies-install.js to install missing...' -ForegroundColor Yellow
  218. & $nodeExe $nodeDependenciesScript
  219. if ($LASTEXITCODE -ne 0) {
  220. Write-Host '[X] Node dependencies installation failed' -ForegroundColor Red
  221. exit 1
  222. }
  223. } else {
  224. exit 1
  225. }
  226. } else {
  227. Write-Host ('[OK] Node dependencies match dependencies.txt (' + $requiredLines.Count + ' packages)') -ForegroundColor Green
  228. }
  229. $generateScript = Join-Path $scriptRoot 'nodejs\dependences\generate-nodejs-dependencies.js'
  230. if (Test-Path $generateScript) {
  231. & $nodeExe $generateScript 2>$null
  232. }
  233. Write-Host ''
  234. Write-Host ('Checking Python (local python/' + $arch + ')...') -ForegroundColor Yellow
  235. if (-not $pythonExe) {
  236. Write-Host ('[X] Local Python not found. Put Python in: ' + $pythonRoot) -ForegroundColor Red
  237. exit 1
  238. }
  239. Write-Host '[OK] python found' -ForegroundColor Green
  240. Write-Host ''
  241. Write-Host 'Embedded Python (py): prefer backup restore, network only as last fallback...' -ForegroundColor Yellow
  242. $pyEmbeddedExe = Join-Path $pythonRoot 'py\python.exe'
  243. $embSitePackages = Join-Path $pythonRoot 'py\Lib\site-packages'
  244. $bakRoot = Join-Path $pythonRoot 'backup'
  245. $bakSitePackages = Join-Path $pythonRoot 'backup\site-packages'
  246. $pypiIndexUrl = 'https://pypi.tuna.tsinghua.edu.cn/simple'
  247. $pypiTrustedHost = 'pypi.tuna.tsinghua.edu.cn'
  248. New-Item -ItemType Directory -Force -Path $bakRoot | Out-Null
  249. New-Item -ItemType Directory -Force -Path $bakSitePackages | Out-Null
  250. if (Test-Path $pyEmbeddedExe) {
  251. $embeddedPipOk = $false
  252. $toolchainOk = $false
  253. $getPipScript = Join-Path $pythonRoot 'get-pip.py'
  254. $requiredPipFiles = @(
  255. (Join-Path $embSitePackages 'pip\__main__.py'),
  256. (Join-Path $embSitePackages 'pip\_internal\cli\main.py'),
  257. (Join-Path $embSitePackages 'pip\_internal\operations\build\metadata.py')
  258. )
  259. $requiredToolchainFiles = @(
  260. (Join-Path $embSitePackages 'setuptools\build_meta.py'),
  261. (Join-Path $embSitePackages 'wheel\__init__.py')
  262. )
  263. $pipMissing = @($requiredPipFiles | Where-Object { -not (Test-Path $_) })
  264. if ($pipMissing.Count -eq 0) { $embeddedPipOk = $true }
  265. if (-not $embeddedPipOk) {
  266. Write-Host '[WARN] Embedded pip missing or broken; restoring from backup\site-packages (copy)...' -ForegroundColor Yellow
  267. Get-ChildItem -LiteralPath $bakSitePackages -ErrorAction SilentlyContinue | ForEach-Object {
  268. Copy-Item -LiteralPath $_.FullName -Destination $embSitePackages -Recurse -Force
  269. }
  270. $pipMissing = @($requiredPipFiles | Where-Object { -not (Test-Path $_) })
  271. if ($pipMissing.Count -eq 0) {
  272. $embeddedPipOk = $true
  273. Write-Host '[OK] pip restored from local backup (no download)' -ForegroundColor Green
  274. }
  275. }
  276. if (-not $embeddedPipOk) {
  277. Write-Host '[WARN] pip still broken after backup copy; reinstalling pip with local get-pip.py...' -ForegroundColor Yellow
  278. if (Test-Path $getPipScript) {
  279. & $pyEmbeddedExe $getPipScript --force-reinstall -i $pypiIndexUrl --trusted-host $pypiTrustedHost --no-warn-script-location
  280. $pipMissing = @($requiredPipFiles | Where-Object { -not (Test-Path $_) })
  281. if ($pipMissing.Count -eq 0) {
  282. $embeddedPipOk = $true
  283. Write-Host '[OK] pip repaired by get-pip.py' -ForegroundColor Green
  284. }
  285. } else {
  286. Write-Host ('[WARN] get-pip.py not found: ' + $getPipScript) -ForegroundColor Yellow
  287. }
  288. }
  289. if ($embeddedPipOk) {
  290. $toolchainMissing = @($requiredToolchainFiles | Where-Object { -not (Test-Path $_) })
  291. if ($toolchainMissing.Count -eq 0) { $toolchainOk = $true }
  292. }
  293. if (-not $toolchainOk) {
  294. Write-Host '[WARN] pip/setuptools/wheel incomplete; restore from backup\site-packages (copy)...' -ForegroundColor Yellow
  295. Get-ChildItem -LiteralPath $bakSitePackages -ErrorAction SilentlyContinue | ForEach-Object {
  296. Copy-Item -LiteralPath $_.FullName -Destination $embSitePackages -Recurse -Force
  297. }
  298. if ($embeddedPipOk) {
  299. $toolchainMissing = @($requiredToolchainFiles | Where-Object { -not (Test-Path $_) })
  300. if ($toolchainMissing.Count -eq 0) {
  301. $toolchainOk = $true
  302. Write-Host '[OK] toolchain restored from backup copy' -ForegroundColor Green
  303. }
  304. }
  305. }
  306. if (-not $toolchainOk -and $embeddedPipOk) {
  307. Write-Host '[WARN] backup copy not enough; trying backup whl offline install...' -ForegroundColor Yellow
  308. & $pyEmbeddedExe -m pip install --no-index --find-links $bakRoot --upgrade pip setuptools wheel --no-warn-script-location
  309. & $pyEmbeddedExe -c "import pip,setuptools,wheel; import setuptools.build_meta" 2>$null | Out-Null
  310. if ($LASTEXITCODE -eq 0) {
  311. $toolchainOk = $true
  312. Write-Host '[OK] toolchain repaired from backup wheels (offline)' -ForegroundColor Green
  313. }
  314. }
  315. if (-not $toolchainOk -and $embeddedPipOk) {
  316. Write-Host '[WARN] offline backup not enough; upgrading via Tsinghua mirror...' -ForegroundColor Yellow
  317. & $pyEmbeddedExe -m pip install -i $pypiIndexUrl --trusted-host $pypiTrustedHost --upgrade pip setuptools wheel --no-warn-script-location
  318. & $pyEmbeddedExe -c "import pip,setuptools,wheel; import setuptools.build_meta" 2>$null | Out-Null
  319. if ($LASTEXITCODE -eq 0) {
  320. $toolchainOk = $true
  321. Write-Host '[OK] toolchain repaired via online upgrade' -ForegroundColor Green
  322. }
  323. }
  324. if ($toolchainOk) {
  325. foreach ($pkgDir in @('pip', 'setuptools', 'wheel', 'pkg_resources')) {
  326. $fromPkg = Join-Path $embSitePackages $pkgDir
  327. if (Test-Path $fromPkg) {
  328. Copy-Item -LiteralPath $fromPkg -Destination (Join-Path $bakSitePackages $pkgDir) -Recurse -Force
  329. }
  330. }
  331. Get-ChildItem -LiteralPath $embSitePackages -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '^(pip|setuptools|wheel)-.+\.dist-info$' } | ForEach-Object {
  332. Copy-Item -LiteralPath $_.FullName -Destination (Join-Path $bakSitePackages $_.Name) -Recurse -Force
  333. }
  334. & $pyEmbeddedExe -m pip download pip setuptools wheel -d $bakRoot -i $pypiIndexUrl --trusted-host $pypiTrustedHost 2>$null
  335. Write-Host '[OK] Synced py\Lib\site-packages → backup\site-packages' -ForegroundColor Green
  336. } else {
  337. Write-Host '[WARN] Embedded Python toolchain still broken. Use python\x64\get-pip.py or repair manually.' -ForegroundColor Yellow
  338. }
  339. }
  340. Write-Host ''
  341. Write-Host 'Running python-enviroment-install.py (creates venv if missing, installs dependencies)...' -ForegroundColor Yellow
  342. if (-not $venvPath) { $venvPath = Join-Path $scriptRoot ('python\' + $arch + '\env') }
  343. $pythonDependenciesScript = Join-Path $scriptRoot ('python\' + $arch + '\python-enviroment-install.py')
  344. if (Test-Path $pythonDependenciesScript) {
  345. $env:PYTHON_VENV_PATH = $venvPath
  346. # 始终用本地 Python (py/python.exe) 运行,避免 venv 内脚本指向系统 Python (如 C:\programs\python)
  347. & $pythonExe $pythonDependenciesScript
  348. if ($LASTEXITCODE -ne 0) {
  349. Write-Host '[X] Python dependencies check/installation failed' -ForegroundColor Red
  350. exit 1
  351. }
  352. } else {
  353. Write-Host ('[X] Not found: ' + $pythonDependenciesScript) -ForegroundColor Red
  354. Write-Host '[WARN] Continuing without Python dependency check...' -ForegroundColor Yellow
  355. }
  356. Write-Host ''
  357. # Final verification: npm must exist for run_react to start Vite
  358. if (-not (Test-Path $npmCliPath)) {
  359. Write-Host '[X] npm-cli.js not found. Run: nodejs\node\install-npm.bat' -ForegroundColor Red
  360. exit 1
  361. }
  362. Write-Host '================================' -ForegroundColor Cyan
  363. Write-Host 'Environment check completed!' -ForegroundColor Green
  364. Write-Host 'All dependencies are ready. You can now start the project.' -ForegroundColor Green