Files
wecom_it_smart_desk/scripts/dev-frontend-start.ps1
T
Simon eee2bcc071 feat(dev): 本地开发工具集 v0.5.6-dev-tooling
包含本地 dev 链路完整跑通的工具集(不进生产):

backend:
- dev_auth.py: /api/dev/login Mock 企微 OAuth(/dev/* 路由)
- messages.py: dev 模式短路企微推送,避免 invalid corpid 噪音
- main.py: dev 模式启动时建 5 条 demo conversation,让前端有数据可测

frontend:
- PortalSelect.vue: dev 模式 enterRole 跳完整 URL(5173/5174/5175 端口),生产仍走相对路径

infrastructure:
- docker-compose.dev.yml: dev compose(包含 backend/postgres/redis)

scripts(Windows PowerShell):
- dev-frontend-install.ps1: 一次性装 4 个前端依赖
- dev-frontend-start.ps1: 后台起 4 个前端 dev server
- dev-check-schema-drift.ps1: 对比 SQLAlchemy 模型 vs Postgres schema,漂移 exit 1

docs:
- CURRENT-FOCUS.md: 项目状态看板(每次 session 维护)
2026-06-16 19:24:02 +08:00

303 lines
12 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# =============================================================================
# 企微IT智能服务台 — 前端 dev server 一键启动脚本
# =============================================================================
# 作用:一次性后台启动 4 个前端 dev server(每个独立窗口 + 独立日志)
# 用法:.\scripts\dev-frontend-start.ps1
# .\scripts\dev-frontend-start.ps1 -Frontend h5 # 只起一个
# .\scripts\dev-frontend-start.ps1 -Frontend h5,admin # 起多个
# .\scripts\dev-frontend-start.ps1 -Stop # 停掉所有
# 前置:已跑过 dev-frontend-install.ps1(每个前端有 node_modules)
# 输出:日志写到 .pnpm-dev-<name>.log;进程名 pnpm.exe(用 Get-Process pnpm 查)
# 注意:启动顺序 h5 → agent → admin → portal,每个隔 4 秒
# =============================================================================
[CmdletBinding()]
param(
[string]$Frontend = '', # 空 = 全部 4 个;逗号分隔可多选: h5,agent,admin,portal
[switch]$Stop # 切到停止模式
)
$ErrorActionPreference = 'Stop'
$ProjectRoot = $PSScriptRoot | Split-Path -Parent
Set-Location $ProjectRoot
# --------------------------------------------------------------------------
# 自动把 npm 全局 bin 加进 PATH(只对本脚本生效)
# 原因:某些用户(如 NVM 用户)系统 PATH 缺 npm global bin,pnpm 找不到
# --------------------------------------------------------------------------
$NpmGlobalBin = npm config get prefix 2>$null
if ($NpmGlobalBin -and (Test-Path $NpmGlobalBin)) {
if ($env:PATH -notlike "*$NpmGlobalBin*") {
$env:PATH = "$env:PATH;$NpmGlobalBin"
Write-Host " 已临时把 $NpmGlobalBin 加进 PATH(只对本次脚本生效)" -ForegroundColor DarkGray
}
}
# --------------------------------------------------------------------------
# 前置检查 pnpm
# --------------------------------------------------------------------------
$pnpmCmd = Get-Command pnpm -ErrorAction SilentlyContinue
if (-not $pnpmCmd) {
Write-Host " ✗ pnpm 没装或不在 PATH,请先跑 scripts\dev-frontend-install.ps1" -ForegroundColor Red
exit 1
}
# --------------------------------------------------------------------------
# 4 个前端清单(name -> port)
# 端口必须跟每个前端 vite.config.ts 的 server.port 一致
# --------------------------------------------------------------------------
$AllFrontends = @(
@{ Name = 'frontend-h5'; Port = 5174; Label = 'h5' },
@{ Name = 'frontend-agent'; Port = 5173; Label = 'agent' },
@{ Name = 'frontend-admin'; Port = 5175; Label = 'admin' },
@{ Name = 'frontend-portal'; Port = 5176; Label = 'portal' }
)
# --------------------------------------------------------------------------
# 颜色函数(跟 dev-frontend-install.ps1 一致)
# --------------------------------------------------------------------------
function Write-Step($msg) { Write-Host "`n$msg" -ForegroundColor Cyan }
function Write-OK($msg) { Write-Host "$msg" -ForegroundColor Green }
function Write-Warn($msg) { Write-Host "$msg" -ForegroundColor Yellow }
function Write-Err($msg) { Write-Host "$msg" -ForegroundColor Red }
# --------------------------------------------------------------------------
# 工具函数
# --------------------------------------------------------------------------
function Get-FrontendByLabel($label) {
foreach ($f in $AllFrontends) {
if ($f.Label -eq $label -or $f.Name -eq $label) { return $f }
}
return $null
}
function Test-PortListening($port) {
# 优先用 Get-NetTCPConnection(快);失败回退 Test-NetConnection
try {
$conn = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction Stop
return ($null -ne $conn)
} catch {
try {
$tnc = Test-NetConnection -ComputerName 'localhost' -Port $port -InformationLevel Quiet -WarningAction SilentlyContinue
return $tnc
} catch {
return $false
}
}
}
function Get-PnpmPids() {
# 拿到所有 pnpm 进程 PID(用作"停止所有")
$procs = Get-Process -Name 'pnpm' -ErrorAction SilentlyContinue
if ($null -eq $procs) { return @() }
return @($procs | Select-Object -ExpandProperty Id)
}
function Get-NodePidsByCwd($dir) {
# 拿到工作目录在 $dir 下的 node.exe 进程(Vite dev server)
$procs = Get-CimInstance Win32_Process -Filter "Name = 'node.exe'" -ErrorAction SilentlyContinue
$pids = @()
foreach ($p in $procs) {
if ($p.CommandLine -and $p.CommandLine.Contains($dir)) {
$pids += $p.ProcessId
}
}
return $pids
}
# --------------------------------------------------------------------------
# 模式:停掉所有前端
# --------------------------------------------------------------------------
if ($Stop) {
Write-Step "═══ 停掉所有前端 dev server ═══"
# 先停 node.exe(它们是 Vite dev server 真正的工作进程)
$killed = 0
foreach ($f in $AllFrontends) {
$dir = Join-Path $ProjectRoot $f.Name
$pids = Get-NodePidsByCwd $dir
foreach ($pid in $pids) {
try {
Stop-Process -Id $pid -Force -ErrorAction Stop
$killed++
} catch {}
}
}
# 再停 pnpm 父进程(它会再起 node,所以得反过来先杀子再杀父?实际先父后子都行,这里杀剩的 pnpm)
$pnpmPids = Get-PnpmPids
foreach ($pid in $pnpmPids) {
try {
Stop-Process -Id $pid -Force -ErrorAction Stop
$killed++
} catch {}
}
Write-OK "已停掉 $killed 个进程"
# 兜底:再扫一遍端口确认空
Start-Sleep -Seconds 1
foreach ($f in $AllFrontends) {
if (Test-PortListening $f.Port) {
Write-Warn "$($f.Label) 端口 $($f.Port) 还占着,可能不是 pnpm 起的"
} else {
Write-OK "$($f.Label) 端口 $($f.Port) 已释放"
}
}
Write-Host "`n下一步: .\scripts\dev-frontend-start.ps1 重启" -ForegroundColor Gray
exit 0
}
# --------------------------------------------------------------------------
# 解析 -Frontend 参数
# --------------------------------------------------------------------------
if ([string]::IsNullOrWhiteSpace($Frontend)) {
$Selected = $AllFrontends
} else {
$Selected = @()
foreach ($label in ($Frontend -split ',')) {
$label = $label.Trim()
$f = Get-FrontendByLabel $label
if ($null -eq $f) {
Write-Err "未知前端: $label(可选: h5, agent, admin, portal)"
exit 1
}
$Selected += $f
}
}
# --------------------------------------------------------------------------
# 前置检查
# --------------------------------------------------------------------------
Write-Step "═══ 企微IT智能服务台 — 前端 dev server 启动 ═══"
# 检查 pnpm
try {
$pnpmVer = pnpm --version
Write-OK "pnpm 已装: $pnpmVer"
} catch {
Write-Err "pnpm 没装,请先跑: npm install -g pnpm"
exit 1
}
# 检查 Node 版本(警告,不阻塞)
$nodeVer = node --version
Write-Host " Node 版本: $nodeVer"
if ($nodeVer -notmatch '^v(20\.|21\.|22\.)') {
Write-Warn "前端要求 Node 20.x,你是 $nodeVer。多数情况能跑,出问题再装 Node 20。"
}
# 检查每个选中的前端是否有 node_modules
foreach ($f in $Selected) {
$dir = Join-Path $ProjectRoot $f.Name
if (-not (Test-Path "$dir/package.json")) {
Write-Err "找不到 $dir/package.json,跳过 $($f.Label)"
# 用占位标记一下,在后面的循环里跳过
$f | Add-Member -NotePropertyName 'Skip' -NotePropertyValue $true -Force
continue
}
if (-not (Test-Path "$dir/node_modules")) {
Write-Warn "$($f.Label) 没有 node_modules,先跑 dev-frontend-install.ps1"
$f | Add-Member -NotePropertyName 'Skip' -NotePropertyValue $true -Force
}
}
# --------------------------------------------------------------------------
# 逐个后台启动
# --------------------------------------------------------------------------
$Started = @()
foreach ($f in $Selected) {
if ($f.Skip) { continue }
$dir = Join-Path $ProjectRoot $f.Name
$log = Join-Path $ProjectRoot ".pnpm-dev-$($f.Label).log"
$port = $f.Port
$label = $f.Label
Write-Step "─── $label (port $port) ───"
# 检查端口是否已被占(被占就跳过,不报错)
if (Test-PortListening $port) {
Write-Warn "端口 $port 已被占,$label 跳过(可能别人/上次没退干净在跑)"
Write-Host " 看进程: Get-NetTCPConnection -LocalPort $port -State Listen" -ForegroundColor Gray
Write-Host " 强制停: .\scripts\dev-frontend-start.ps1 -Stop" -ForegroundColor Gray
continue
}
# 清空旧日志(每次启动都是干净的一页)
if (Test-Path $log) { Remove-Item $log -Force }
# 后台启动 Start-Process,WindowStyle Hidden(不弹黑窗)
# 关键:必须用 cmd.exe /c 调 pnpm,因为 pnpm 是 .cmd 不是 .exe
# 直接 Start-Process 'pnpm' 会报 "%1 不是有效的 Win32 应用程序"
try {
$proc = Start-Process -FilePath 'cmd.exe' `
-ArgumentList '/c', 'pnpm', 'dev' `
-WorkingDirectory $dir `
-WindowStyle Hidden `
-RedirectStandardOutput $log `
-RedirectStandardError "$log.err" `
-PassThru `
-ErrorAction Stop
Write-OK "已起进程 PID=$($proc.Id),日志: $log"
} catch {
Write-Err "启动失败: $_"
continue
}
# 等 3-5 秒,看端口起来没
$up = $false
for ($i = 1; $i -le 8; $i++) {
Start-Sleep -Seconds 1
if (Test-PortListening $port) {
$up = $true
break
}
}
if ($up) {
Write-OK "$label 端口 $port 已监听"
$Started += $f
} else {
Write-Warn "$label 8 秒内未监听,看日志: $log"
Write-Host " 常见原因: 依赖缺/Node 版本/端口冲突/语法错" -ForegroundColor Yellow
}
}
# --------------------------------------------------------------------------
# 汇总表格
# --------------------------------------------------------------------------
Write-Step "═══ 启动汇总 ═══"
if ($Started.Count -eq 0) {
Write-Warn "没前端成功起来"
} else {
# 算每行最大宽度
$maxLabel = ($Started | ForEach-Object { $_.Label.Length } | Measure-Object -Maximum).Maximum
$maxLabel = [Math]::Max($maxLabel, 5)
Write-Host (" {0,-$maxLabel} {1,-8} {2}" -f 'NAME', 'PORT', 'URL') -ForegroundColor White
Write-Host (" {0,-$maxLabel} {1,-8} {2}" -f ($maxLabel | ForEach-Object { '-' * $_ }), '----', '---') -ForegroundColor DarkGray
foreach ($f in $Started) {
$url = "http://localhost:$($f.Port)"
Write-Host (" {0,-$maxLabel} {1,-8} {2}" -f $f.Label, $f.Port, $url) -ForegroundColor Green
}
Write-Host "`n 日志位置:" -ForegroundColor Cyan
foreach ($f in $Started) {
$log = Join-Path $ProjectRoot ".pnpm-dev-$($f.Label).log"
Write-Host " $log" -ForegroundColor Gray
}
Write-Host "`n 实时跟日志(另开窗口跑):" -ForegroundColor Cyan
Write-Host " Get-Content .pnpm-dev-h5.log -Wait" -ForegroundColor Gray
Write-Host " Get-Content .pnpm-dev-agent.log -Wait" -ForegroundColor Gray
Write-Host "`n 停掉所有前端:" -ForegroundColor Cyan
Write-Host " .\scripts\dev-frontend-start.ps1 -Stop" -ForegroundColor Gray
}
Write-Host "`n下一步:浏览器开上面的 URL 就能用了" -ForegroundColor Cyan