diff --git a/src/process/capture/ProcessNormalization.test.ts b/src/process/capture/ProcessNormalization.test.ts index 9631c66f3f..15f94403af 100644 --- a/src/process/capture/ProcessNormalization.test.ts +++ b/src/process/capture/ProcessNormalization.test.ts @@ -71,6 +71,30 @@ const scenario = (normalization: { }, }); +it("replaces caller-declared patterns literally, without expanding replacement patterns", () => { + const declared = parseProcessScenario({ + executable: "/usr/bin/node", + working_directory: "/workspace", + normalization: { + paths: false, + time_bucket_ms: 10, + patterns: [ + { pattern: "8080", replacement: "$&$&" }, + { pattern: "READY", replacement: "$$" }, + { pattern: "NAME", replacement: "$1" }, + ], + }, + }); + expect( + normalizeProcessText( + "listening on 8080; READY; NAME=alpha; tail NAME", + declared, + "/temporary", + rootPid, + ), + ).toBe("listening on $&$&; $$; $1=alpha; tail $1"); +}); + it("preserves compact JSON, counters, line numbers, and ambiguous endpoint spellings", () => { const input = [ '{"pid":32,"size":108,"height":480,"width":640,"score":20}', diff --git a/src/process/capture/ProcessNormalization.ts b/src/process/capture/ProcessNormalization.ts index 24209012cb..f2aa88deda 100644 --- a/src/process/capture/ProcessNormalization.ts +++ b/src/process/capture/ProcessNormalization.ts @@ -34,8 +34,11 @@ export const normalizeProcessText = ( normalized = normalizePortTokens(normalized); if (scenario.normalization.pids) normalized = normalizePidTokens(normalized, [pid]); + // Scenario replacements are literal text, so `$&`, `` $` ``, and `$1` must + // reach the captured output verbatim instead of expanding as a replacement + // pattern. split/join is literal on both sides. for (const pattern of scenario.normalization.patterns) - normalized = normalized.replaceAll(pattern.pattern, pattern.replacement); + normalized = normalized.split(pattern.pattern).join(pattern.replacement); return normalized; };