feat: 新增 proc wait,等待进程退出

- 每秒检查一次目标进程,存在则继续等待,直到消失为止
- 关键字在进程名与命令行中匹配(忽略大小写),多个关键字为逻辑与;
  --pid 可直接指定进程 ID(逗号分隔可多个)
- 默认不超时,--timeout 限时(退出码 2 并列出残留 PID),--once 只检查一次
- 三平台实现:Linux 读 /proc(回退 ps)、macOS 用 ps、Windows 用 tasklist
- ExitError 改为优先展示具体原因,不再只显示「命令以状态码 N 退出」
- 文档:README.proc.md 新增完整小节,主 README 补充示例与速查
This commit is contained in:
2026-11-01 09:01:32 +08:00
parent 3725f8307a
commit 14817bcfe7
10 changed files with 837 additions and 3 deletions
+28 -1
View File
@@ -25,7 +25,7 @@ func TestBuiltinIDsStable(t *testing.T) {
"docker.prunebuilder", "docker.prunevolumes", "docker.clearall",
"docker.compose.up", "docker.compose.down", "docker.compose.ps", "docker.compose.logs",
"diagnose",
"disk.usage", "net.pids", "net.ports", "proc.nohup", "pkg.refreshindex",
"disk.usage", "net.pids", "net.ports", "proc.nohup", "proc.wait", "pkg.refreshindex",
}
if got := reg.IDs(); !reflect.DeepEqual(got, want) {
t.Fatalf("IDs() =\n%v\nwant\n%v", got, want)
@@ -346,3 +346,30 @@ func TestMatchIsComparable(t *testing.T) {
t.Fatal("Match should be a comparable plain struct value")
}
}
func TestProcWaitIsBuiltinEverywhere(t *testing.T) {
reg := Builtin()
cmd, ok := reg.Lookup("proc.wait")
if !ok {
t.Fatal("proc.wait not found")
}
// The command is read-only: it must not demand --yes.
if cmd.RequiresYes {
t.Error("proc.wait only observes processes and must not require --yes")
}
if cmd.Usage == "" {
t.Error("proc.wait should document its arguments in Usage")
}
seen := map[string]bool{}
for _, v := range cmd.Variants {
if v.Builtin != "proc.wait" || len(v.Argv) != 0 {
t.Errorf("variant for %s should be the proc.wait builtin, got %+v", v.Match.OS, v)
}
seen[v.Match.OS] = true
}
for _, osName := range []string{"linux", "darwin", "windows"} {
if !seen[osName] {
t.Errorf("proc.wait lacks a %s variant", osName)
}
}
}