From db96659b14b41e11fedc4755c87ef60028f7f202 Mon Sep 17 00:00:00 2001 From: sudacode Date: Sun, 23 Aug 2026 04:31:52 -0700 Subject: [PATCH] test(release): treat escaped shell separators as literal text The scanner honoured backslash escapes inside double quotes but not outside them, so an escaped separator such as find's -exec ... \; still broke a line into a bare command position. Consume an unquoted backslash and the character after it before the comment and separator checks. --- src/workflow-test-helpers.test.ts | 8 ++++++++ src/workflow-test-helpers.ts | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/workflow-test-helpers.test.ts b/src/workflow-test-helpers.test.ts index b871bbca..c4190b8d 100644 --- a/src/workflow-test-helpers.test.ts +++ b/src/workflow-test-helpers.test.ts @@ -38,6 +38,14 @@ test('stepRunsCommand ignores separators inside quotes and inline comments', () assert.equal(stepRunsCommand({ run: 'gh release view "$V" 2>&1 | tee log' }, /^tee\b/), true); }); +test('stepRunsCommand treats backslash-escaped separators as literal text', () => { + assert.equal(runs(String.raw`echo foo \; bun run verify --flag "$VALUE"`), false); + assert.equal(runs(String.raw`echo foo \| bun run verify --flag "$VALUE"`), false); + assert.equal(runs(String.raw`find . -exec bun run verify --flag "$VALUE" \;`), false); + // An escape does not swallow a following real separator. + assert.equal(runs(String.raw`echo a\b; bun run verify --flag "$VALUE"`), true); +}); + test('commandPositions splits on separators and strips control-flow prefixes', () => { assert.deepEqual( commandPositions({ run: 'if gh release view "$V"; then\ngh release edit "$V"\nfi' }), diff --git a/src/workflow-test-helpers.ts b/src/workflow-test-helpers.ts index babd996b..9e74fae2 100644 --- a/src/workflow-test-helpers.ts +++ b/src/workflow-test-helpers.ts @@ -75,6 +75,14 @@ function splitCommandSeparators(line: string): string[] { continue; } + // An unquoted backslash escapes the next character, so `\;` is literal text + // rather than a separator. Checked before comments and separators. + if (char === '\\' && index + 1 < line.length) { + current += char + line[index + 1]!; + index += 1; + continue; + } + if (char === "'" || char === '"') { quote = char; current += char;