增加 Windows 脚本交互菜单

This commit is contained in:
mid
2026-08-08 18:35:01 +08:00
parent 4e3d7fed85
commit 41398fcf3d
6 changed files with 61 additions and 1 deletions
+1
View File
@@ -80,6 +80,7 @@ IDs must remain synchronized across the catalog, default navigation, icon map, a
- Supported deployments are Docker and Node.js 22.12.0+ with `web-tools.ps1` on Windows Server. A Windows single EXE is an optional future deliverable and is not currently built or supported.
- Keep production port changes synchronized across `server/index.mjs`, `web-tools.ps1`, `Dockerfile`, `docker-compose.yml`, `.env.example`, deployment tests, and both READMEs where applicable.
- The Windows update flow requires a clean `main` checkout and uses `git pull --ff-only`. Preserve external `C:\ProgramData\WebTools\data` state.
- Running `web-tools.ps1` without an action is an interactive user entry point and must keep the start/update/update-without-start/exit menu. Scheduled tasks and automation must pass an explicit `start` or `update` action so they never wait for input.
- New catalog data is loaded by the Node process at startup. Rebuilding `dist` alone is insufficient after catalog changes; restart the server and refresh already-open browser pages.
- If a single EXE is later authorized, use Node.js SEA with embedded read-only web/catalog assets and an external writable data directory. Add per-architecture builds, asset-read abstraction, Windows smoke tests, replacement/update instructions, and optional Authenticode signing. Derive EXE version metadata from `package.json`; do not create another manually maintained version source. Do not commit generated executables.
+1
View File
@@ -17,6 +17,7 @@
- 固件 Hash 在普通 HTTP 局域网访问时增加本地 SHA-256/SHA-512 回退,不再依赖 `SubtleCrypto` 必须可用。
- 生产服务在监听端口前检查 `dist/index.html`,缺少构建产物时立即给出构建命令,不再启动后按请求重复返回 500。
- 修复固件 Hash 和安全随机数模式选择框宽度过大,以及 801-1040 像素中间宽度下 CRC 参数区可能横向溢出的问题。
- 修复 Windows 脚本无参数运行时直接启动旧 `dist`、容易被误认为已完成更新的问题;现在交互启动会明确选择启动、更新并启动、仅更新或退出。
### Documentation
+2
View File
@@ -62,6 +62,8 @@ powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\web-tools.ps1 start
The script listens on `0.0.0.0:9000` by default and stores navigation settings in `C:\ProgramData\WebTools\data\navigation.json`. It accepts `-ListenAddress`, `-Port`, and `-DataDir` overrides. It does not create Windows Firewall rules.
Running `web-tools.ps1` without an action opens an interactive menu: start the existing build, update/rebuild/start, update/rebuild without starting, or exit. Explicit `start` and `update` arguments remain available for shortcuts, scheduled tasks, and automation. When launching interactively after a repository update, choose option `2`; option `1` intentionally starts the existing `dist` without rebuilding it.
To update a clean checkout on the `main` branch, stop the foreground process with `Ctrl+C`, then run:
```powershell
+11
View File
@@ -160,6 +160,17 @@ powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\web-tools.ps1 start `
-DataDir C:\ProgramData\WebTools\data
```
不带操作参数运行 `web-tools.ps1` 时会显示交互菜单:
```text
[1] Start current build (no update)
[2] Update from Git, rebuild, and start
[3] Update from Git and rebuild only
[0] Exit
```
日常双击或使用“使用 PowerShell 运行”打开脚本后,需要更新代码时选择 `2`;`1` 只启动磁盘上已有的 `dist`,不会拉取代码或重新构建。计划任务、快捷方式和自动化仍应显式传入 `start`、`update` 或 `update -NoStart`,不会进入菜单等待输入。
脚本不会自动修改 Windows 防火墙。需要从局域网访问默认端口时,可由管理员仅对专用网络配置入站规则:
```powershell
+11
View File
@@ -50,6 +50,17 @@ describe('deployment configuration', () => {
expect(script).toContain("@('ci', '--include=dev')")
})
it('offers an interactive Windows action menu without changing explicit automation actions', async () => {
const script = await readProjectFile('web-tools.ps1')
expect(script).toContain('function Select-InteractiveAction')
expect(script).toContain("[2] Update from Git, rebuild, and start")
expect(script).toContain("[3] Update from Git and rebuild only")
expect(script).toContain("$PSBoundParameters.ContainsKey('Action')")
expect(script).toContain("[ValidateSet('start', 'update')]")
expect(script).not.toContain("[string]$Action = 'start'")
})
it('provides standard entry points for a new AI session', async () => {
const [entryReadme, agentGuide, completeReadme] = await Promise.all([
readProjectFile('README.md'),
+35 -1
View File
@@ -4,7 +4,7 @@
param(
[Parameter(Position = 0)]
[ValidateSet('start', 'update')]
[string]$Action = 'start',
[string]$Action,
[ValidateNotNullOrEmpty()]
[string]$ListenAddress = '0.0.0.0',
@@ -62,6 +62,26 @@ function Invoke-NativeCommand {
}
}
function Select-InteractiveAction {
Write-Host ''
Write-Host 'Web Tools'
Write-Host ' [1] Start current build (no update)'
Write-Host ' [2] Update from Git, rebuild, and start'
Write-Host ' [3] Update from Git and rebuild only'
Write-Host ' [0] Exit'
while ($true) {
$selection = (Read-Host 'Select an action (0-3)').Trim()
switch ($selection) {
'1' { return 'start' }
'2' { return 'update' }
'3' { return 'update-no-start' }
'0' { return 'exit' }
default { Write-Warning 'Invalid selection. Enter 0, 1, 2, or 3.' }
}
}
}
function Assert-ProductionBuild {
if (-not (Test-Path -LiteralPath $serverEntry -PathType Leaf)) {
throw "Server entry was not found: $serverEntry"
@@ -166,6 +186,20 @@ function Start-WebTools {
}
}
if (-not $PSBoundParameters.ContainsKey('Action')) {
$interactiveAction = Select-InteractiveAction
if ($interactiveAction -eq 'exit') {
return
}
if ($interactiveAction -eq 'update-no-start') {
$Action = 'update'
$NoStart = $true
}
else {
$Action = $interactiveAction
}
}
if ($Action -eq 'start' -and $NoStart) {
throw "-NoStart can only be used with the 'update' action."
}