From cfd19f6626020fb6576457b10732cd8a3ed76f32 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Wed, 22 Apr 2026 12:23:28 +0800 Subject: [PATCH] add bridge version trace output --- internal/acp/runtime_version.go | 2 +- internal/acp/server.go | 2 +- internal/acp/web_contract_test.go | 2 +- main.go | 28 +++++++++++++ main_test.go | 68 +++++++++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 3 deletions(-) diff --git a/internal/acp/runtime_version.go b/internal/acp/runtime_version.go index 355907e..2d68dd7 100644 --- a/internal/acp/runtime_version.go +++ b/internal/acp/runtime_version.go @@ -9,7 +9,7 @@ type imageVersionInfo struct { Version string `json:"version,omitempty"` } -func parseImageVersionInfo(imageRef string) imageVersionInfo { +func ParseImageVersionInfo(imageRef string) imageVersionInfo { ref := strings.TrimSpace(imageRef) info := imageVersionInfo{ImageRef: ref} if ref == "" { diff --git a/internal/acp/server.go b/internal/acp/server.go index 327e9e1..3effe02 100644 --- a/internal/acp/server.go +++ b/internal/acp/server.go @@ -121,7 +121,7 @@ func (s *Server) Handler() http.Handler { w.Header().Set("Content-Type", "text/plain; charset=utf-8") _, _ = w.Write([]byte("xworkmate-bridge is running")) case "/api/ping": - info := parseImageVersionInfo(os.Getenv("IMAGE")) + info := ParseImageVersionInfo(os.Getenv("IMAGE")) resp := map[string]any{ "status": "ok", "image": info.ImageRef, diff --git a/internal/acp/web_contract_test.go b/internal/acp/web_contract_test.go index 1f6642e..e72b31f 100644 --- a/internal/acp/web_contract_test.go +++ b/internal/acp/web_contract_test.go @@ -194,7 +194,7 @@ func TestHTTPHandlerBareAliasPathsServeRPC(t *testing.T) { } func TestParseImageVersionInfoHandlesTaggedImageRef(t *testing.T) { - info := parseImageVersionInfo("ghcr.io/x-evor/xworkmate-bridge:main-2026-04-12") + info := ParseImageVersionInfo("ghcr.io/x-evor/xworkmate-bridge:main-2026-04-12") if info.ImageRef != "ghcr.io/x-evor/xworkmate-bridge:main-2026-04-12" { t.Fatalf("expected full image ref, got %q", info.ImageRef) diff --git a/main.go b/main.go index 883f84b..39ad3d2 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "os" @@ -11,6 +12,16 @@ import ( ) func main() { + if len(os.Args) > 1 { + switch os.Args[1] { + case "-v", "--version": + if err := printBridgeVersionInfo(); err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } + return + } + } if len(os.Args) > 1 && os.Args[1] == "serve" { if err := acp.Serve(os.Args[2:]); err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) @@ -39,3 +50,20 @@ func main() { toolbridge.Run(os.Stdin, os.Stdout) } + +func printBridgeVersionInfo() error { + info := acp.ParseImageVersionInfo(os.Getenv("IMAGE")) + payload := map[string]any{ + "status": "ok", + "image": info.ImageRef, + "tag": info.Tag, + "commit": info.Commit, + "version": info.Version, + } + encoded, err := json.Marshal(payload) + if err != nil { + return err + } + _, err = os.Stdout.Write(append(encoded, '\n')) + return err +} diff --git a/main_test.go b/main_test.go index 650e8e4..088fa02 100644 --- a/main_test.go +++ b/main_test.go @@ -2,10 +2,12 @@ package main import ( "encoding/json" + "io" "net/http" "net/http/httptest" "os" "path/filepath" + "strings" "testing" "time" ) @@ -129,3 +131,69 @@ func TestRunClaudeReviewSurfacesNonJSONStdout(t *testing.T) { t.Fatal("expected non-json stdout error") } } + +func TestPrintBridgeVersionInfoMatchesPingContract(t *testing.T) { + t.Setenv("IMAGE", "ghcr.io/x-evor/xworkmate-bridge:0123456789abcdef0123456789abcdef01234567") + + output := captureStdout(t, printBridgeVersionInfo) + + var payload map[string]any + if err := json.Unmarshal([]byte(output), &payload); err != nil { + t.Fatalf("decode version payload: %v", err) + } + if payload["status"] != "ok" { + t.Fatalf("expected status ok, got %#v", payload["status"]) + } + if payload["image"] != "ghcr.io/x-evor/xworkmate-bridge:0123456789abcdef0123456789abcdef01234567" { + t.Fatalf("unexpected image ref: %#v", payload["image"]) + } + if payload["tag"] != "0123456789abcdef0123456789abcdef01234567" { + t.Fatalf("unexpected tag: %#v", payload["tag"]) + } + if payload["commit"] != "0123456789abcdef0123456789abcdef01234567" { + t.Fatalf("unexpected commit: %#v", payload["commit"]) + } + if payload["version"] != "0123456789abcdef0123456789abcdef01234567" { + t.Fatalf("unexpected version: %#v", payload["version"]) + } +} + +func TestPrintBridgeVersionInfoWithoutImageEnv(t *testing.T) { + t.Setenv("IMAGE", "") + + output := captureStdout(t, printBridgeVersionInfo) + if !strings.Contains(output, `"status":"ok"`) { + t.Fatalf("unexpected output: %q", output) + } +} + +func captureStdout(t *testing.T, fn func() error) string { + t.Helper() + + old := os.Stdout + r, w, err := os.Pipe() + if err != nil { + t.Fatalf("pipe stdout: %v", err) + } + os.Stdout = w + defer func() { + os.Stdout = old + }() + + done := make(chan string, 1) + go func() { + var builder strings.Builder + _, _ = io.Copy(&builder, r) + done <- builder.String() + }() + + callErr := fn() + _ = w.Close() + out := <-done + _ = r.Close() + + if callErr != nil { + t.Fatalf("call failed: %v", callErr) + } + return strings.TrimSpace(out) +}