|
1 | 1 | package main |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bytes" |
4 | 5 | "context" |
5 | 6 | "encoding/json" |
6 | 7 | "errors" |
@@ -48,6 +49,20 @@ var grpcMarshalOptions = protojson.MarshalOptions{ |
48 | 49 | EmitUnpopulated: true, |
49 | 50 | } |
50 | 51 |
|
| 52 | +// marshalSessionJSON encodes nested session payloads without HTML escaping so |
| 53 | +// recorded fixture strings stay byte-for-byte close to the CLI text. |
| 54 | +func marshalSessionJSON(value any) ([]byte, error) { |
| 55 | + var buf bytes.Buffer |
| 56 | + |
| 57 | + encoder := json.NewEncoder(&buf) |
| 58 | + encoder.SetEscapeHTML(false) |
| 59 | + if err := encoder.Encode(value); err != nil { |
| 60 | + return nil, err |
| 61 | + } |
| 62 | + |
| 63 | + return bytes.TrimSuffix(buf.Bytes(), []byte("\n")), nil |
| 64 | +} |
| 65 | + |
51 | 66 | // sessionRecorder captures CLI IO and gRPC traffic for replay. |
52 | 67 | type sessionRecorder struct { |
53 | 68 | mu sync.Mutex |
@@ -80,9 +95,9 @@ type sessionMetadata struct { |
80 | 95 | Args []string `json:"args"` |
81 | 96 | Env map[string]string `json:"env"` |
82 | 97 | Version string `json:"version"` |
83 | | - ClockStartUnix int64 `json:"clock_start_unix"` |
84 | 98 | RunError *string `json:"run_error,omitempty"` |
85 | 99 | Duration *time.Duration `json:"duration,omitempty"` |
| 100 | + ClockStartUnix int64 `json:"clock_start_unix"` |
86 | 101 | } |
87 | 102 |
|
88 | 103 | // sessionEvent records a single timestamped payload entry. |
@@ -262,7 +277,7 @@ func ensureSessionBaseDir(baseDir string) error { |
262 | 277 |
|
263 | 278 | // logEvent records a new event with the elapsed timestamp. |
264 | 279 | func (r *sessionRecorder) logEvent(kind string, payload any) { |
265 | | - data, err := json.Marshal(payload) |
| 280 | + data, err := marshalSessionJSON(payload) |
266 | 281 | if err != nil { |
267 | 282 | r.mu.Lock() |
268 | 283 | if r.eventErr == nil { |
@@ -434,6 +449,7 @@ func (r *sessionRecorder) finalize(runErr error) error { |
434 | 449 | defer file.Close() |
435 | 450 |
|
436 | 451 | encoder := json.NewEncoder(file) |
| 452 | + encoder.SetEscapeHTML(false) |
437 | 453 | encoder.SetIndent("", " ") |
438 | 454 | if err := encoder.Encode(fileContent); err != nil { |
439 | 455 | finalizeErr = errors.Join(err, eventErr, hookErr) |
@@ -563,7 +579,7 @@ func (r *sessionRecorder) logGRPCMessage(method, event string, msg any, |
563 | 579 | payload.Payload = data |
564 | 580 | } |
565 | 581 | } else { |
566 | | - data, err := json.Marshal(msg) |
| 582 | + data, err := marshalSessionJSON(msg) |
567 | 583 | if err == nil { |
568 | 584 | payload.Payload = data |
569 | 585 | } |
|
0 commit comments