Files
nodeagent/.github/workflows/windows-e2e.yml
T
jkooe cd278ffda8 fix(ci): 5.1 执行的内联脚本一律 ASCII-only(同一个 BOM 坑,臣在 CI 步骤里原样复现)
第二轮 CI 暴露:『install.ps1 安装链路』失败,报 string is missing the terminator。
根因不是被测脚本(BOM 守卫同轮通过),而是**步骤自身的 run: 块里有中文** ——
GitHub 把 run 块写成**无 BOM** 的临时 .ps1,5.1 按 GBK 读 → 中文乱码 → 引号边界崩。
这与 2026-10-04 install.ps1 无 BOM 导致的事故是同一机制,臣在 CI 里原样踩了一遍。

修正:
- 所有 shell: powershell(= 5.1)/ shell: cmd 步骤的内联脚本改为 ASCII-only
  (中文只留在步骤名,那是元数据不参与执行)
- BOM 守卫的 throw 消息也一并英文化(它上一轮是侥幸没过炸)
- 清理步骤加 continue-on-error,避免前置失败连带掩盖真实结论

保留:shell: pwsh 步骤的英文/中文不受影响(7+ 认 UTF-8 无 BOM)。
2026-10-07 02:14:27 +08:00

176 lines
7.5 KiB
YAML
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.
name: Windows E2E
on:
push:
branches: [main]
paths:
- 'apps/**'
- 'packages/**'
- 'tests/**'
- 'scripts/**'
- 'package.json'
- 'pnpm-lock.yaml'
- '.github/workflows/windows-e2e.yml'
pull_request:
branches: [main]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
windows-e2e:
name: Windows 真机验证
runs-on: windows-latest
timeout-minutes: 30
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 安装 pnpm
uses: pnpm/action-setup@v4
with:
version: 12
- name: 安装 Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: 安装依赖
run: pnpm install --frozen-lockfile
- name: 构建
run: pnpm -r build
- name: 类型检查
run: pnpm typecheck
- name: 单元测试 — 协议纯函数 / 清单守护 / 脚本生成
# 来自 ci/workflow-unit-tests 分支(4d73d6e)的实质贡献;该分支基于老 main,
# 直接 merge 会回退上万行,故只取其提交内容手工并入。
run: node --test "tests/unit/**/*.test.mjs"
- name: E2E — 协议与跨平台能力
run: node tests/e2e/run.mjs
- name: E2E — Windows 专属能力(服务 / 软件 / winget)
run: node tests/e2e/windows.mjs
# ⚠️ 这一步刻意用 **powershell(Windows PowerShell 5.1)** 而非 pwsh。
# 5.1 读无 BOM 的 .ps1 会按 ANSI/GBK 解析,中文注释会破坏字符串边界 →
# 整份脚本 ParseError(真机 2026-10-04 踩过:install.ps1 报「","后面缺少表达式」)。
# 之前的冒烟全用 pwsh(7+ 认无 BOM UTF-8),所以 CI 绿、真机炸。
# 这里只做「解析 + 首行 help」检查:5.1 能否读懂,就是真机的核心约束。
# ⚠️ 必须用 shell: powershell(runner 上的 powershell 即 Windows PowerShell 5.1)。
# 不要用 shell: cmd 再套 powershell -Command "..." —— 多行带引号经 cmd 转义会全乱
# (实测:Missing closing '}' / '$errs' is not recognized,是步骤自身坏,不是脚本坏)。
- name: 冒烟 — 脚本可被 Windows PowerShell 5.1 解析(BOM 守卫)
shell: powershell
run: |
$ErrorActionPreference = 'Stop'
foreach ($f in @('scripts\install.ps1', 'scripts\uninstall.ps1', 'scripts\rescue-network.ps1')) {
$errs = $null
[void][System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path $f).Path, [ref]$null, [ref]$errs)
if ($errs -and $errs.Count -gt 0) { throw ('5.1 parse FAILED: ' + $f + ' -> ' + $errs[0].Message) }
Write-Host ('OK 5.1 parse: ' + $f)
}
- name: 冒烟 — install.ps1 安装链路
shell: powershell
run: |
$ErrorActionPreference = "Stop"
./scripts/install.ps1 -Port 18769 -NodeId ci_win
$task = Get-ScheduledTask -TaskName "nodeagent" -ErrorAction Stop
Write-Host "Scheduled task state: $($task.State)"
$cfg = Get-Content "$env:USERPROFILE\.nodeagent\agent.json" -Raw | ConvertFrom-Json
if ($cfg.port -ne 18769) { throw "port mismatch: $($cfg.port)" }
if (-not $cfg.key) { throw "PSK missing in agent.json" }
if (-not $cfg.tls) { throw "TLS should be on by default" }
$rule = Get-NetFirewallRule -DisplayName "nodeagent (TCP 18769)" -ErrorAction Stop
if (-not $rule) { throw "Firewall rule" }
Write-Host "OK scheduled task / config / firewallall in place"
# 一键入口冒烟:真跑 install.cmd / control.cmd。
# 目的不是「再装一遍」,而是证明双击路径可用 —— 这些脚本是用户唯一的
# 上手入口,语法或编码一旦坏了,只有真机 cmd.exe 才测得出来。
# 端口用 18770 避开前一步的 18770 之外的安装,便于分辨是哪一步装的。
- name: 冒烟 — install.cmd 双击链路(含自提权与 PSK 读取)
shell: cmd
run: call scripts\install.cmd 18770
env:
CI: "true"
- name: 冒烟 — control.cmd 状态查询
shell: cmd
run: scripts\control.cmd 18770 < nul
env:
CI: "true"
- name: 冒烟 — 产物包结构与编码
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
pnpm pack:win
$zip = "release/nodeagent-win-x64.zip"
if (-not (Test-Path $zip)) { throw "未产出 $zip" }
Add-Type -AssemblyName System.IO.Compression.FileSystem
$z = [IO.Compression.ZipFile]::OpenRead((Resolve-Path $zip))
try {
$names = $z.Entries.Name
foreach ($need in @('install.cmd','control.cmd','install.ps1','control.ps1','PSK.txt','README.txt','agent.mjs','node.exe')) {
if ($names -notcontains $need) { throw "包内缺少 $need" }
}
foreach ($e in $z.Entries) {
$s = $e.Open(); $buf = New-Object byte[] 3
[void]$s.Read($buf, 0, 3); $s.Close()
$hasBom = ($buf[0] -eq 0xEF -and $buf[1] -eq 0xBB -and $buf[2] -eq 0xBF)
if ($e.Name -like '*.ps1' -and -not $hasBom) { throw "$($e.Name) 在包内丢了 BOM" }
if ($e.Name -like '*.cmd' -and $hasBom) { throw "$($e.Name) 不该有 BOM" }
}
Write-Host "OK — 包结构完整,.ps1 带 BOM / .cmd 无 BOM"
} finally { $z.Dispose() }
- name: 冒烟 — 被控端监听
shell: pwsh
run: |
Start-Process -FilePath "node" -ArgumentList "apps/agent/dist/index.js" -NoNewWindow
Start-Sleep -Seconds 4
$ok = (Test-NetConnection 127.0.0.1 -Port 18769 -WarningAction SilentlyContinue).TcpTestSucceeded
if (-not $ok) { throw "agent 未监听 18769" }
Write-Host "agent 已在 18769 监听"
- name: 冒烟 — CLI 连入(wss + 自签证书 + HMAC 鉴权)
shell: pwsh
run: |
$cfg = Get-Content "$env:USERPROFILE\.nodeagent\agent.json" -Raw | ConvertFrom-Json
node apps/cli/dist/index.js connect 127.0.0.1 --port 18769 --key $cfg.key --insecure
Write-Host "--- status ---"
node apps/cli/dist/index.js status
Write-Host "--- services ---"
node apps/cli/dist/index.js services --limit 5
- name: 冒烟 — MCP 工具列表
shell: pwsh
run: |
$cfg = Get-Content "$env:USERPROFILE\.nodeagent\agent.json" -Raw | ConvertFrom-Json
@{ client_id="ci"; host="127.0.0.1"; port=18769; tls=$true; insecure=$true; key=$cfg.key } |
ConvertTo-Json | Set-Content "$env:USERPROFILE\.nodeagent\config.json" -Encoding UTF8
$req = @(
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"ci","version":"1.0"}}}',
'{"jsonrpc":"2.0","method":"notifications/initialized"}',
'{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
) -join "`n"
($req + "`n") | node apps/mcp/dist/index.js 2>$null | Select-String "na_status"
- name: 清理
if: always()
shell: powershell
run: |
Get-Process node -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
./scripts/uninstall.ps1 -Port 18769 -Purge