From df083cab8eeeeddea21778076aa9ac650b4c1df9 Mon Sep 17 00:00:00 2001 From: Robert Yates Date: Mon, 9 Feb 2026 17:00:20 -0500 Subject: [PATCH 1/4] WIP: save current work --- debug-nested-bash.mjs | 21 + excelData.json | 955 ++++++++++++++++++++++++++ src/commands/bash/bash.test.ts | 49 ++ src/interpreter/pipeline-execution.ts | 9 + test-actual-pipeline.mjs | 28 + test-arithmetic-bug.mjs | 25 + test-bash-comparison.mjs | 48 ++ test-bash-excel-command.mjs | 84 +++ test-bash-excel-debug.mjs | 36 + test-bracket-bug.mjs | 24 + test-bracket-data.mjs | 29 + test-command-sub-cut.mjs | 22 + test-compare-methods.mjs | 65 ++ test-cut-empty-stdin.mjs | 24 + test-echo-variable.mjs | 25 + test-eliminate-jq.mjs | 33 + test-excel-minimal.mjs | 24 + test-final-minimal.mjs | 22 + test-grep-diff.mjs | 31 + test-grep-head-cut.mjs | 28 + test-head-cut-empty.mjs | 24 + test-jq-grep-head.mjs | 23 + test-jq-simple.mjs | 28 + test-jq-tail-grep.mjs | 28 + test-minimal-error.mjs | 35 + test-narrow-error.mjs | 23 + test-narrow-pipeline.mjs | 28 + test-nested-substitution.mjs | 26 + test-pipeline-debug.mjs | 39 ++ test-pipeline-steps.mjs | 37 + test-script.sh | 11 + test-simple-diff.mjs | 35 + test-simplest-bug.mjs | 39 ++ test-simplest.mjs | 25 + test-stdin-passthrough.mjs | 34 + test-stdin-simple.mjs | 36 + test-stdin.sh | 1 + test-stopline-value.mjs | 45 ++ test-work-backwards.mjs | 43 ++ 39 files changed, 2142 insertions(+) create mode 100644 debug-nested-bash.mjs create mode 100644 excelData.json create mode 100644 test-actual-pipeline.mjs create mode 100644 test-arithmetic-bug.mjs create mode 100644 test-bash-comparison.mjs create mode 100644 test-bash-excel-command.mjs create mode 100644 test-bash-excel-debug.mjs create mode 100644 test-bracket-bug.mjs create mode 100644 test-bracket-data.mjs create mode 100644 test-command-sub-cut.mjs create mode 100644 test-compare-methods.mjs create mode 100644 test-cut-empty-stdin.mjs create mode 100644 test-echo-variable.mjs create mode 100644 test-eliminate-jq.mjs create mode 100644 test-excel-minimal.mjs create mode 100644 test-final-minimal.mjs create mode 100644 test-grep-diff.mjs create mode 100644 test-grep-head-cut.mjs create mode 100644 test-head-cut-empty.mjs create mode 100644 test-jq-grep-head.mjs create mode 100644 test-jq-simple.mjs create mode 100644 test-jq-tail-grep.mjs create mode 100644 test-minimal-error.mjs create mode 100644 test-narrow-error.mjs create mode 100644 test-narrow-pipeline.mjs create mode 100644 test-nested-substitution.mjs create mode 100644 test-pipeline-debug.mjs create mode 100644 test-pipeline-steps.mjs create mode 100644 test-script.sh create mode 100644 test-simple-diff.mjs create mode 100644 test-simplest-bug.mjs create mode 100644 test-simplest.mjs create mode 100644 test-stdin-passthrough.mjs create mode 100644 test-stdin-simple.mjs create mode 100644 test-stdin.sh create mode 100644 test-stopline-value.mjs create mode 100644 test-work-backwards.mjs diff --git a/debug-nested-bash.mjs b/debug-nested-bash.mjs new file mode 100644 index 00000000..9a242944 --- /dev/null +++ b/debug-nested-bash.mjs @@ -0,0 +1,21 @@ +import { Bash, InMemoryFs } from './dist/index.js'; + +async function test() { + const env = new Bash({ fs: new InMemoryFs() }); + + console.log('=== Test 1: Direct test (like in test-final-minimal.mjs) ==='); + const result1 = await env.exec('echo "hello world" | bash -c \'DATA=$(cat); VAR=$(echo "$DATA"); echo "VAR=[$VAR]"\''); + console.log('stdout:', JSON.stringify(result1.stdout)); + console.log('stderr:', JSON.stringify(result1.stderr)); + console.log('exitCode:', result1.exitCode); + + console.log('\n=== Test 2: With escaped $ (like in test-final-minimal.mjs) ==='); + const result2 = await env.exec('echo "hello world" | bash -c \'DATA=\\$(cat); VAR=\\$(echo "\\$DATA"); echo "VAR=[\\$VAR]"\''); + console.log('stdout:', JSON.stringify(result2.stdout)); + console.log('stderr:', JSON.stringify(result2.stderr)); + console.log('exitCode:', result2.exitCode); +} + +test(); + +// Made with Bob diff --git a/excelData.json b/excelData.json new file mode 100644 index 00000000..680d94c3 --- /dev/null +++ b/excelData.json @@ -0,0 +1,955 @@ +[ + { + "sheets": [ + { + "name": "Budget Overview", + "rows": [ + [ + "INCOME", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + [ + "Source", + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + "TOTAL" + ], + [ + "Salary", + 5200, + 5200, + 5200, + 5400, + 5400, + 5400, + 5400, + 5400, + 5600, + 5600, + 5600, + 5600, + 65000 + ], + [ + "Bonus", + 0, + 0, + 2500, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 3000, + 5500 + ], + [ + "Freelance", + 800, + 1200, + 650, + 900, + 1100, + 750, + 1300, + 950, + 1150, + 800, + 1050, + 1400, + 12050 + ], + [ + "Investments", + 350, + 380, + 420, + 390, + 410, + 450, + 480, + 520, + 490, + 510, + 530, + 580, + 5510 + ], + [ + "Dividends", + 0, + 0, + 125, + 0, + 0, + 140, + 0, + 0, + 155, + 0, + 0, + 170, + 590 + ], + [ + "Rental Income", + 1500, + 1500, + 1500, + 1500, + 1550, + 1550, + 1550, + 1550, + 1600, + 1600, + 1600, + 1600, + 18600 + ], + [ + "Side Business", + 200, + 350, + 275, + 400, + 450, + 380, + 520, + 475, + 390, + 440, + 510, + 620, + 5010 + ], + [ + "Cash Back/Rewards", + 45, + 62, + 38, + 55, + 71, + 48, + 59, + 67, + 52, + 63, + 58, + 85, + 703 + ], + [ + "Tax Refund", + 0, + 0, + 0, + 1850, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1850 + ], + [ + "Other", + 150, + 75, + 200, + 100, + 125, + 180, + 90, + 140, + 160, + 110, + 130, + 250, + 1710 + ], + [ + "TOTAL INCOME", + 8245, + 8767, + 10908, + 10595, + 9106, + 8898, + 9399, + 9102, + 9597, + 9123, + 9478, + 13305, + 116523 + ], + [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + [ + "EXPENSES", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + [ + "Source", + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + "TOTAL" + ], + [ + "Rent/Mortgage", + 1800, + 1800, + 1800, + 1800, + 1800, + 1800, + 1850, + 1850, + 1850, + 1850, + 1850, + 1850, + 21900 + ], + [ + "Utilities", + 220, + 195, + 180, + 165, + 145, + 160, + 185, + 210, + 175, + 155, + 190, + 230, + 2210 + ], + [ + "Internet/Phone", + 125, + 125, + 125, + 125, + 125, + 130, + 130, + 130, + 130, + 130, + 130, + 130, + 1535 + ], + [ + "Groceries", + 650, + 720, + 680, + 710, + 690, + 740, + 720, + 750, + 695, + 730, + 760, + 820, + 8665 + ], + [ + "Dining Out", + 280, + 320, + 350, + 290, + 380, + 420, + 450, + 390, + 340, + 310, + 370, + 480, + 4380 + ], + [ + "Transportation", + 350, + 400, + 320, + 380, + 360, + 340, + 390, + 420, + 370, + 350, + 380, + 410, + 4470 + ], + [ + "Gas", + 180, + 165, + 175, + 190, + 200, + 210, + 220, + 195, + 185, + 170, + 175, + 190, + 2255 + ], + [ + "Insurance", + 450, + 450, + 450, + 450, + 450, + 450, + 450, + 450, + 470, + 470, + 470, + 470, + 5480 + ], + [ + "Healthcare", + 150, + 85, + 200, + 120, + 95, + 180, + 110, + 250, + 140, + 90, + 165, + 130, + 1715 + ], + [ + "Entertainment", + 200, + 250, + 180, + 220, + 280, + 350, + 320, + 290, + 240, + 210, + 260, + 380, + 3180 + ], + [ + "Subscriptions", + 85, + 85, + 85, + 95, + 95, + 95, + 95, + 95, + 95, + 105, + 105, + 105, + 1140 + ], + [ + "Shopping", + 150, + 280, + 120, + 200, + 175, + 320, + 250, + 180, + 220, + 160, + 350, + 450, + 2855 + ], + [ + "Personal Care", + 60, + 45, + 80, + 55, + 70, + 65, + 75, + 50, + 60, + 55, + 70, + 85, + 770 + ], + [ + "Gym/Fitness", + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 600 + ], + [ + "Education", + 0, + 0, + 150, + 0, + 0, + 200, + 0, + 0, + 175, + 0, + 0, + 0, + 525 + ], + [ + "Gifts", + 50, + 120, + 75, + 80, + 200, + 90, + 60, + 45, + 110, + 85, + 150, + 350, + 1415 + ], + [ + "Travel", + 0, + 0, + 0, + 800, + 0, + 1200, + 0, + 0, + 500, + 0, + 0, + 0, + 2500 + ], + [ + "Home Maintenance", + 75, + 120, + 200, + 90, + 150, + 80, + 250, + 110, + 95, + 180, + 130, + 160, + 1640 + ], + [ + "Pet Expenses", + 85, + 90, + 85, + 150, + 90, + 85, + 95, + 120, + 85, + 90, + 95, + 110, + 1180 + ], + [ + "Miscellaneous", + 100, + 75, + 130, + 85, + 110, + 95, + 120, + 80, + 105, + 90, + 115, + 140, + 1245 + ], + [ + "TOTAL EXPENSES", + 5060, + 5375, + 5435, + 6055, + 5465, + 7060, + 5820, + 5665, + 6090, + 5280, + 5815, + 6540, + 69660 + ], + [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + [ + "MONTHLY SUMMARY", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + [ + "Source", + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + "TOTAL" + ], + [ + "Net Savings", + 3185, + 3392, + 5473, + 4540, + 3641, + 1838, + 3579, + 3437, + 3507, + 3843, + 3663, + 6765, + 46863 + ], + [ + "Savings Rate", + 0.3862947240751971, + 0.3869054408577621, + 0.5017418408507518, + 0.4285040113260972, + 0.3998462552163409, + 0.2065632726455383, + 0.3807851899138206, + 0.3776093166337069, + 0.36542669584245074, + 0.4212430121670503, + 0.3864739396497151, + 0.5084554678692221, + 0.40217811075924925 + ], + [ + "Cumulative Savings", + 3185, + 6577, + 12050, + 16590, + 20231, + 22069, + 25648, + 29085, + 32592, + 36435, + 40098, + 46863, + 46863 + ], + [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + [ + "ANNUAL STATISTICS", + "", + "", + "", + "", + "TOP EXPENSE CATEGORIES", + "", + "", + "", + "", + "", + "", + "", + "" + ], + [ + "Total Annual Income", + 116523, + "", + "", + "", + "Rent/Mortgage", + 21900, + "", + "", + "", + "", + "", + "", + "" + ], + [ + "Total Annual Expenses", + 69660, + "", + "", + "", + "Groceries", + 8665, + "", + "", + "", + "", + "", + "", + "" + ], + [ + "Total Annual Savings", + 46863, + "", + "", + "", + "Insurance", + 5480, + "", + "", + "", + "", + "", + "", + "" + ], + [ + "Average Monthly Income", + 9710.25, + "", + "", + "", + "Dining Out", + 4380, + "", + "", + "", + "", + "", + "", + "" + ], + [ + "Average Monthly Expenses", + 5805, + "", + "", + "", + "Transportation", + 4470, + "", + "", + "", + "", + "", + "", + "" + ], + [ + "Average Monthly Savings", + 3905.25, + "", + "", + "", + "Entertainment", + 3180, + "", + "", + "", + "", + "", + "", + "" + ], + [ + "Highest Income Month", + 13305, + "", + "", + "", + "Shopping", + 2855, + "", + "", + "", + "", + "", + "", + "" + ], + [ + "Lowest Income Month", + 8245, + "", + "", + "", + "Travel", + 2500, + "", + "", + "", + "", + "", + "", + "" + ], + [ + "Highest Expense Month", + 7060, + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + [ + "Lowest Expense Month", + 5060, + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + [ + "Annual Savings Rate", + 0.40217811075924925, + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + ] + } + ] + } +] \ No newline at end of file diff --git a/src/commands/bash/bash.test.ts b/src/commands/bash/bash.test.ts index 51afb09a..86c75250 100644 --- a/src/commands/bash/bash.test.ts +++ b/src/commands/bash/bash.test.ts @@ -200,4 +200,53 @@ cat /tmp/test.txt`, expect(result.exitCode).toBe(0); }); }); + + describe("stdin piping to nested bash -c", () => { + it("should handle stdin when piping to bash -c with command substitution", async () => { + const env = new Bash(); + // This is the key test case: piping stdin to a nested bash -c command + // The stdin should be available to commands inside the bash -c script + const result = await env.exec( + 'echo "hello world" | bash -c \'DATA=$(cat); echo "$DATA"\'', + ); + expect(result.stdout).toBe("hello world\n"); + expect(result.exitCode).toBe(0); + }); + + it("should handle stdin with multiple commands in bash -c", async () => { + const env = new Bash(); + const result = await env.exec( + 'echo "test data" | bash -c \'read LINE; echo "Got: $LINE"\'', + ); + expect(result.stdout).toBe("Got: test data\n"); + expect(result.exitCode).toBe(0); + }); + + it("should handle stdin piping to sh -c", async () => { + const env = new Bash(); + const result = await env.exec("echo \"from stdin\" | sh -c 'cat'"); + expect(result.stdout).toBe("from stdin\n"); + expect(result.exitCode).toBe(0); + }); + + it("should handle complex piping with bash -c", async () => { + const env = new Bash(); + const result = await env.exec( + "echo -e \"line1\\nline2\\nline3\" | bash -c 'grep line2'", + ); + expect(result.stdout).toBe("line2\n"); + expect(result.exitCode).toBe(0); + }); + + it("should handle command substitution with grep and head in piped bash -c", async () => { + const env = new Bash(); + // This test demonstrates a bug where grep with no matches followed by head + // incorrectly passes through the original stdin instead of empty output + const result = await env.exec( + 'echo "test" | bash -c \'RESULT=$(cat | grep "nomatch" | head -1); echo "RESULT=[$RESULT]"\'', + ); + expect(result.stdout).toBe("RESULT=[]\n"); + expect(result.exitCode).toBe(0); + }); + }); }); diff --git a/src/interpreter/pipeline-execution.ts b/src/interpreter/pipeline-execution.ts index aa588136..3d774278 100644 --- a/src/interpreter/pipeline-execution.ts +++ b/src/interpreter/pipeline-execution.ts @@ -43,12 +43,21 @@ export async function executePipeline( for (let i = 0; i < node.commands.length; i++) { const command = node.commands[i]; const isLast = i === node.commands.length - 1; + const isFirst = i === 0; // In a multi-command pipeline, each command runs in a subshell context // where $_ starts empty (subshells don't inherit $_ from parent in same way) if (isMultiCommandPipeline) { // Clear $_ for each pipeline command - they each get fresh subshell context ctx.state.lastArg = ""; + + // After the first command, clear groupStdin so subsequent commands + // only see stdin from the pipeline (even if empty), not the original groupStdin + // This prevents commands like head from incorrectly falling back to groupStdin + // when they receive empty output from a previous command (e.g., grep with no matches) + if (!isFirst) { + ctx.state.groupStdin = undefined; + } } // Determine if this command runs in a subshell context diff --git a/test-actual-pipeline.mjs b/test-actual-pipeline.mjs new file mode 100644 index 00000000..3836bb14 --- /dev/null +++ b/test-actual-pipeline.mjs @@ -0,0 +1,28 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import fs from 'fs'; +import { execSync } from 'child_process'; + +async function test() { + const jsonData = fs.readFileSync('excelData.json', 'utf-8'); + + // Test the exact failing pipeline with real bash + console.log('=== REAL BASH ==='); + const realCmd = `cat excelData.json | bash -c 'DATA=$(cat); VAR=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1); echo "VAR=[$VAR]"'`; + const realResult = execSync(realCmd, { encoding: 'utf-8' }); + console.log(realResult); + + // Test with just-bash using the SAME command + console.log('=== JUST-BASH ==='); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs }); + await memFs.writeFile('excelData.json', jsonData); + + // Use the exact same realCmd + const result = await bash.exec(realCmd); + console.log(result.stdout); + if (result.stderr) console.log('Stderr:', result.stderr); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-arithmetic-bug.mjs b/test-arithmetic-bug.mjs new file mode 100644 index 00000000..888e0e5f --- /dev/null +++ b/test-arithmetic-bug.mjs @@ -0,0 +1,25 @@ +import { Bash, InMemoryFs } from 'just-bash'; + +async function test() { + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); + + console.log('=== Test: STOP_LINE="[" with arithmetic via bash -c ==='); + const result = await bash.exec(`bash -c 'STOP_LINE="[" +if [ -n "$STOP_LINE" ]; then + COUNT=$((STOP_LINE - 1)) + echo "COUNT=$COUNT" +else + echo "empty" +fi'`); + + console.log('Exit:', result.exitCode); + console.log('Stdout:', result.stdout); + console.log('Stderr:', result.stderr); + + console.log('\n=== Compare with real bash ==='); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-bash-comparison.mjs b/test-bash-comparison.mjs new file mode 100644 index 00000000..4592dd0b --- /dev/null +++ b/test-bash-comparison.mjs @@ -0,0 +1,48 @@ +import { Bash, InMemoryFs } from './dist/index.js'; +import { execSync } from 'child_process'; + +async function test() { + console.log('=== TEST 1: Without bash -c ==='); + const command1 = `RESULT=$(echo "test" | grep "nomatch" | head -1); echo "RESULT=[$RESULT]"`; + + // Real bash + console.log('REAL BASH:'); + try { + const realResult = execSync(command1, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }); + console.log('stdout:', realResult); + } catch (e) { + console.log('stdout:', e.stdout); + console.log('exit code:', e.status); + } + + // Just-bash + console.log('\nJUST-BASH:'); + const bash1 = new Bash({ fs: new InMemoryFs() }); + const result1 = await bash1.exec(command1); + console.log('stdout:', result1.stdout); + console.log('exit code:', result1.exitCode); + + console.log('\n=== TEST 2: With bash -c ==='); + const command2 = `echo "test" | bash -c 'RESULT=$(cat | grep "nomatch" | head -1); echo "RESULT=[$RESULT]"'`; + + // Real bash + console.log('REAL BASH:'); + try { + const realResult = execSync(command2, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }); + console.log('stdout:', realResult); + } catch (e) { + console.log('stdout:', e.stdout); + console.log('exit code:', e.status); + } + + // Just-bash + console.log('\nJUST-BASH:'); + const bash2 = new Bash({ fs: new InMemoryFs() }); + const result2 = await bash2.exec(command2); + console.log('stdout:', result2.stdout); + console.log('exit code:', result2.exitCode); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-bash-excel-command.mjs b/test-bash-excel-command.mjs new file mode 100644 index 00000000..42ef21c0 --- /dev/null +++ b/test-bash-excel-command.mjs @@ -0,0 +1,84 @@ +/** + * Test script to execute the bash command using just-bash + * This demonstrates piping the excelData.json through the bash command + */ + +import { Bash, InMemoryFs } from 'just-bash'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +async function testBashCommand() { + try { + // Read the JSON file + const jsonPath = path.join(__dirname, 'excelData.json'); + const jsonData = fs.readFileSync(jsonPath, 'utf-8'); + + console.log('Input JSON size:', jsonData.length, 'bytes'); + + // The bash command from the task + const command = `DATA=$(cat) +HEADER_IDX=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | grep -n '"Source"' | tail -1 | cut -d: -f1 | awk '{print $1 - 1}') +START_IDX=$((HEADER_IDX + 1)) +STOP_LINE=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) +if [ -n "$STOP_LINE" ]; then + COUNT=$((STOP_LINE - 1)) +else + TOTAL=$(echo "$DATA" | jq '.[0].sheets[0].rows | length') + COUNT=$((TOTAL - START_IDX)) +fi +echo "{\\"h\\": $HEADER_IDX, \\"s\\": $START_IDX, \\"n\\": $COUNT}"`; + + // Create in-memory filesystem + const memFs = new InMemoryFs(); + + // Create Bash instance + const bash = new Bash({ + fs: memFs, + cwd: '/', + env: { + HOME: '/', + TMPDIR: '/tmp', + }, + }); + + // Write JSON data to a temp file + const tempFile = '/tmp/stdin_data'; + await memFs.writeFile(tempFile, jsonData); + + // Execute the command with cat piping the data + const fullCommand = `cat ${tempFile} | bash -c '${command.replace(/'/g, "'\\''")}'`; + + console.log('\nExecuting command...\n'); + + const result = await bash.exec(fullCommand); + + console.log('Exit code:', result.exitCode); + console.log('\nStdout:'); + console.log(result.stdout); + + if (result.stderr) { + console.log('\nStderr:'); + console.log(result.stderr); + } + + // Try to parse the output as JSON + if (result.stdout.trim()) { + try { + const parsed = JSON.parse(result.stdout.trim()); + console.log('\nParsed output:'); + console.log(JSON.stringify(parsed, null, 2)); + } catch (e) { + console.log('\nCould not parse output as JSON'); + } + } + } catch (error) { + console.error('Error:', error.message); + console.error(error.stack); + } +} + +testBashCommand(); \ No newline at end of file diff --git a/test-bash-excel-debug.mjs b/test-bash-excel-debug.mjs new file mode 100644 index 00000000..d8d9da53 --- /dev/null +++ b/test-bash-excel-debug.mjs @@ -0,0 +1,36 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import fs from 'fs'; + +async function test() { + const jsonData = fs.readFileSync('excelData.json', 'utf-8'); + + // Simplified command with debugging + const command = `DATA=$(cat) +echo "DEBUG: DATA length = \${#DATA}" >&2 +HEADER_IDX=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | grep -n '"Source"' | tail -1 | cut -d: -f1 | awk '{print $1 - 1}') +echo "DEBUG: HEADER_IDX = [$HEADER_IDX]" >&2 +START_IDX=$((HEADER_IDX + 1)) +echo "DEBUG: START_IDX = [$START_IDX]" >&2 +STOP_LINE=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) +echo "DEBUG: STOP_LINE = [$STOP_LINE]" >&2 +echo "Final: h=$HEADER_IDX, s=$START_IDX, stop=$STOP_LINE"`; + + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/', TMPDIR: '/tmp' } }); + await memFs.writeFile('/tmp/data.json', jsonData); + + const fullCommand = `cat /tmp/data.json | bash -c '${command.replace(/'/g, "'\\''")}'`; + + console.log('Executing...\n'); + const result = await bash.exec(fullCommand); + + console.log('STDOUT:'); + console.log(result.stdout); + console.log('\nSTDERR:'); + console.log(result.stderr); + console.log('\nExit code:', result.exitCode); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-bracket-bug.mjs b/test-bracket-bug.mjs new file mode 100644 index 00000000..02d23446 --- /dev/null +++ b/test-bracket-bug.mjs @@ -0,0 +1,24 @@ +import { Bash, InMemoryFs } from 'just-bash'; + +async function test() { + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); + + console.log('=== Minimal reproduction: STOP_LINE="[" in bash -c ==='); + const result = await bash.exec(`bash -c 'STOP_LINE="[" +if [ -n "$STOP_LINE" ]; then + echo "not empty" +else + echo "empty" +fi'`); + + console.log('Exit:', result.exitCode); + console.log('Stdout:', result.stdout); + console.log('Stderr:', result.stderr); + + console.log('\n=== Compare with real bash ==='); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-bracket-data.mjs b/test-bracket-data.mjs new file mode 100644 index 00000000..cdb7a259 --- /dev/null +++ b/test-bracket-data.mjs @@ -0,0 +1,29 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import { execSync } from 'child_process'; +import fs from 'fs'; + +async function test() { + // Create JSON that starts with [ like the Excel data + const jsonData = '[{"test":"value"}]'; + fs.writeFileSync('bracket.json', jsonData); + + // Test with real bash + console.log('=== REAL BASH ==='); + const realResult = execSync(`bash -c 'DATA=$(cat bracket.json); VAR=$(echo "$DATA" | jq -c ".[]" | tail -n +2 | grep "nomatch" | head -1); echo "VAR=[$VAR]"'`, { encoding: 'utf-8' }); + console.log(realResult); + + // Test with just-bash + console.log('=== JUST-BASH ==='); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); + await memFs.writeFile('/bracket.json', jsonData); + + const result = await bash.exec(`bash -c 'DATA=$(cat /bracket.json); VAR=$(echo "$DATA" | jq -c ".[]" | tail -n +2 | grep "nomatch" | head -1); echo "VAR=[\${VAR}]"'`); + console.log(result.stdout); + + fs.unlinkSync('bracket.json'); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-command-sub-cut.mjs b/test-command-sub-cut.mjs new file mode 100644 index 00000000..fb60789e --- /dev/null +++ b/test-command-sub-cut.mjs @@ -0,0 +1,22 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import { execSync } from 'child_process'; + +async function test() { + // Test with real bash - command substitution with empty grep | cut + console.log('=== REAL BASH: VAR=$(true | cut -d: -f1) ==='); + const realResult = execSync(`bash -c 'VAR=$(true | cut -d: -f1); echo "VAR=[\${VAR}]"'`, { encoding: 'utf-8' }); + console.log('Output:', realResult); + + // Test with just-bash + console.log('=== JUST-BASH: VAR=$(true | cut -d: -f1) ==='); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); + + const result = await bash.exec(`bash -c 'VAR=$(true | cut -d: -f1); echo "VAR=[\${VAR}]"'`); + console.log('Output:', result.stdout); + console.log('Stderr:', result.stderr); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-compare-methods.mjs b/test-compare-methods.mjs new file mode 100644 index 00000000..19ed2296 --- /dev/null +++ b/test-compare-methods.mjs @@ -0,0 +1,65 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import fs from 'fs'; + +async function testDirect() { + console.log('=== TEST 1: Direct command ==='); + const jsonData = fs.readFileSync('excelData.json', 'utf-8'); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/', TMPDIR: '/tmp' } }); + + await memFs.writeFile('/tmp/data.json', jsonData); + + const command = `DATA=$(cat /tmp/data.json) +HEADER_IDX=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | grep -n '"Source"' | tail -1 | cut -d: -f1 | awk '{print $1 - 1}') +START_IDX=$((HEADER_IDX + 1)) +STOP_LINE=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) +if [ -n "$STOP_LINE" ]; then + COUNT=$((STOP_LINE - 1)) +else + TOTAL=$(echo "$DATA" | jq '.[0].sheets[0].rows | length') + COUNT=$((TOTAL - START_IDX)) +fi +echo "{\\"h\\": $HEADER_IDX, \\"s\\": $START_IDX, \\"n\\": $COUNT}"`; + + const result = await bash.exec(command); + console.log('Exit code:', result.exitCode); + console.log('Stdout:', result.stdout); + console.log('Stderr:', result.stderr); +} + +async function testViaBashC() { + console.log('\n=== TEST 2: Via bash -c (like original) ==='); + const jsonData = fs.readFileSync('excelData.json', 'utf-8'); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/', TMPDIR: '/tmp' } }); + + await memFs.writeFile('/tmp/stdin_data', jsonData); + + const innerCommand = `DATA=$(cat) +HEADER_IDX=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | grep -n '"Source"' | tail -1 | cut -d: -f1 | awk '{print $1 - 1}') +START_IDX=$((HEADER_IDX + 1)) +STOP_LINE=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) +if [ -n "$STOP_LINE" ]; then + COUNT=$((STOP_LINE - 1)) +else + TOTAL=$(echo "$DATA" | jq '.[0].sheets[0].rows | length') + COUNT=$((TOTAL - START_IDX)) +fi +echo "{\\"h\\": $HEADER_IDX, \\"s\\": $START_IDX, \\"n\\": $COUNT}"`; + + const fullCommand = `cat /tmp/stdin_data | bash -c '${innerCommand.replace(/'/g, "'\\''")}'`; + + const result = await bash.exec(fullCommand); + console.log('Exit code:', result.exitCode); + console.log('Stdout:', result.stdout); + console.log('Stderr:', result.stderr); +} + +async function run() { + await testDirect(); + await testViaBashC(); +} + +run().catch(console.error); + +// Made with Bob diff --git a/test-cut-empty-stdin.mjs b/test-cut-empty-stdin.mjs new file mode 100644 index 00000000..2b2931aa --- /dev/null +++ b/test-cut-empty-stdin.mjs @@ -0,0 +1,24 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import { execSync } from 'child_process'; + +async function test() { + // Test with real bash - truly empty input to cut + console.log('=== REAL BASH: true | cut -d: -f1 ==='); + const realResult = execSync(`true | cut -d: -f1`, { encoding: 'utf-8' }); + console.log('Output:', JSON.stringify(realResult)); + console.log('Hex:', Buffer.from(realResult).toString('hex')); + + // Test with just-bash + console.log('\n=== JUST-BASH: true | cut -d: -f1 ==='); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); + + const result = await bash.exec(`true | cut -d: -f1`); + console.log('Output:', JSON.stringify(result.stdout)); + console.log('Hex:', Buffer.from(result.stdout).toString('hex')); + console.log('Exit code:', result.exitCode); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-echo-variable.mjs b/test-echo-variable.mjs new file mode 100644 index 00000000..06c80da3 --- /dev/null +++ b/test-echo-variable.mjs @@ -0,0 +1,25 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import fs from 'fs'; +import { execSync } from 'child_process'; + +async function test() { + const jsonData = fs.readFileSync('excelData.json', 'utf-8'); + const jqOutput = execSync(`cat excelData.json | jq -c ".[0].sheets[0].rows[]"`, { encoding: 'utf-8' }); + + // Test with real bash - echo variable into pipeline + console.log('=== REAL BASH: echo "$VAR" | tail | grep | head ==='); + const realResult = execSync(`bash -c 'DATA="${jqOutput.replace(/"/g, '\\"').replace(/\$/g, '\\$')}"; VAR=$(echo "$DATA" | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1); echo "VAR=[$VAR]"'`, { encoding: 'utf-8' }); + console.log(realResult); + + // Test with just-bash + console.log('=== JUST-BASH: echo "$VAR" | tail | grep | head ==='); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); + + const result = await bash.exec(`bash -c 'DATA="${jqOutput.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\$/g, '\\$')}"; VAR=$(echo "$DATA" | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1); echo "VAR=[\${VAR}]"'`); + console.log(result.stdout); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-eliminate-jq.mjs b/test-eliminate-jq.mjs new file mode 100644 index 00000000..6ff5bad4 --- /dev/null +++ b/test-eliminate-jq.mjs @@ -0,0 +1,33 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import fs from 'fs'; +import { execSync } from 'child_process'; + +async function test() { + const jsonData = fs.readFileSync('excelData.json', 'utf-8'); + + // First, get the jq output + console.log('=== Step 1: Get jq output ==='); + const jqOutput = execSync(`cat excelData.json | jq -c ".[0].sheets[0].rows[]"`, { encoding: 'utf-8' }); + console.log('Lines:', jqOutput.split('\n').length); + fs.writeFileSync('jq-output.txt', jqOutput); + + // Test with real bash using the jq output directly + console.log('\n=== REAL BASH: cat jq-output | tail | grep | head ==='); + const realResult = execSync(`bash -c 'VAR=$(cat jq-output.txt | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1); echo "VAR=[$VAR]"'`, { encoding: 'utf-8' }); + console.log(realResult); + + // Test with just-bash + console.log('=== JUST-BASH: cat jq-output | tail | grep | head ==='); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); + await memFs.writeFile('/jq-output.txt', jqOutput); + + const result = await bash.exec(`bash -c 'VAR=$(cat /jq-output.txt | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1); echo "VAR=[\${VAR}]"'`); + console.log(result.stdout); + + fs.unlinkSync('jq-output.txt'); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-excel-minimal.mjs b/test-excel-minimal.mjs new file mode 100644 index 00000000..3dd6cc06 --- /dev/null +++ b/test-excel-minimal.mjs @@ -0,0 +1,24 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import fs from 'fs'; +import { execSync } from 'child_process'; + +async function test() { + const jsonData = fs.readFileSync('excelData.json', 'utf-8'); + + // Simplest possible test with the actual data + console.log('=== REAL BASH: DATA=$(cat) then echo "$DATA" | jq | tail +43 | grep | head ==='); + const realResult = execSync(`cat excelData.json | bash -c 'DATA=$(cat); VAR=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1); echo "VAR=[$VAR]"'`, { encoding: 'utf-8' }); + console.log(realResult); + + console.log('=== JUST-BASH: same command ==='); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); + await memFs.writeFile('/data.json', jsonData); + + const result = await bash.exec(`cat /data.json | bash -c 'DATA=$(cat); VAR=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1); echo "VAR=[\${VAR}]"'`); + console.log(result.stdout); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-final-minimal.mjs b/test-final-minimal.mjs new file mode 100644 index 00000000..44382a4e --- /dev/null +++ b/test-final-minimal.mjs @@ -0,0 +1,22 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import { execSync } from 'child_process'; + +async function test() { + const testData = 'hello world'; + + // Real bash - outputs: VAR=[hello world] + console.log('=== REAL BASH ==='); + const cmd = 'DATA=$(cat); VAR=$(echo "$DATA"); echo "VAR=[$VAR]"'; + const realResult = execSync(`echo "${testData}" | bash -c '${cmd}'`, { encoding: 'utf-8' }); + console.log(realResult); + + // Just-bash - outputs: (empty) + console.log('=== JUST-BASH ==='); + const bash = new Bash({ fs: new InMemoryFs() }); + const result = await bash.exec(`echo "${testData}" | bash -c 'DATA=\\$(cat); VAR=\\$(echo "\\$DATA"); echo "VAR=[\\$VAR]"'`); + console.log(result.stdout); +} + +test(); + +// Made with Bob diff --git a/test-grep-diff.mjs b/test-grep-diff.mjs new file mode 100644 index 00000000..fcb95e14 --- /dev/null +++ b/test-grep-diff.mjs @@ -0,0 +1,31 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import fs from 'fs'; +import { execSync } from 'child_process'; + +async function test() { + const jsonData = fs.readFileSync('excelData.json', 'utf-8'); + + // Test with real bash - just the grep part + console.log('=== REAL BASH: grep output ==='); + try { + const realResult = execSync(`cat excelData.json | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | grep -in "TOTAL EXPENSES"`, { encoding: 'utf-8' }); + console.log('Output:', JSON.stringify(realResult)); + } catch (e) { + console.log('Exit code:', e.status); + console.log('Output:', JSON.stringify(e.stdout)); + } + + // Test with just-bash + console.log('\n=== JUST-BASH: grep output ==='); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/', TMPDIR: '/tmp' } }); + await memFs.writeFile('/tmp/data.json', jsonData); + + const result = await bash.exec(`cat /tmp/data.json | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | grep -in "TOTAL EXPENSES"`); + console.log('Output:', JSON.stringify(result.stdout)); + console.log('Stderr:', result.stderr); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-grep-head-cut.mjs b/test-grep-head-cut.mjs new file mode 100644 index 00000000..397619f0 --- /dev/null +++ b/test-grep-head-cut.mjs @@ -0,0 +1,28 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import { execSync } from 'child_process'; + +async function test() { + // Test with real bash - grep with no match | head | cut + console.log('=== REAL BASH: echo "test" | grep "nomatch" | head -1 | cut -d: -f1 ==='); + try { + const realResult = execSync(`bash -c 'VAR=$(echo "test" | grep "nomatch" | head -1 | cut -d: -f1); echo "VAR=[\${VAR}]"'`, { encoding: 'utf-8' }); + console.log('Output:', realResult); + } catch (e) { + console.log('Exit code:', e.status); + console.log('Output:', e.stdout); + } + + // Test with just-bash + console.log('\n=== JUST-BASH: echo "test" | grep "nomatch" | head -1 | cut -d: -f1 ==='); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); + + const result = await bash.exec(`bash -c 'VAR=$(echo "test" | grep "nomatch" | head -1 | cut -d: -f1); echo "VAR=[\${VAR}]"'`); + console.log('Output:', result.stdout); + console.log('Stderr:', result.stderr); + console.log('Exit code:', result.exitCode); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-head-cut-empty.mjs b/test-head-cut-empty.mjs new file mode 100644 index 00000000..9d736968 --- /dev/null +++ b/test-head-cut-empty.mjs @@ -0,0 +1,24 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import { execSync } from 'child_process'; + +async function test() { + // Test with real bash - empty input to head | cut + console.log('=== REAL BASH: empty | head -1 | cut -d: -f1 ==='); + const realResult = execSync(`echo -n "" | head -1 | cut -d: -f1`, { encoding: 'utf-8' }); + console.log('Output:', JSON.stringify(realResult)); + console.log('Length:', realResult.length); + + // Test with just-bash + console.log('\n=== JUST-BASH: empty | head -1 | cut -d: -f1 ==='); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); + + const result = await bash.exec(`echo -n "" | head -1 | cut -d: -f1`); + console.log('Output:', JSON.stringify(result.stdout)); + console.log('Length:', result.stdout.length); + console.log('Exit code:', result.exitCode); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-jq-grep-head.mjs b/test-jq-grep-head.mjs new file mode 100644 index 00000000..a0c6bdfe --- /dev/null +++ b/test-jq-grep-head.mjs @@ -0,0 +1,23 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import { execSync } from 'child_process'; + +async function test() { + const simpleJson = '[{"a":1},{"b":2}]'; + + // Test with real bash + console.log('=== REAL BASH: jq | grep nomatch | head ==='); + const realResult = execSync(`bash -c 'VAR=$(echo '${simpleJson}' | jq -c ".[]" | grep "nomatch" | head -1); echo "VAR=[$VAR]"'`, { encoding: 'utf-8' }); + console.log(realResult); + + // Test with just-bash + console.log('=== JUST-BASH: jq | grep nomatch | head ==='); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); + + const result = await bash.exec(`bash -c 'VAR=$(echo '${simpleJson}' | jq -c ".[]" | grep "nomatch" | head -1); echo "VAR=[\${VAR}]"'`); + console.log(result.stdout); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-jq-simple.mjs b/test-jq-simple.mjs new file mode 100644 index 00000000..6a8493bd --- /dev/null +++ b/test-jq-simple.mjs @@ -0,0 +1,28 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import { execSync } from 'child_process'; +import fs from 'fs'; + +async function test() { + const simpleJson = '[{"a":1},{"b":2}]'; + fs.writeFileSync('simple.json', simpleJson); + + // Test with real bash + console.log('=== REAL BASH: jq | grep nomatch | head ==='); + const realResult = execSync(`bash -c 'VAR=$(cat simple.json | jq -c ".[]" | grep "nomatch" | head -1); echo "VAR=[$VAR]"'`, { encoding: 'utf-8' }); + console.log(realResult); + + // Test with just-bash + console.log('=== JUST-BASH: jq | grep nomatch | head ==='); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); + await memFs.writeFile('/simple.json', simpleJson); + + const result = await bash.exec(`bash -c 'VAR=$(cat /simple.json | jq -c ".[]" | grep "nomatch" | head -1); echo "VAR=[\${VAR}]"'`); + console.log(result.stdout); + + fs.unlinkSync('simple.json'); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-jq-tail-grep.mjs b/test-jq-tail-grep.mjs new file mode 100644 index 00000000..414ce5b9 --- /dev/null +++ b/test-jq-tail-grep.mjs @@ -0,0 +1,28 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import { execSync } from 'child_process'; +import fs from 'fs'; + +async function test() { + const simpleJson = '[{"a":1},{"b":2},{"c":3}]'; + fs.writeFileSync('simple.json', simpleJson); + + // Test with real bash + console.log('=== REAL BASH: jq | tail | grep nomatch | head ==='); + const realResult = execSync(`bash -c 'VAR=$(cat simple.json | jq -c ".[]" | tail -n +2 | grep "nomatch" | head -1); echo "VAR=[$VAR]"'`, { encoding: 'utf-8' }); + console.log(realResult); + + // Test with just-bash + console.log('=== JUST-BASH: jq | tail | grep nomatch | head ==='); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); + await memFs.writeFile('/simple.json', simpleJson); + + const result = await bash.exec(`bash -c 'VAR=$(cat /simple.json | jq -c ".[]" | tail -n +2 | grep "nomatch" | head -1); echo "VAR=[\${VAR}]"'`); + console.log(result.stdout); + + fs.unlinkSync('simple.json'); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-minimal-error.mjs b/test-minimal-error.mjs new file mode 100644 index 00000000..98e6671d --- /dev/null +++ b/test-minimal-error.mjs @@ -0,0 +1,35 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import fs from 'fs'; + +async function test() { + const jsonData = fs.readFileSync('excelData.json', 'utf-8'); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/', TMPDIR: '/tmp' } }); + + await memFs.writeFile('/tmp/data.json', jsonData); + + // Simplified version - just the part that fails + const command = ` +DATA=$(cat /tmp/data.json) +HEADER_IDX=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | grep -n '"Source"' | tail -1 | cut -d: -f1 | awk '{print $1 - 1}') +START_IDX=$((HEADER_IDX + 1)) +STOP_LINE=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) +echo "STOP_LINE=[$STOP_LINE]" +if [ -n "$STOP_LINE" ]; then + COUNT=$((STOP_LINE - 1)) +else + TOTAL=$(echo "$DATA" | jq '.[0].sheets[0].rows | length') + COUNT=$((TOTAL - START_IDX)) +fi +echo "COUNT=[$COUNT]" +`; + + const result = await bash.exec(command); + console.log('Exit code:', result.exitCode); + console.log('Stdout:\n', result.stdout); + console.log('Stderr:\n', result.stderr); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-narrow-error.mjs b/test-narrow-error.mjs new file mode 100644 index 00000000..a25f1a78 --- /dev/null +++ b/test-narrow-error.mjs @@ -0,0 +1,23 @@ +import { Bash, InMemoryFs } from 'just-bash'; + +async function test() { + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); + + // Minimal test case - just the [ -n "$VAR" ] test via bash -c + console.log('=== Test 1: Empty variable in [ -n ] via bash -c ==='); + let result = await bash.exec(`bash -c 'VAR=""; if [ -n "$VAR" ]; then echo "not empty"; else echo "empty"; fi'`); + console.log('Exit:', result.exitCode, 'Out:', result.stdout.trim(), 'Err:', result.stderr.trim()); + + console.log('\n=== Test 2: Variable with brackets in [ -n ] via bash -c ==='); + result = await bash.exec(`bash -c 'VAR="["; if [ -n "$VAR" ]; then echo "not empty"; else echo "empty"; fi'`); + console.log('Exit:', result.exitCode, 'Out:', result.stdout.trim(), 'Err:', result.stderr.trim()); + + console.log('\n=== Test 3: Direct (no bash -c) ==='); + result = await bash.exec(`VAR="["; if [ -n "$VAR" ]; then echo "not empty"; else echo "empty"; fi`); + console.log('Exit:', result.exitCode, 'Out:', result.stdout.trim(), 'Err:', result.stderr.trim()); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-narrow-pipeline.mjs b/test-narrow-pipeline.mjs new file mode 100644 index 00000000..ccebdad6 --- /dev/null +++ b/test-narrow-pipeline.mjs @@ -0,0 +1,28 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import fs from 'fs'; + +async function test() { + const jsonData = fs.readFileSync('excelData.json', 'utf-8'); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/', TMPDIR: '/tmp' } }); + await memFs.writeFile('/tmp/data.json', jsonData); + + // Test without cut + console.log('=== Without cut (just grep | head) ==='); + let result = await bash.exec(`cat /tmp/data.json | bash -c 'DATA=$(cat); VAR=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1); echo "VAR=[\${VAR}]"'`); + console.log(result.stdout); + + // Test without head + console.log('=== Without head (just grep | cut) ==='); + result = await bash.exec(`cat /tmp/data.json | bash -c 'DATA=$(cat); VAR=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | grep -in "TOTAL EXPENSES" | cut -d: -f1); echo "VAR=[\${VAR}]"'`); + console.log(result.stdout); + + // Test without grep + console.log('=== Without grep (just tail | head | cut) ==='); + result = await bash.exec(`cat /tmp/data.json | bash -c 'DATA=$(cat); VAR=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | head -1 | cut -d: -f1); echo "VAR=[\${VAR}]"'`); + console.log(result.stdout); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-nested-substitution.mjs b/test-nested-substitution.mjs new file mode 100644 index 00000000..bc618cdf --- /dev/null +++ b/test-nested-substitution.mjs @@ -0,0 +1,26 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import fs from 'fs'; +import { execSync } from 'child_process'; + +async function test() { + const jsonData = fs.readFileSync('excelData.json', 'utf-8'); + + // Test with real bash - nested command substitution + console.log('=== REAL BASH: DATA=$(cat) then echo "$DATA" | jq ==='); + const realResult = execSync(`cat excelData.json | bash -c 'DATA=$(cat); VAR=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1); echo "VAR=[$VAR]"'`, { encoding: 'utf-8' }); + console.log(realResult); + + // Test with just-bash + console.log('=== JUST-BASH: DATA=$(cat) then echo "$DATA" | jq ==='); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); + await memFs.writeFile('/data.json', jsonData); + + const result = await bash.exec(`cat /data.json | bash -c 'DATA=$(cat); VAR=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1); echo "VAR=[\${VAR}]"'`); + console.log(result.stdout); + console.log('Stderr:', result.stderr); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-pipeline-debug.mjs b/test-pipeline-debug.mjs new file mode 100644 index 00000000..4206d2d3 --- /dev/null +++ b/test-pipeline-debug.mjs @@ -0,0 +1,39 @@ +import { Bash, InMemoryFs } from './dist/index.js'; +import fs from 'fs'; + +async function test() { + const jsonData = fs.readFileSync('excelData.json', 'utf-8'); + + // Test just the problematic pipeline step by step + const command = `DATA=$(cat) +echo "=== Step 1: jq output (first 5 lines) ===" >&2 +echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | head -5 >&2 +echo "" >&2 +echo "=== Step 2: after tail -n +43 (first 5 lines) ===" >&2 +echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | head -5 >&2 +echo "" >&2 +echo "=== Step 3: grep -in TOTAL EXPENSES ===" >&2 +echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | grep -in "TOTAL EXPENSES" >&2 +echo "" >&2 +echo "=== Step 4: after head -1 ===" >&2 +echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1 >&2 +echo "" >&2 +echo "=== Step 5: after cut -d: -f1 ===" >&2 +RESULT=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) +echo "RESULT=[$RESULT]" >&2`; + + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs }); + await memFs.writeFile('/tmp/data.json', jsonData); + + const fullCommand = `cat /tmp/data.json | bash -c '${command.replace(/'/g, "'\\''")}'`; + + const result = await bash.exec(fullCommand); + + console.log('STDERR:'); + console.log(result.stderr); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-pipeline-steps.mjs b/test-pipeline-steps.mjs new file mode 100644 index 00000000..1276e835 --- /dev/null +++ b/test-pipeline-steps.mjs @@ -0,0 +1,37 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import fs from 'fs'; + +async function test() { + const jsonData = fs.readFileSync('excelData.json', 'utf-8'); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/', TMPDIR: '/tmp' } }); + + await memFs.writeFile('/tmp/data.json', jsonData); + + // Step 1: Get rows after START_IDX + console.log('=== Step 1: jq rows | tail -n +43 ==='); + let result = await bash.exec(`cat /tmp/data.json | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | head -5`); + console.log('Output:', result.stdout); + + // Step 2: grep for TOTAL EXPENSES + console.log('\n=== Step 2: grep -in "TOTAL EXPENSES" ==='); + result = await bash.exec(`cat /tmp/data.json | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | grep -in "TOTAL EXPENSES"`); + console.log('Output:', result.stdout); + console.log('Stderr:', result.stderr); + + // Step 3: head -1 + console.log('\n=== Step 3: head -1 ==='); + result = await bash.exec(`cat /tmp/data.json | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1`); + console.log('Output:', result.stdout); + console.log('Stderr:', result.stderr); + + // Step 4: cut -d: -f1 + console.log('\n=== Step 4: cut -d: -f1 ==='); + result = await bash.exec(`cat /tmp/data.json | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1`); + console.log('Output:', result.stdout); + console.log('Stderr:', result.stderr); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-script.sh b/test-script.sh new file mode 100644 index 00000000..25706f50 --- /dev/null +++ b/test-script.sh @@ -0,0 +1,11 @@ +DATA=$(cat) +HEADER_IDX=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | grep -n '"Source"' | tail -1 | cut -d: -f1 | awk '{print $1 - 1}') +START_IDX=$((HEADER_IDX + 1)) +STOP_LINE=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) +if [ -n "$STOP_LINE" ]; then + COUNT=$((STOP_LINE - 1)) +else + TOTAL=$(echo "$DATA" | jq '.[0].sheets[0].rows | length') + COUNT=$((TOTAL - START_IDX)) +fi +echo "{\"h\": $HEADER_IDX, \"s\": $START_IDX, \"n\": $COUNT}" \ No newline at end of file diff --git a/test-simple-diff.mjs b/test-simple-diff.mjs new file mode 100644 index 00000000..86ac33f0 --- /dev/null +++ b/test-simple-diff.mjs @@ -0,0 +1,35 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import fs from 'fs'; +import { execSync } from 'child_process'; + +async function test() { + const jsonData = fs.readFileSync('excelData.json', 'utf-8'); + + // Test with real bash + console.log('=== REAL BASH ==='); + const realResult = execSync(`cat excelData.json | bash -c 'DATA=$(cat) +HEADER_IDX=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | grep -n "\\"Source\\"" | tail -1 | cut -d: -f1 | awk "{print \\$1 - 1}") +START_IDX=$((HEADER_IDX + 1)) +STOP_LINE=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) +echo "STOP_LINE=[$STOP_LINE]"'`, { encoding: 'utf-8' }); + console.log(realResult); + + // Test with just-bash + console.log('=== JUST-BASH ==='); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/', TMPDIR: '/tmp' } }); + await memFs.writeFile('/tmp/stdin_data', jsonData); + + const result = await bash.exec(`cat /tmp/stdin_data | bash -c 'DATA=$(cat) +HEADER_IDX=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | grep -n "\\"Source\\"" | tail -1 | cut -d: -f1 | awk "{print \\$1 - 1}") +START_IDX=$((HEADER_IDX + 1)) +STOP_LINE=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) +echo "STOP_LINE=[\${STOP_LINE}]"'`); + + console.log(result.stdout); + if (result.stderr) console.log('Stderr:', result.stderr); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-simplest-bug.mjs b/test-simplest-bug.mjs new file mode 100644 index 00000000..8b8da070 --- /dev/null +++ b/test-simplest-bug.mjs @@ -0,0 +1,39 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import fs from 'fs'; +import { execSync } from 'child_process'; + +async function testCommand(desc, innerPipeline) { + const jsonData = fs.readFileSync('excelData.json', 'utf-8'); + + const cmd = `DATA=$(cat); VAR=$(${innerPipeline}); echo "VAR=[$VAR]"`; + const realResult = execSync(`cat excelData.json | bash -c '${cmd}'`, { encoding: 'utf-8' }); + + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); + await memFs.writeFile('/data.json', jsonData); + + const result = await bash.exec(`cat /data.json | bash -c '${cmd.replace(/\$/g, '\\$')}'`); + + const match = realResult.trim() === result.stdout.trim(); + console.log(`${match ? '✓' : '✗'} ${desc}`); + if (!match) { + console.log(` Real: ${JSON.stringify(realResult.trim().substring(0, 50))}`); + console.log(` Just: ${JSON.stringify(result.stdout.trim().substring(0, 50))}`); + } +} + +async function test() { + console.log('Testing simpler cases:\n'); + + await testCommand('echo "$DATA" | jq', 'echo "$DATA" | jq -c ".[0].sheets[0].rows[]"'); + + await testCommand('echo "$DATA" | head', 'echo "$DATA" | head -1'); + + await testCommand('echo "$DATA"', 'echo "$DATA"'); + + await testCommand('cat (no echo)', 'cat'); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-simplest.mjs b/test-simplest.mjs new file mode 100644 index 00000000..51a1a909 --- /dev/null +++ b/test-simplest.mjs @@ -0,0 +1,25 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import { execSync } from 'child_process'; + +async function test() { + // Test with simple data - grep no match | head + console.log('=== REAL BASH: echo "[test]" | grep "nomatch" | head -1 ==='); + try { + const realResult = execSync(`bash -c 'VAR=$(echo "[test]" | grep "nomatch" | head -1); echo "VAR=[$VAR]"'`, { encoding: 'utf-8' }); + console.log(realResult); + } catch (e) { + console.log('Exit:', e.status, 'Output:', e.stdout); + } + + console.log('=== JUST-BASH: echo "[test]" | grep "nomatch" | head -1 ==='); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); + + const result = await bash.exec(`bash -c 'VAR=$(echo "[test]" | grep "nomatch" | head -1); echo "VAR=[\${VAR}]"'`); + console.log(result.stdout); + console.log('Exit:', result.exitCode); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-stdin-passthrough.mjs b/test-stdin-passthrough.mjs new file mode 100644 index 00000000..681f08a1 --- /dev/null +++ b/test-stdin-passthrough.mjs @@ -0,0 +1,34 @@ +import { Bash, InMemoryFs } from './dist/index.js'; +import { execSync } from 'child_process'; +import fs from 'fs'; + +async function test() { + // Simple script that just reads stdin + const scriptContent = 'cat'; + fs.writeFileSync('test-stdin.sh', scriptContent); + + const testData = 'hello world'; + const fullCommand = `echo "${testData}" | bash test-stdin.sh`; + + // Test with real bash + console.log('=== REAL BASH ==='); + console.log('Command:', fullCommand); + const realResult = execSync(fullCommand, { encoding: 'utf-8' }); + console.log('stdout:', JSON.stringify(realResult)); + + // Test with just-bash + console.log('\n=== JUST-BASH ==='); + console.log('Command:', fullCommand); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs }); + await memFs.writeFile('test-stdin.sh', scriptContent); + + const result = await bash.exec(fullCommand); + console.log('stdout:', JSON.stringify(result.stdout)); + console.log('stderr:', JSON.stringify(result.stderr)); + console.log('exit code:', result.exitCode); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-stdin-simple.mjs b/test-stdin-simple.mjs new file mode 100644 index 00000000..76eb1292 --- /dev/null +++ b/test-stdin-simple.mjs @@ -0,0 +1,36 @@ +import { Bash, InMemoryFs } from './dist/index.js'; + +async function test() { + console.log('=== Test 1: Without leading slash ==='); + const memFs1 = new InMemoryFs(); + const bash1 = new Bash({ fs: memFs1 }); + await memFs1.writeFile('test.sh', 'cat'); + const result1 = await bash1.exec('echo "hello" | bash test.sh'); + console.log('stdout:', JSON.stringify(result1.stdout)); + console.log('stderr:', JSON.stringify(result1.stderr)); + console.log('exitCode:', result1.exitCode); + + console.log('\n=== Test 2: With leading slash ==='); + const memFs2 = new InMemoryFs(); + const bash2 = new Bash({ fs: memFs2 }); + await memFs2.writeFile('/test.sh', 'cat'); + const result2 = await bash2.exec('echo "hello" | bash /test.sh'); + console.log('stdout:', JSON.stringify(result2.stdout)); + console.log('stderr:', JSON.stringify(result2.stderr)); + console.log('exitCode:', result2.exitCode); + + console.log('\n=== Test 3: Using files option ==='); + const bash3 = new Bash({ + files: { + '/test.sh': 'cat' + } + }); + const result3 = await bash3.exec('echo "hello" | bash /test.sh'); + console.log('stdout:', JSON.stringify(result3.stdout)); + console.log('stderr:', JSON.stringify(result3.stderr)); + console.log('exitCode:', result3.exitCode); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-stdin.sh b/test-stdin.sh new file mode 100644 index 00000000..5d5b306e --- /dev/null +++ b/test-stdin.sh @@ -0,0 +1 @@ +cat \ No newline at end of file diff --git a/test-stopline-value.mjs b/test-stopline-value.mjs new file mode 100644 index 00000000..f3f59b9e --- /dev/null +++ b/test-stopline-value.mjs @@ -0,0 +1,45 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import fs from 'fs'; + +async function test() { + const jsonData = fs.readFileSync('excelData.json', 'utf-8'); + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/', TMPDIR: '/tmp' } }); + + await memFs.writeFile('/tmp/data.json', jsonData); + + // Get the actual STOP_LINE value via bash -c + console.log('=== Getting STOP_LINE value via bash -c ==='); + const cmd1 = `cat /tmp/data.json | bash -c 'DATA=$(cat) +HEADER_IDX=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | grep -n "\\"Source\\"" | tail -1 | cut -d: -f1 | awk "{print \\$1 - 1}") +START_IDX=$((HEADER_IDX + 1)) +STOP_LINE=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) +echo "STOP_LINE=[\${STOP_LINE}]" +printf "STOP_LINE_HEX="; printf "%s" "\$STOP_LINE" | od -A n -t x1'`; + + let result = await bash.exec(cmd1); + console.log('Exit:', result.exitCode); + console.log('Stdout:', result.stdout); + console.log('Stderr:', result.stderr); + + // Now test the [ -n ] with that value + console.log('\n=== Testing [ -n "$STOP_LINE" ] via bash -c ==='); + const cmd2 = `cat /tmp/data.json | bash -c 'DATA=$(cat) +HEADER_IDX=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | grep -n "\\"Source\\"" | tail -1 | cut -d: -f1 | awk "{print \\$1 - 1}") +START_IDX=$((HEADER_IDX + 1)) +STOP_LINE=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) +if [ -n "$STOP_LINE" ]; then + echo "STOP_LINE is not empty: [$STOP_LINE]" +else + echo "STOP_LINE is empty" +fi'`; + + result = await bash.exec(cmd2); + console.log('Exit:', result.exitCode); + console.log('Stdout:', result.stdout); + console.log('Stderr:', result.stderr); +} + +test().catch(console.error); + +// Made with Bob diff --git a/test-work-backwards.mjs b/test-work-backwards.mjs new file mode 100644 index 00000000..9f8e1599 --- /dev/null +++ b/test-work-backwards.mjs @@ -0,0 +1,43 @@ +import { Bash, InMemoryFs } from 'just-bash'; +import fs from 'fs'; +import { execSync } from 'child_process'; + +async function testCommand(desc, innerPipeline) { + const jsonData = fs.readFileSync('excelData.json', 'utf-8'); + + const cmd = `DATA=$(cat); VAR=$(${innerPipeline}); echo "VAR=[$VAR]"`; + const realResult = execSync(`cat excelData.json | bash -c '${cmd}'`, { encoding: 'utf-8' }); + + const memFs = new InMemoryFs(); + const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); + await memFs.writeFile('/data.json', jsonData); + + const result = await bash.exec(`cat /data.json | bash -c '${cmd.replace(/\$/g, '\\$')}'`); + + const match = realResult.trim() === result.stdout.trim(); + console.log(`${match ? '✓' : '✗'} ${desc}`); + if (!match) { + console.log(` Real: ${JSON.stringify(realResult.trim())}`); + console.log(` Just: ${JSON.stringify(result.stdout.trim())}`); + } +} + +async function test() { + console.log('Working backwards from the inner pipeline:\n'); + + await testCommand('Full: jq | tail | grep | head', 'echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1'); + + await testCommand('Remove head', 'echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | grep -in "TOTAL EXPENSES"'); + + await testCommand('Remove grep', 'echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | head -1'); + + await testCommand('Remove tail', 'echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | grep -in "TOTAL EXPENSES" | head -1'); + + await testCommand('Just jq | head', 'echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | head -1'); + + await testCommand('Just jq', 'echo "$DATA" | jq -c ".[0].sheets[0].rows[]"'); +} + +test().catch(console.error); + +// Made with Bob From acde490723d6618f9690bd2681fce71f0468298e Mon Sep 17 00:00:00 2001 From: Robert Yates Date: Mon, 9 Feb 2026 17:10:58 -0500 Subject: [PATCH 2/4] removing the debug tests used to hone in on the issue --- test-actual-pipeline.mjs | 28 ------------ test-arithmetic-bug.mjs | 25 ----------- test-bash-comparison.mjs | 48 --------------------- test-bash-excel-command.mjs | 84 ------------------------------------ test-bash-excel-debug.mjs | 36 ---------------- test-bracket-bug.mjs | 24 ----------- test-bracket-data.mjs | 29 ------------- test-command-sub-cut.mjs | 22 ---------- test-compare-methods.mjs | 65 ---------------------------- test-cut-empty-stdin.mjs | 24 ----------- test-echo-variable.mjs | 25 ----------- test-eliminate-jq.mjs | 33 -------------- test-excel-minimal.mjs | 24 ----------- test-final-minimal.mjs | 22 ---------- test-grep-diff.mjs | 31 ------------- test-grep-head-cut.mjs | 28 ------------ test-head-cut-empty.mjs | 24 ----------- test-jq-grep-head.mjs | 23 ---------- test-jq-simple.mjs | 28 ------------ test-jq-tail-grep.mjs | 28 ------------ test-minimal-error.mjs | 35 --------------- test-narrow-error.mjs | 23 ---------- test-narrow-pipeline.mjs | 28 ------------ test-nested-substitution.mjs | 26 ----------- test-pipeline-debug.mjs | 39 ----------------- test-pipeline-steps.mjs | 37 ---------------- test-script.sh | 11 ----- test-simple-diff.mjs | 35 --------------- test-simplest-bug.mjs | 39 ----------------- test-simplest.mjs | 25 ----------- test-stdin-passthrough.mjs | 34 --------------- test-stdin-simple.mjs | 36 ---------------- test-stdin.sh | 1 - test-stopline-value.mjs | 45 ------------------- test-work-backwards.mjs | 43 ------------------ 35 files changed, 1108 deletions(-) delete mode 100644 test-actual-pipeline.mjs delete mode 100644 test-arithmetic-bug.mjs delete mode 100644 test-bash-comparison.mjs delete mode 100644 test-bash-excel-command.mjs delete mode 100644 test-bash-excel-debug.mjs delete mode 100644 test-bracket-bug.mjs delete mode 100644 test-bracket-data.mjs delete mode 100644 test-command-sub-cut.mjs delete mode 100644 test-compare-methods.mjs delete mode 100644 test-cut-empty-stdin.mjs delete mode 100644 test-echo-variable.mjs delete mode 100644 test-eliminate-jq.mjs delete mode 100644 test-excel-minimal.mjs delete mode 100644 test-final-minimal.mjs delete mode 100644 test-grep-diff.mjs delete mode 100644 test-grep-head-cut.mjs delete mode 100644 test-head-cut-empty.mjs delete mode 100644 test-jq-grep-head.mjs delete mode 100644 test-jq-simple.mjs delete mode 100644 test-jq-tail-grep.mjs delete mode 100644 test-minimal-error.mjs delete mode 100644 test-narrow-error.mjs delete mode 100644 test-narrow-pipeline.mjs delete mode 100644 test-nested-substitution.mjs delete mode 100644 test-pipeline-debug.mjs delete mode 100644 test-pipeline-steps.mjs delete mode 100644 test-script.sh delete mode 100644 test-simple-diff.mjs delete mode 100644 test-simplest-bug.mjs delete mode 100644 test-simplest.mjs delete mode 100644 test-stdin-passthrough.mjs delete mode 100644 test-stdin-simple.mjs delete mode 100644 test-stdin.sh delete mode 100644 test-stopline-value.mjs delete mode 100644 test-work-backwards.mjs diff --git a/test-actual-pipeline.mjs b/test-actual-pipeline.mjs deleted file mode 100644 index 3836bb14..00000000 --- a/test-actual-pipeline.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import fs from 'fs'; -import { execSync } from 'child_process'; - -async function test() { - const jsonData = fs.readFileSync('excelData.json', 'utf-8'); - - // Test the exact failing pipeline with real bash - console.log('=== REAL BASH ==='); - const realCmd = `cat excelData.json | bash -c 'DATA=$(cat); VAR=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1); echo "VAR=[$VAR]"'`; - const realResult = execSync(realCmd, { encoding: 'utf-8' }); - console.log(realResult); - - // Test with just-bash using the SAME command - console.log('=== JUST-BASH ==='); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs }); - await memFs.writeFile('excelData.json', jsonData); - - // Use the exact same realCmd - const result = await bash.exec(realCmd); - console.log(result.stdout); - if (result.stderr) console.log('Stderr:', result.stderr); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-arithmetic-bug.mjs b/test-arithmetic-bug.mjs deleted file mode 100644 index 888e0e5f..00000000 --- a/test-arithmetic-bug.mjs +++ /dev/null @@ -1,25 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; - -async function test() { - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); - - console.log('=== Test: STOP_LINE="[" with arithmetic via bash -c ==='); - const result = await bash.exec(`bash -c 'STOP_LINE="[" -if [ -n "$STOP_LINE" ]; then - COUNT=$((STOP_LINE - 1)) - echo "COUNT=$COUNT" -else - echo "empty" -fi'`); - - console.log('Exit:', result.exitCode); - console.log('Stdout:', result.stdout); - console.log('Stderr:', result.stderr); - - console.log('\n=== Compare with real bash ==='); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-bash-comparison.mjs b/test-bash-comparison.mjs deleted file mode 100644 index 4592dd0b..00000000 --- a/test-bash-comparison.mjs +++ /dev/null @@ -1,48 +0,0 @@ -import { Bash, InMemoryFs } from './dist/index.js'; -import { execSync } from 'child_process'; - -async function test() { - console.log('=== TEST 1: Without bash -c ==='); - const command1 = `RESULT=$(echo "test" | grep "nomatch" | head -1); echo "RESULT=[$RESULT]"`; - - // Real bash - console.log('REAL BASH:'); - try { - const realResult = execSync(command1, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }); - console.log('stdout:', realResult); - } catch (e) { - console.log('stdout:', e.stdout); - console.log('exit code:', e.status); - } - - // Just-bash - console.log('\nJUST-BASH:'); - const bash1 = new Bash({ fs: new InMemoryFs() }); - const result1 = await bash1.exec(command1); - console.log('stdout:', result1.stdout); - console.log('exit code:', result1.exitCode); - - console.log('\n=== TEST 2: With bash -c ==='); - const command2 = `echo "test" | bash -c 'RESULT=$(cat | grep "nomatch" | head -1); echo "RESULT=[$RESULT]"'`; - - // Real bash - console.log('REAL BASH:'); - try { - const realResult = execSync(command2, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }); - console.log('stdout:', realResult); - } catch (e) { - console.log('stdout:', e.stdout); - console.log('exit code:', e.status); - } - - // Just-bash - console.log('\nJUST-BASH:'); - const bash2 = new Bash({ fs: new InMemoryFs() }); - const result2 = await bash2.exec(command2); - console.log('stdout:', result2.stdout); - console.log('exit code:', result2.exitCode); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-bash-excel-command.mjs b/test-bash-excel-command.mjs deleted file mode 100644 index 42ef21c0..00000000 --- a/test-bash-excel-command.mjs +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Test script to execute the bash command using just-bash - * This demonstrates piping the excelData.json through the bash command - */ - -import { Bash, InMemoryFs } from 'just-bash'; -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -async function testBashCommand() { - try { - // Read the JSON file - const jsonPath = path.join(__dirname, 'excelData.json'); - const jsonData = fs.readFileSync(jsonPath, 'utf-8'); - - console.log('Input JSON size:', jsonData.length, 'bytes'); - - // The bash command from the task - const command = `DATA=$(cat) -HEADER_IDX=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | grep -n '"Source"' | tail -1 | cut -d: -f1 | awk '{print $1 - 1}') -START_IDX=$((HEADER_IDX + 1)) -STOP_LINE=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) -if [ -n "$STOP_LINE" ]; then - COUNT=$((STOP_LINE - 1)) -else - TOTAL=$(echo "$DATA" | jq '.[0].sheets[0].rows | length') - COUNT=$((TOTAL - START_IDX)) -fi -echo "{\\"h\\": $HEADER_IDX, \\"s\\": $START_IDX, \\"n\\": $COUNT}"`; - - // Create in-memory filesystem - const memFs = new InMemoryFs(); - - // Create Bash instance - const bash = new Bash({ - fs: memFs, - cwd: '/', - env: { - HOME: '/', - TMPDIR: '/tmp', - }, - }); - - // Write JSON data to a temp file - const tempFile = '/tmp/stdin_data'; - await memFs.writeFile(tempFile, jsonData); - - // Execute the command with cat piping the data - const fullCommand = `cat ${tempFile} | bash -c '${command.replace(/'/g, "'\\''")}'`; - - console.log('\nExecuting command...\n'); - - const result = await bash.exec(fullCommand); - - console.log('Exit code:', result.exitCode); - console.log('\nStdout:'); - console.log(result.stdout); - - if (result.stderr) { - console.log('\nStderr:'); - console.log(result.stderr); - } - - // Try to parse the output as JSON - if (result.stdout.trim()) { - try { - const parsed = JSON.parse(result.stdout.trim()); - console.log('\nParsed output:'); - console.log(JSON.stringify(parsed, null, 2)); - } catch (e) { - console.log('\nCould not parse output as JSON'); - } - } - } catch (error) { - console.error('Error:', error.message); - console.error(error.stack); - } -} - -testBashCommand(); \ No newline at end of file diff --git a/test-bash-excel-debug.mjs b/test-bash-excel-debug.mjs deleted file mode 100644 index d8d9da53..00000000 --- a/test-bash-excel-debug.mjs +++ /dev/null @@ -1,36 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import fs from 'fs'; - -async function test() { - const jsonData = fs.readFileSync('excelData.json', 'utf-8'); - - // Simplified command with debugging - const command = `DATA=$(cat) -echo "DEBUG: DATA length = \${#DATA}" >&2 -HEADER_IDX=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | grep -n '"Source"' | tail -1 | cut -d: -f1 | awk '{print $1 - 1}') -echo "DEBUG: HEADER_IDX = [$HEADER_IDX]" >&2 -START_IDX=$((HEADER_IDX + 1)) -echo "DEBUG: START_IDX = [$START_IDX]" >&2 -STOP_LINE=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) -echo "DEBUG: STOP_LINE = [$STOP_LINE]" >&2 -echo "Final: h=$HEADER_IDX, s=$START_IDX, stop=$STOP_LINE"`; - - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/', TMPDIR: '/tmp' } }); - await memFs.writeFile('/tmp/data.json', jsonData); - - const fullCommand = `cat /tmp/data.json | bash -c '${command.replace(/'/g, "'\\''")}'`; - - console.log('Executing...\n'); - const result = await bash.exec(fullCommand); - - console.log('STDOUT:'); - console.log(result.stdout); - console.log('\nSTDERR:'); - console.log(result.stderr); - console.log('\nExit code:', result.exitCode); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-bracket-bug.mjs b/test-bracket-bug.mjs deleted file mode 100644 index 02d23446..00000000 --- a/test-bracket-bug.mjs +++ /dev/null @@ -1,24 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; - -async function test() { - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); - - console.log('=== Minimal reproduction: STOP_LINE="[" in bash -c ==='); - const result = await bash.exec(`bash -c 'STOP_LINE="[" -if [ -n "$STOP_LINE" ]; then - echo "not empty" -else - echo "empty" -fi'`); - - console.log('Exit:', result.exitCode); - console.log('Stdout:', result.stdout); - console.log('Stderr:', result.stderr); - - console.log('\n=== Compare with real bash ==='); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-bracket-data.mjs b/test-bracket-data.mjs deleted file mode 100644 index cdb7a259..00000000 --- a/test-bracket-data.mjs +++ /dev/null @@ -1,29 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import { execSync } from 'child_process'; -import fs from 'fs'; - -async function test() { - // Create JSON that starts with [ like the Excel data - const jsonData = '[{"test":"value"}]'; - fs.writeFileSync('bracket.json', jsonData); - - // Test with real bash - console.log('=== REAL BASH ==='); - const realResult = execSync(`bash -c 'DATA=$(cat bracket.json); VAR=$(echo "$DATA" | jq -c ".[]" | tail -n +2 | grep "nomatch" | head -1); echo "VAR=[$VAR]"'`, { encoding: 'utf-8' }); - console.log(realResult); - - // Test with just-bash - console.log('=== JUST-BASH ==='); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); - await memFs.writeFile('/bracket.json', jsonData); - - const result = await bash.exec(`bash -c 'DATA=$(cat /bracket.json); VAR=$(echo "$DATA" | jq -c ".[]" | tail -n +2 | grep "nomatch" | head -1); echo "VAR=[\${VAR}]"'`); - console.log(result.stdout); - - fs.unlinkSync('bracket.json'); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-command-sub-cut.mjs b/test-command-sub-cut.mjs deleted file mode 100644 index fb60789e..00000000 --- a/test-command-sub-cut.mjs +++ /dev/null @@ -1,22 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import { execSync } from 'child_process'; - -async function test() { - // Test with real bash - command substitution with empty grep | cut - console.log('=== REAL BASH: VAR=$(true | cut -d: -f1) ==='); - const realResult = execSync(`bash -c 'VAR=$(true | cut -d: -f1); echo "VAR=[\${VAR}]"'`, { encoding: 'utf-8' }); - console.log('Output:', realResult); - - // Test with just-bash - console.log('=== JUST-BASH: VAR=$(true | cut -d: -f1) ==='); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); - - const result = await bash.exec(`bash -c 'VAR=$(true | cut -d: -f1); echo "VAR=[\${VAR}]"'`); - console.log('Output:', result.stdout); - console.log('Stderr:', result.stderr); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-compare-methods.mjs b/test-compare-methods.mjs deleted file mode 100644 index 19ed2296..00000000 --- a/test-compare-methods.mjs +++ /dev/null @@ -1,65 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import fs from 'fs'; - -async function testDirect() { - console.log('=== TEST 1: Direct command ==='); - const jsonData = fs.readFileSync('excelData.json', 'utf-8'); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/', TMPDIR: '/tmp' } }); - - await memFs.writeFile('/tmp/data.json', jsonData); - - const command = `DATA=$(cat /tmp/data.json) -HEADER_IDX=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | grep -n '"Source"' | tail -1 | cut -d: -f1 | awk '{print $1 - 1}') -START_IDX=$((HEADER_IDX + 1)) -STOP_LINE=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) -if [ -n "$STOP_LINE" ]; then - COUNT=$((STOP_LINE - 1)) -else - TOTAL=$(echo "$DATA" | jq '.[0].sheets[0].rows | length') - COUNT=$((TOTAL - START_IDX)) -fi -echo "{\\"h\\": $HEADER_IDX, \\"s\\": $START_IDX, \\"n\\": $COUNT}"`; - - const result = await bash.exec(command); - console.log('Exit code:', result.exitCode); - console.log('Stdout:', result.stdout); - console.log('Stderr:', result.stderr); -} - -async function testViaBashC() { - console.log('\n=== TEST 2: Via bash -c (like original) ==='); - const jsonData = fs.readFileSync('excelData.json', 'utf-8'); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/', TMPDIR: '/tmp' } }); - - await memFs.writeFile('/tmp/stdin_data', jsonData); - - const innerCommand = `DATA=$(cat) -HEADER_IDX=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | grep -n '"Source"' | tail -1 | cut -d: -f1 | awk '{print $1 - 1}') -START_IDX=$((HEADER_IDX + 1)) -STOP_LINE=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) -if [ -n "$STOP_LINE" ]; then - COUNT=$((STOP_LINE - 1)) -else - TOTAL=$(echo "$DATA" | jq '.[0].sheets[0].rows | length') - COUNT=$((TOTAL - START_IDX)) -fi -echo "{\\"h\\": $HEADER_IDX, \\"s\\": $START_IDX, \\"n\\": $COUNT}"`; - - const fullCommand = `cat /tmp/stdin_data | bash -c '${innerCommand.replace(/'/g, "'\\''")}'`; - - const result = await bash.exec(fullCommand); - console.log('Exit code:', result.exitCode); - console.log('Stdout:', result.stdout); - console.log('Stderr:', result.stderr); -} - -async function run() { - await testDirect(); - await testViaBashC(); -} - -run().catch(console.error); - -// Made with Bob diff --git a/test-cut-empty-stdin.mjs b/test-cut-empty-stdin.mjs deleted file mode 100644 index 2b2931aa..00000000 --- a/test-cut-empty-stdin.mjs +++ /dev/null @@ -1,24 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import { execSync } from 'child_process'; - -async function test() { - // Test with real bash - truly empty input to cut - console.log('=== REAL BASH: true | cut -d: -f1 ==='); - const realResult = execSync(`true | cut -d: -f1`, { encoding: 'utf-8' }); - console.log('Output:', JSON.stringify(realResult)); - console.log('Hex:', Buffer.from(realResult).toString('hex')); - - // Test with just-bash - console.log('\n=== JUST-BASH: true | cut -d: -f1 ==='); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); - - const result = await bash.exec(`true | cut -d: -f1`); - console.log('Output:', JSON.stringify(result.stdout)); - console.log('Hex:', Buffer.from(result.stdout).toString('hex')); - console.log('Exit code:', result.exitCode); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-echo-variable.mjs b/test-echo-variable.mjs deleted file mode 100644 index 06c80da3..00000000 --- a/test-echo-variable.mjs +++ /dev/null @@ -1,25 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import fs from 'fs'; -import { execSync } from 'child_process'; - -async function test() { - const jsonData = fs.readFileSync('excelData.json', 'utf-8'); - const jqOutput = execSync(`cat excelData.json | jq -c ".[0].sheets[0].rows[]"`, { encoding: 'utf-8' }); - - // Test with real bash - echo variable into pipeline - console.log('=== REAL BASH: echo "$VAR" | tail | grep | head ==='); - const realResult = execSync(`bash -c 'DATA="${jqOutput.replace(/"/g, '\\"').replace(/\$/g, '\\$')}"; VAR=$(echo "$DATA" | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1); echo "VAR=[$VAR]"'`, { encoding: 'utf-8' }); - console.log(realResult); - - // Test with just-bash - console.log('=== JUST-BASH: echo "$VAR" | tail | grep | head ==='); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); - - const result = await bash.exec(`bash -c 'DATA="${jqOutput.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\$/g, '\\$')}"; VAR=$(echo "$DATA" | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1); echo "VAR=[\${VAR}]"'`); - console.log(result.stdout); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-eliminate-jq.mjs b/test-eliminate-jq.mjs deleted file mode 100644 index 6ff5bad4..00000000 --- a/test-eliminate-jq.mjs +++ /dev/null @@ -1,33 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import fs from 'fs'; -import { execSync } from 'child_process'; - -async function test() { - const jsonData = fs.readFileSync('excelData.json', 'utf-8'); - - // First, get the jq output - console.log('=== Step 1: Get jq output ==='); - const jqOutput = execSync(`cat excelData.json | jq -c ".[0].sheets[0].rows[]"`, { encoding: 'utf-8' }); - console.log('Lines:', jqOutput.split('\n').length); - fs.writeFileSync('jq-output.txt', jqOutput); - - // Test with real bash using the jq output directly - console.log('\n=== REAL BASH: cat jq-output | tail | grep | head ==='); - const realResult = execSync(`bash -c 'VAR=$(cat jq-output.txt | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1); echo "VAR=[$VAR]"'`, { encoding: 'utf-8' }); - console.log(realResult); - - // Test with just-bash - console.log('=== JUST-BASH: cat jq-output | tail | grep | head ==='); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); - await memFs.writeFile('/jq-output.txt', jqOutput); - - const result = await bash.exec(`bash -c 'VAR=$(cat /jq-output.txt | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1); echo "VAR=[\${VAR}]"'`); - console.log(result.stdout); - - fs.unlinkSync('jq-output.txt'); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-excel-minimal.mjs b/test-excel-minimal.mjs deleted file mode 100644 index 3dd6cc06..00000000 --- a/test-excel-minimal.mjs +++ /dev/null @@ -1,24 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import fs from 'fs'; -import { execSync } from 'child_process'; - -async function test() { - const jsonData = fs.readFileSync('excelData.json', 'utf-8'); - - // Simplest possible test with the actual data - console.log('=== REAL BASH: DATA=$(cat) then echo "$DATA" | jq | tail +43 | grep | head ==='); - const realResult = execSync(`cat excelData.json | bash -c 'DATA=$(cat); VAR=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1); echo "VAR=[$VAR]"'`, { encoding: 'utf-8' }); - console.log(realResult); - - console.log('=== JUST-BASH: same command ==='); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); - await memFs.writeFile('/data.json', jsonData); - - const result = await bash.exec(`cat /data.json | bash -c 'DATA=$(cat); VAR=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1); echo "VAR=[\${VAR}]"'`); - console.log(result.stdout); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-final-minimal.mjs b/test-final-minimal.mjs deleted file mode 100644 index 44382a4e..00000000 --- a/test-final-minimal.mjs +++ /dev/null @@ -1,22 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import { execSync } from 'child_process'; - -async function test() { - const testData = 'hello world'; - - // Real bash - outputs: VAR=[hello world] - console.log('=== REAL BASH ==='); - const cmd = 'DATA=$(cat); VAR=$(echo "$DATA"); echo "VAR=[$VAR]"'; - const realResult = execSync(`echo "${testData}" | bash -c '${cmd}'`, { encoding: 'utf-8' }); - console.log(realResult); - - // Just-bash - outputs: (empty) - console.log('=== JUST-BASH ==='); - const bash = new Bash({ fs: new InMemoryFs() }); - const result = await bash.exec(`echo "${testData}" | bash -c 'DATA=\\$(cat); VAR=\\$(echo "\\$DATA"); echo "VAR=[\\$VAR]"'`); - console.log(result.stdout); -} - -test(); - -// Made with Bob diff --git a/test-grep-diff.mjs b/test-grep-diff.mjs deleted file mode 100644 index fcb95e14..00000000 --- a/test-grep-diff.mjs +++ /dev/null @@ -1,31 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import fs from 'fs'; -import { execSync } from 'child_process'; - -async function test() { - const jsonData = fs.readFileSync('excelData.json', 'utf-8'); - - // Test with real bash - just the grep part - console.log('=== REAL BASH: grep output ==='); - try { - const realResult = execSync(`cat excelData.json | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | grep -in "TOTAL EXPENSES"`, { encoding: 'utf-8' }); - console.log('Output:', JSON.stringify(realResult)); - } catch (e) { - console.log('Exit code:', e.status); - console.log('Output:', JSON.stringify(e.stdout)); - } - - // Test with just-bash - console.log('\n=== JUST-BASH: grep output ==='); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/', TMPDIR: '/tmp' } }); - await memFs.writeFile('/tmp/data.json', jsonData); - - const result = await bash.exec(`cat /tmp/data.json | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | grep -in "TOTAL EXPENSES"`); - console.log('Output:', JSON.stringify(result.stdout)); - console.log('Stderr:', result.stderr); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-grep-head-cut.mjs b/test-grep-head-cut.mjs deleted file mode 100644 index 397619f0..00000000 --- a/test-grep-head-cut.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import { execSync } from 'child_process'; - -async function test() { - // Test with real bash - grep with no match | head | cut - console.log('=== REAL BASH: echo "test" | grep "nomatch" | head -1 | cut -d: -f1 ==='); - try { - const realResult = execSync(`bash -c 'VAR=$(echo "test" | grep "nomatch" | head -1 | cut -d: -f1); echo "VAR=[\${VAR}]"'`, { encoding: 'utf-8' }); - console.log('Output:', realResult); - } catch (e) { - console.log('Exit code:', e.status); - console.log('Output:', e.stdout); - } - - // Test with just-bash - console.log('\n=== JUST-BASH: echo "test" | grep "nomatch" | head -1 | cut -d: -f1 ==='); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); - - const result = await bash.exec(`bash -c 'VAR=$(echo "test" | grep "nomatch" | head -1 | cut -d: -f1); echo "VAR=[\${VAR}]"'`); - console.log('Output:', result.stdout); - console.log('Stderr:', result.stderr); - console.log('Exit code:', result.exitCode); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-head-cut-empty.mjs b/test-head-cut-empty.mjs deleted file mode 100644 index 9d736968..00000000 --- a/test-head-cut-empty.mjs +++ /dev/null @@ -1,24 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import { execSync } from 'child_process'; - -async function test() { - // Test with real bash - empty input to head | cut - console.log('=== REAL BASH: empty | head -1 | cut -d: -f1 ==='); - const realResult = execSync(`echo -n "" | head -1 | cut -d: -f1`, { encoding: 'utf-8' }); - console.log('Output:', JSON.stringify(realResult)); - console.log('Length:', realResult.length); - - // Test with just-bash - console.log('\n=== JUST-BASH: empty | head -1 | cut -d: -f1 ==='); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); - - const result = await bash.exec(`echo -n "" | head -1 | cut -d: -f1`); - console.log('Output:', JSON.stringify(result.stdout)); - console.log('Length:', result.stdout.length); - console.log('Exit code:', result.exitCode); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-jq-grep-head.mjs b/test-jq-grep-head.mjs deleted file mode 100644 index a0c6bdfe..00000000 --- a/test-jq-grep-head.mjs +++ /dev/null @@ -1,23 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import { execSync } from 'child_process'; - -async function test() { - const simpleJson = '[{"a":1},{"b":2}]'; - - // Test with real bash - console.log('=== REAL BASH: jq | grep nomatch | head ==='); - const realResult = execSync(`bash -c 'VAR=$(echo '${simpleJson}' | jq -c ".[]" | grep "nomatch" | head -1); echo "VAR=[$VAR]"'`, { encoding: 'utf-8' }); - console.log(realResult); - - // Test with just-bash - console.log('=== JUST-BASH: jq | grep nomatch | head ==='); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); - - const result = await bash.exec(`bash -c 'VAR=$(echo '${simpleJson}' | jq -c ".[]" | grep "nomatch" | head -1); echo "VAR=[\${VAR}]"'`); - console.log(result.stdout); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-jq-simple.mjs b/test-jq-simple.mjs deleted file mode 100644 index 6a8493bd..00000000 --- a/test-jq-simple.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import { execSync } from 'child_process'; -import fs from 'fs'; - -async function test() { - const simpleJson = '[{"a":1},{"b":2}]'; - fs.writeFileSync('simple.json', simpleJson); - - // Test with real bash - console.log('=== REAL BASH: jq | grep nomatch | head ==='); - const realResult = execSync(`bash -c 'VAR=$(cat simple.json | jq -c ".[]" | grep "nomatch" | head -1); echo "VAR=[$VAR]"'`, { encoding: 'utf-8' }); - console.log(realResult); - - // Test with just-bash - console.log('=== JUST-BASH: jq | grep nomatch | head ==='); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); - await memFs.writeFile('/simple.json', simpleJson); - - const result = await bash.exec(`bash -c 'VAR=$(cat /simple.json | jq -c ".[]" | grep "nomatch" | head -1); echo "VAR=[\${VAR}]"'`); - console.log(result.stdout); - - fs.unlinkSync('simple.json'); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-jq-tail-grep.mjs b/test-jq-tail-grep.mjs deleted file mode 100644 index 414ce5b9..00000000 --- a/test-jq-tail-grep.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import { execSync } from 'child_process'; -import fs from 'fs'; - -async function test() { - const simpleJson = '[{"a":1},{"b":2},{"c":3}]'; - fs.writeFileSync('simple.json', simpleJson); - - // Test with real bash - console.log('=== REAL BASH: jq | tail | grep nomatch | head ==='); - const realResult = execSync(`bash -c 'VAR=$(cat simple.json | jq -c ".[]" | tail -n +2 | grep "nomatch" | head -1); echo "VAR=[$VAR]"'`, { encoding: 'utf-8' }); - console.log(realResult); - - // Test with just-bash - console.log('=== JUST-BASH: jq | tail | grep nomatch | head ==='); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); - await memFs.writeFile('/simple.json', simpleJson); - - const result = await bash.exec(`bash -c 'VAR=$(cat /simple.json | jq -c ".[]" | tail -n +2 | grep "nomatch" | head -1); echo "VAR=[\${VAR}]"'`); - console.log(result.stdout); - - fs.unlinkSync('simple.json'); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-minimal-error.mjs b/test-minimal-error.mjs deleted file mode 100644 index 98e6671d..00000000 --- a/test-minimal-error.mjs +++ /dev/null @@ -1,35 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import fs from 'fs'; - -async function test() { - const jsonData = fs.readFileSync('excelData.json', 'utf-8'); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/', TMPDIR: '/tmp' } }); - - await memFs.writeFile('/tmp/data.json', jsonData); - - // Simplified version - just the part that fails - const command = ` -DATA=$(cat /tmp/data.json) -HEADER_IDX=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | grep -n '"Source"' | tail -1 | cut -d: -f1 | awk '{print $1 - 1}') -START_IDX=$((HEADER_IDX + 1)) -STOP_LINE=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) -echo "STOP_LINE=[$STOP_LINE]" -if [ -n "$STOP_LINE" ]; then - COUNT=$((STOP_LINE - 1)) -else - TOTAL=$(echo "$DATA" | jq '.[0].sheets[0].rows | length') - COUNT=$((TOTAL - START_IDX)) -fi -echo "COUNT=[$COUNT]" -`; - - const result = await bash.exec(command); - console.log('Exit code:', result.exitCode); - console.log('Stdout:\n', result.stdout); - console.log('Stderr:\n', result.stderr); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-narrow-error.mjs b/test-narrow-error.mjs deleted file mode 100644 index a25f1a78..00000000 --- a/test-narrow-error.mjs +++ /dev/null @@ -1,23 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; - -async function test() { - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); - - // Minimal test case - just the [ -n "$VAR" ] test via bash -c - console.log('=== Test 1: Empty variable in [ -n ] via bash -c ==='); - let result = await bash.exec(`bash -c 'VAR=""; if [ -n "$VAR" ]; then echo "not empty"; else echo "empty"; fi'`); - console.log('Exit:', result.exitCode, 'Out:', result.stdout.trim(), 'Err:', result.stderr.trim()); - - console.log('\n=== Test 2: Variable with brackets in [ -n ] via bash -c ==='); - result = await bash.exec(`bash -c 'VAR="["; if [ -n "$VAR" ]; then echo "not empty"; else echo "empty"; fi'`); - console.log('Exit:', result.exitCode, 'Out:', result.stdout.trim(), 'Err:', result.stderr.trim()); - - console.log('\n=== Test 3: Direct (no bash -c) ==='); - result = await bash.exec(`VAR="["; if [ -n "$VAR" ]; then echo "not empty"; else echo "empty"; fi`); - console.log('Exit:', result.exitCode, 'Out:', result.stdout.trim(), 'Err:', result.stderr.trim()); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-narrow-pipeline.mjs b/test-narrow-pipeline.mjs deleted file mode 100644 index ccebdad6..00000000 --- a/test-narrow-pipeline.mjs +++ /dev/null @@ -1,28 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import fs from 'fs'; - -async function test() { - const jsonData = fs.readFileSync('excelData.json', 'utf-8'); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/', TMPDIR: '/tmp' } }); - await memFs.writeFile('/tmp/data.json', jsonData); - - // Test without cut - console.log('=== Without cut (just grep | head) ==='); - let result = await bash.exec(`cat /tmp/data.json | bash -c 'DATA=$(cat); VAR=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1); echo "VAR=[\${VAR}]"'`); - console.log(result.stdout); - - // Test without head - console.log('=== Without head (just grep | cut) ==='); - result = await bash.exec(`cat /tmp/data.json | bash -c 'DATA=$(cat); VAR=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | grep -in "TOTAL EXPENSES" | cut -d: -f1); echo "VAR=[\${VAR}]"'`); - console.log(result.stdout); - - // Test without grep - console.log('=== Without grep (just tail | head | cut) ==='); - result = await bash.exec(`cat /tmp/data.json | bash -c 'DATA=$(cat); VAR=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | head -1 | cut -d: -f1); echo "VAR=[\${VAR}]"'`); - console.log(result.stdout); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-nested-substitution.mjs b/test-nested-substitution.mjs deleted file mode 100644 index bc618cdf..00000000 --- a/test-nested-substitution.mjs +++ /dev/null @@ -1,26 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import fs from 'fs'; -import { execSync } from 'child_process'; - -async function test() { - const jsonData = fs.readFileSync('excelData.json', 'utf-8'); - - // Test with real bash - nested command substitution - console.log('=== REAL BASH: DATA=$(cat) then echo "$DATA" | jq ==='); - const realResult = execSync(`cat excelData.json | bash -c 'DATA=$(cat); VAR=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1); echo "VAR=[$VAR]"'`, { encoding: 'utf-8' }); - console.log(realResult); - - // Test with just-bash - console.log('=== JUST-BASH: DATA=$(cat) then echo "$DATA" | jq ==='); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); - await memFs.writeFile('/data.json', jsonData); - - const result = await bash.exec(`cat /data.json | bash -c 'DATA=$(cat); VAR=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1); echo "VAR=[\${VAR}]"'`); - console.log(result.stdout); - console.log('Stderr:', result.stderr); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-pipeline-debug.mjs b/test-pipeline-debug.mjs deleted file mode 100644 index 4206d2d3..00000000 --- a/test-pipeline-debug.mjs +++ /dev/null @@ -1,39 +0,0 @@ -import { Bash, InMemoryFs } from './dist/index.js'; -import fs from 'fs'; - -async function test() { - const jsonData = fs.readFileSync('excelData.json', 'utf-8'); - - // Test just the problematic pipeline step by step - const command = `DATA=$(cat) -echo "=== Step 1: jq output (first 5 lines) ===" >&2 -echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | head -5 >&2 -echo "" >&2 -echo "=== Step 2: after tail -n +43 (first 5 lines) ===" >&2 -echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | head -5 >&2 -echo "" >&2 -echo "=== Step 3: grep -in TOTAL EXPENSES ===" >&2 -echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | grep -in "TOTAL EXPENSES" >&2 -echo "" >&2 -echo "=== Step 4: after head -1 ===" >&2 -echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1 >&2 -echo "" >&2 -echo "=== Step 5: after cut -d: -f1 ===" >&2 -RESULT=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) -echo "RESULT=[$RESULT]" >&2`; - - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs }); - await memFs.writeFile('/tmp/data.json', jsonData); - - const fullCommand = `cat /tmp/data.json | bash -c '${command.replace(/'/g, "'\\''")}'`; - - const result = await bash.exec(fullCommand); - - console.log('STDERR:'); - console.log(result.stderr); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-pipeline-steps.mjs b/test-pipeline-steps.mjs deleted file mode 100644 index 1276e835..00000000 --- a/test-pipeline-steps.mjs +++ /dev/null @@ -1,37 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import fs from 'fs'; - -async function test() { - const jsonData = fs.readFileSync('excelData.json', 'utf-8'); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/', TMPDIR: '/tmp' } }); - - await memFs.writeFile('/tmp/data.json', jsonData); - - // Step 1: Get rows after START_IDX - console.log('=== Step 1: jq rows | tail -n +43 ==='); - let result = await bash.exec(`cat /tmp/data.json | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | head -5`); - console.log('Output:', result.stdout); - - // Step 2: grep for TOTAL EXPENSES - console.log('\n=== Step 2: grep -in "TOTAL EXPENSES" ==='); - result = await bash.exec(`cat /tmp/data.json | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | grep -in "TOTAL EXPENSES"`); - console.log('Output:', result.stdout); - console.log('Stderr:', result.stderr); - - // Step 3: head -1 - console.log('\n=== Step 3: head -1 ==='); - result = await bash.exec(`cat /tmp/data.json | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1`); - console.log('Output:', result.stdout); - console.log('Stderr:', result.stderr); - - // Step 4: cut -d: -f1 - console.log('\n=== Step 4: cut -d: -f1 ==='); - result = await bash.exec(`cat /tmp/data.json | jq -c '.[0].sheets[0].rows[]' | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1`); - console.log('Output:', result.stdout); - console.log('Stderr:', result.stderr); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-script.sh b/test-script.sh deleted file mode 100644 index 25706f50..00000000 --- a/test-script.sh +++ /dev/null @@ -1,11 +0,0 @@ -DATA=$(cat) -HEADER_IDX=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | grep -n '"Source"' | tail -1 | cut -d: -f1 | awk '{print $1 - 1}') -START_IDX=$((HEADER_IDX + 1)) -STOP_LINE=$(echo "$DATA" | jq -c '.[0].sheets[0].rows[]' | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) -if [ -n "$STOP_LINE" ]; then - COUNT=$((STOP_LINE - 1)) -else - TOTAL=$(echo "$DATA" | jq '.[0].sheets[0].rows | length') - COUNT=$((TOTAL - START_IDX)) -fi -echo "{\"h\": $HEADER_IDX, \"s\": $START_IDX, \"n\": $COUNT}" \ No newline at end of file diff --git a/test-simple-diff.mjs b/test-simple-diff.mjs deleted file mode 100644 index 86ac33f0..00000000 --- a/test-simple-diff.mjs +++ /dev/null @@ -1,35 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import fs from 'fs'; -import { execSync } from 'child_process'; - -async function test() { - const jsonData = fs.readFileSync('excelData.json', 'utf-8'); - - // Test with real bash - console.log('=== REAL BASH ==='); - const realResult = execSync(`cat excelData.json | bash -c 'DATA=$(cat) -HEADER_IDX=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | grep -n "\\"Source\\"" | tail -1 | cut -d: -f1 | awk "{print \\$1 - 1}") -START_IDX=$((HEADER_IDX + 1)) -STOP_LINE=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) -echo "STOP_LINE=[$STOP_LINE]"'`, { encoding: 'utf-8' }); - console.log(realResult); - - // Test with just-bash - console.log('=== JUST-BASH ==='); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/', TMPDIR: '/tmp' } }); - await memFs.writeFile('/tmp/stdin_data', jsonData); - - const result = await bash.exec(`cat /tmp/stdin_data | bash -c 'DATA=$(cat) -HEADER_IDX=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | grep -n "\\"Source\\"" | tail -1 | cut -d: -f1 | awk "{print \\$1 - 1}") -START_IDX=$((HEADER_IDX + 1)) -STOP_LINE=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) -echo "STOP_LINE=[\${STOP_LINE}]"'`); - - console.log(result.stdout); - if (result.stderr) console.log('Stderr:', result.stderr); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-simplest-bug.mjs b/test-simplest-bug.mjs deleted file mode 100644 index 8b8da070..00000000 --- a/test-simplest-bug.mjs +++ /dev/null @@ -1,39 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import fs from 'fs'; -import { execSync } from 'child_process'; - -async function testCommand(desc, innerPipeline) { - const jsonData = fs.readFileSync('excelData.json', 'utf-8'); - - const cmd = `DATA=$(cat); VAR=$(${innerPipeline}); echo "VAR=[$VAR]"`; - const realResult = execSync(`cat excelData.json | bash -c '${cmd}'`, { encoding: 'utf-8' }); - - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); - await memFs.writeFile('/data.json', jsonData); - - const result = await bash.exec(`cat /data.json | bash -c '${cmd.replace(/\$/g, '\\$')}'`); - - const match = realResult.trim() === result.stdout.trim(); - console.log(`${match ? '✓' : '✗'} ${desc}`); - if (!match) { - console.log(` Real: ${JSON.stringify(realResult.trim().substring(0, 50))}`); - console.log(` Just: ${JSON.stringify(result.stdout.trim().substring(0, 50))}`); - } -} - -async function test() { - console.log('Testing simpler cases:\n'); - - await testCommand('echo "$DATA" | jq', 'echo "$DATA" | jq -c ".[0].sheets[0].rows[]"'); - - await testCommand('echo "$DATA" | head', 'echo "$DATA" | head -1'); - - await testCommand('echo "$DATA"', 'echo "$DATA"'); - - await testCommand('cat (no echo)', 'cat'); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-simplest.mjs b/test-simplest.mjs deleted file mode 100644 index 51a1a909..00000000 --- a/test-simplest.mjs +++ /dev/null @@ -1,25 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import { execSync } from 'child_process'; - -async function test() { - // Test with simple data - grep no match | head - console.log('=== REAL BASH: echo "[test]" | grep "nomatch" | head -1 ==='); - try { - const realResult = execSync(`bash -c 'VAR=$(echo "[test]" | grep "nomatch" | head -1); echo "VAR=[$VAR]"'`, { encoding: 'utf-8' }); - console.log(realResult); - } catch (e) { - console.log('Exit:', e.status, 'Output:', e.stdout); - } - - console.log('=== JUST-BASH: echo "[test]" | grep "nomatch" | head -1 ==='); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); - - const result = await bash.exec(`bash -c 'VAR=$(echo "[test]" | grep "nomatch" | head -1); echo "VAR=[\${VAR}]"'`); - console.log(result.stdout); - console.log('Exit:', result.exitCode); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-stdin-passthrough.mjs b/test-stdin-passthrough.mjs deleted file mode 100644 index 681f08a1..00000000 --- a/test-stdin-passthrough.mjs +++ /dev/null @@ -1,34 +0,0 @@ -import { Bash, InMemoryFs } from './dist/index.js'; -import { execSync } from 'child_process'; -import fs from 'fs'; - -async function test() { - // Simple script that just reads stdin - const scriptContent = 'cat'; - fs.writeFileSync('test-stdin.sh', scriptContent); - - const testData = 'hello world'; - const fullCommand = `echo "${testData}" | bash test-stdin.sh`; - - // Test with real bash - console.log('=== REAL BASH ==='); - console.log('Command:', fullCommand); - const realResult = execSync(fullCommand, { encoding: 'utf-8' }); - console.log('stdout:', JSON.stringify(realResult)); - - // Test with just-bash - console.log('\n=== JUST-BASH ==='); - console.log('Command:', fullCommand); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs }); - await memFs.writeFile('test-stdin.sh', scriptContent); - - const result = await bash.exec(fullCommand); - console.log('stdout:', JSON.stringify(result.stdout)); - console.log('stderr:', JSON.stringify(result.stderr)); - console.log('exit code:', result.exitCode); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-stdin-simple.mjs b/test-stdin-simple.mjs deleted file mode 100644 index 76eb1292..00000000 --- a/test-stdin-simple.mjs +++ /dev/null @@ -1,36 +0,0 @@ -import { Bash, InMemoryFs } from './dist/index.js'; - -async function test() { - console.log('=== Test 1: Without leading slash ==='); - const memFs1 = new InMemoryFs(); - const bash1 = new Bash({ fs: memFs1 }); - await memFs1.writeFile('test.sh', 'cat'); - const result1 = await bash1.exec('echo "hello" | bash test.sh'); - console.log('stdout:', JSON.stringify(result1.stdout)); - console.log('stderr:', JSON.stringify(result1.stderr)); - console.log('exitCode:', result1.exitCode); - - console.log('\n=== Test 2: With leading slash ==='); - const memFs2 = new InMemoryFs(); - const bash2 = new Bash({ fs: memFs2 }); - await memFs2.writeFile('/test.sh', 'cat'); - const result2 = await bash2.exec('echo "hello" | bash /test.sh'); - console.log('stdout:', JSON.stringify(result2.stdout)); - console.log('stderr:', JSON.stringify(result2.stderr)); - console.log('exitCode:', result2.exitCode); - - console.log('\n=== Test 3: Using files option ==='); - const bash3 = new Bash({ - files: { - '/test.sh': 'cat' - } - }); - const result3 = await bash3.exec('echo "hello" | bash /test.sh'); - console.log('stdout:', JSON.stringify(result3.stdout)); - console.log('stderr:', JSON.stringify(result3.stderr)); - console.log('exitCode:', result3.exitCode); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-stdin.sh b/test-stdin.sh deleted file mode 100644 index 5d5b306e..00000000 --- a/test-stdin.sh +++ /dev/null @@ -1 +0,0 @@ -cat \ No newline at end of file diff --git a/test-stopline-value.mjs b/test-stopline-value.mjs deleted file mode 100644 index f3f59b9e..00000000 --- a/test-stopline-value.mjs +++ /dev/null @@ -1,45 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import fs from 'fs'; - -async function test() { - const jsonData = fs.readFileSync('excelData.json', 'utf-8'); - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/', TMPDIR: '/tmp' } }); - - await memFs.writeFile('/tmp/data.json', jsonData); - - // Get the actual STOP_LINE value via bash -c - console.log('=== Getting STOP_LINE value via bash -c ==='); - const cmd1 = `cat /tmp/data.json | bash -c 'DATA=$(cat) -HEADER_IDX=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | grep -n "\\"Source\\"" | tail -1 | cut -d: -f1 | awk "{print \\$1 - 1}") -START_IDX=$((HEADER_IDX + 1)) -STOP_LINE=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) -echo "STOP_LINE=[\${STOP_LINE}]" -printf "STOP_LINE_HEX="; printf "%s" "\$STOP_LINE" | od -A n -t x1'`; - - let result = await bash.exec(cmd1); - console.log('Exit:', result.exitCode); - console.log('Stdout:', result.stdout); - console.log('Stderr:', result.stderr); - - // Now test the [ -n ] with that value - console.log('\n=== Testing [ -n "$STOP_LINE" ] via bash -c ==='); - const cmd2 = `cat /tmp/data.json | bash -c 'DATA=$(cat) -HEADER_IDX=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | grep -n "\\"Source\\"" | tail -1 | cut -d: -f1 | awk "{print \\$1 - 1}") -START_IDX=$((HEADER_IDX + 1)) -STOP_LINE=$(echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +$((START_IDX + 1)) | grep -in "TOTAL EXPENSES" | head -1 | cut -d: -f1) -if [ -n "$STOP_LINE" ]; then - echo "STOP_LINE is not empty: [$STOP_LINE]" -else - echo "STOP_LINE is empty" -fi'`; - - result = await bash.exec(cmd2); - console.log('Exit:', result.exitCode); - console.log('Stdout:', result.stdout); - console.log('Stderr:', result.stderr); -} - -test().catch(console.error); - -// Made with Bob diff --git a/test-work-backwards.mjs b/test-work-backwards.mjs deleted file mode 100644 index 9f8e1599..00000000 --- a/test-work-backwards.mjs +++ /dev/null @@ -1,43 +0,0 @@ -import { Bash, InMemoryFs } from 'just-bash'; -import fs from 'fs'; -import { execSync } from 'child_process'; - -async function testCommand(desc, innerPipeline) { - const jsonData = fs.readFileSync('excelData.json', 'utf-8'); - - const cmd = `DATA=$(cat); VAR=$(${innerPipeline}); echo "VAR=[$VAR]"`; - const realResult = execSync(`cat excelData.json | bash -c '${cmd}'`, { encoding: 'utf-8' }); - - const memFs = new InMemoryFs(); - const bash = new Bash({ fs: memFs, cwd: '/', env: { HOME: '/' } }); - await memFs.writeFile('/data.json', jsonData); - - const result = await bash.exec(`cat /data.json | bash -c '${cmd.replace(/\$/g, '\\$')}'`); - - const match = realResult.trim() === result.stdout.trim(); - console.log(`${match ? '✓' : '✗'} ${desc}`); - if (!match) { - console.log(` Real: ${JSON.stringify(realResult.trim())}`); - console.log(` Just: ${JSON.stringify(result.stdout.trim())}`); - } -} - -async function test() { - console.log('Working backwards from the inner pipeline:\n'); - - await testCommand('Full: jq | tail | grep | head', 'echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | grep -in "TOTAL EXPENSES" | head -1'); - - await testCommand('Remove head', 'echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | grep -in "TOTAL EXPENSES"'); - - await testCommand('Remove grep', 'echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | tail -n +43 | head -1'); - - await testCommand('Remove tail', 'echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | grep -in "TOTAL EXPENSES" | head -1'); - - await testCommand('Just jq | head', 'echo "$DATA" | jq -c ".[0].sheets[0].rows[]" | head -1'); - - await testCommand('Just jq', 'echo "$DATA" | jq -c ".[0].sheets[0].rows[]"'); -} - -test().catch(console.error); - -// Made with Bob From 4e1424724b2ea0248408d43f4eb70109b9ff2297 Mon Sep 17 00:00:00 2001 From: Robert Yates Date: Mon, 9 Feb 2026 17:12:26 -0500 Subject: [PATCH 3/4] remove more tests and test data --- debug-nested-bash.mjs | 21 - excelData.json | 955 ------------------------------------------ 2 files changed, 976 deletions(-) delete mode 100644 debug-nested-bash.mjs delete mode 100644 excelData.json diff --git a/debug-nested-bash.mjs b/debug-nested-bash.mjs deleted file mode 100644 index 9a242944..00000000 --- a/debug-nested-bash.mjs +++ /dev/null @@ -1,21 +0,0 @@ -import { Bash, InMemoryFs } from './dist/index.js'; - -async function test() { - const env = new Bash({ fs: new InMemoryFs() }); - - console.log('=== Test 1: Direct test (like in test-final-minimal.mjs) ==='); - const result1 = await env.exec('echo "hello world" | bash -c \'DATA=$(cat); VAR=$(echo "$DATA"); echo "VAR=[$VAR]"\''); - console.log('stdout:', JSON.stringify(result1.stdout)); - console.log('stderr:', JSON.stringify(result1.stderr)); - console.log('exitCode:', result1.exitCode); - - console.log('\n=== Test 2: With escaped $ (like in test-final-minimal.mjs) ==='); - const result2 = await env.exec('echo "hello world" | bash -c \'DATA=\\$(cat); VAR=\\$(echo "\\$DATA"); echo "VAR=[\\$VAR]"\''); - console.log('stdout:', JSON.stringify(result2.stdout)); - console.log('stderr:', JSON.stringify(result2.stderr)); - console.log('exitCode:', result2.exitCode); -} - -test(); - -// Made with Bob diff --git a/excelData.json b/excelData.json deleted file mode 100644 index 680d94c3..00000000 --- a/excelData.json +++ /dev/null @@ -1,955 +0,0 @@ -[ - { - "sheets": [ - { - "name": "Budget Overview", - "rows": [ - [ - "INCOME", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "" - ], - [ - "Source", - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - "TOTAL" - ], - [ - "Salary", - 5200, - 5200, - 5200, - 5400, - 5400, - 5400, - 5400, - 5400, - 5600, - 5600, - 5600, - 5600, - 65000 - ], - [ - "Bonus", - 0, - 0, - 2500, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 3000, - 5500 - ], - [ - "Freelance", - 800, - 1200, - 650, - 900, - 1100, - 750, - 1300, - 950, - 1150, - 800, - 1050, - 1400, - 12050 - ], - [ - "Investments", - 350, - 380, - 420, - 390, - 410, - 450, - 480, - 520, - 490, - 510, - 530, - 580, - 5510 - ], - [ - "Dividends", - 0, - 0, - 125, - 0, - 0, - 140, - 0, - 0, - 155, - 0, - 0, - 170, - 590 - ], - [ - "Rental Income", - 1500, - 1500, - 1500, - 1500, - 1550, - 1550, - 1550, - 1550, - 1600, - 1600, - 1600, - 1600, - 18600 - ], - [ - "Side Business", - 200, - 350, - 275, - 400, - 450, - 380, - 520, - 475, - 390, - 440, - 510, - 620, - 5010 - ], - [ - "Cash Back/Rewards", - 45, - 62, - 38, - 55, - 71, - 48, - 59, - 67, - 52, - 63, - 58, - 85, - 703 - ], - [ - "Tax Refund", - 0, - 0, - 0, - 1850, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 1850 - ], - [ - "Other", - 150, - 75, - 200, - 100, - 125, - 180, - 90, - 140, - 160, - 110, - 130, - 250, - 1710 - ], - [ - "TOTAL INCOME", - 8245, - 8767, - 10908, - 10595, - 9106, - 8898, - 9399, - 9102, - 9597, - 9123, - 9478, - 13305, - 116523 - ], - [ - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "" - ], - [ - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "" - ], - [ - "EXPENSES", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "" - ], - [ - "Source", - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - "TOTAL" - ], - [ - "Rent/Mortgage", - 1800, - 1800, - 1800, - 1800, - 1800, - 1800, - 1850, - 1850, - 1850, - 1850, - 1850, - 1850, - 21900 - ], - [ - "Utilities", - 220, - 195, - 180, - 165, - 145, - 160, - 185, - 210, - 175, - 155, - 190, - 230, - 2210 - ], - [ - "Internet/Phone", - 125, - 125, - 125, - 125, - 125, - 130, - 130, - 130, - 130, - 130, - 130, - 130, - 1535 - ], - [ - "Groceries", - 650, - 720, - 680, - 710, - 690, - 740, - 720, - 750, - 695, - 730, - 760, - 820, - 8665 - ], - [ - "Dining Out", - 280, - 320, - 350, - 290, - 380, - 420, - 450, - 390, - 340, - 310, - 370, - 480, - 4380 - ], - [ - "Transportation", - 350, - 400, - 320, - 380, - 360, - 340, - 390, - 420, - 370, - 350, - 380, - 410, - 4470 - ], - [ - "Gas", - 180, - 165, - 175, - 190, - 200, - 210, - 220, - 195, - 185, - 170, - 175, - 190, - 2255 - ], - [ - "Insurance", - 450, - 450, - 450, - 450, - 450, - 450, - 450, - 450, - 470, - 470, - 470, - 470, - 5480 - ], - [ - "Healthcare", - 150, - 85, - 200, - 120, - 95, - 180, - 110, - 250, - 140, - 90, - 165, - 130, - 1715 - ], - [ - "Entertainment", - 200, - 250, - 180, - 220, - 280, - 350, - 320, - 290, - 240, - 210, - 260, - 380, - 3180 - ], - [ - "Subscriptions", - 85, - 85, - 85, - 95, - 95, - 95, - 95, - 95, - 95, - 105, - 105, - 105, - 1140 - ], - [ - "Shopping", - 150, - 280, - 120, - 200, - 175, - 320, - 250, - 180, - 220, - 160, - 350, - 450, - 2855 - ], - [ - "Personal Care", - 60, - 45, - 80, - 55, - 70, - 65, - 75, - 50, - 60, - 55, - 70, - 85, - 770 - ], - [ - "Gym/Fitness", - 50, - 50, - 50, - 50, - 50, - 50, - 50, - 50, - 50, - 50, - 50, - 50, - 600 - ], - [ - "Education", - 0, - 0, - 150, - 0, - 0, - 200, - 0, - 0, - 175, - 0, - 0, - 0, - 525 - ], - [ - "Gifts", - 50, - 120, - 75, - 80, - 200, - 90, - 60, - 45, - 110, - 85, - 150, - 350, - 1415 - ], - [ - "Travel", - 0, - 0, - 0, - 800, - 0, - 1200, - 0, - 0, - 500, - 0, - 0, - 0, - 2500 - ], - [ - "Home Maintenance", - 75, - 120, - 200, - 90, - 150, - 80, - 250, - 110, - 95, - 180, - 130, - 160, - 1640 - ], - [ - "Pet Expenses", - 85, - 90, - 85, - 150, - 90, - 85, - 95, - 120, - 85, - 90, - 95, - 110, - 1180 - ], - [ - "Miscellaneous", - 100, - 75, - 130, - 85, - 110, - 95, - 120, - 80, - 105, - 90, - 115, - 140, - 1245 - ], - [ - "TOTAL EXPENSES", - 5060, - 5375, - 5435, - 6055, - 5465, - 7060, - 5820, - 5665, - 6090, - 5280, - 5815, - 6540, - 69660 - ], - [ - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "" - ], - [ - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "" - ], - [ - "MONTHLY SUMMARY", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "" - ], - [ - "Source", - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - "TOTAL" - ], - [ - "Net Savings", - 3185, - 3392, - 5473, - 4540, - 3641, - 1838, - 3579, - 3437, - 3507, - 3843, - 3663, - 6765, - 46863 - ], - [ - "Savings Rate", - 0.3862947240751971, - 0.3869054408577621, - 0.5017418408507518, - 0.4285040113260972, - 0.3998462552163409, - 0.2065632726455383, - 0.3807851899138206, - 0.3776093166337069, - 0.36542669584245074, - 0.4212430121670503, - 0.3864739396497151, - 0.5084554678692221, - 0.40217811075924925 - ], - [ - "Cumulative Savings", - 3185, - 6577, - 12050, - 16590, - 20231, - 22069, - 25648, - 29085, - 32592, - 36435, - 40098, - 46863, - 46863 - ], - [ - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "" - ], - [ - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "" - ], - [ - "ANNUAL STATISTICS", - "", - "", - "", - "", - "TOP EXPENSE CATEGORIES", - "", - "", - "", - "", - "", - "", - "", - "" - ], - [ - "Total Annual Income", - 116523, - "", - "", - "", - "Rent/Mortgage", - 21900, - "", - "", - "", - "", - "", - "", - "" - ], - [ - "Total Annual Expenses", - 69660, - "", - "", - "", - "Groceries", - 8665, - "", - "", - "", - "", - "", - "", - "" - ], - [ - "Total Annual Savings", - 46863, - "", - "", - "", - "Insurance", - 5480, - "", - "", - "", - "", - "", - "", - "" - ], - [ - "Average Monthly Income", - 9710.25, - "", - "", - "", - "Dining Out", - 4380, - "", - "", - "", - "", - "", - "", - "" - ], - [ - "Average Monthly Expenses", - 5805, - "", - "", - "", - "Transportation", - 4470, - "", - "", - "", - "", - "", - "", - "" - ], - [ - "Average Monthly Savings", - 3905.25, - "", - "", - "", - "Entertainment", - 3180, - "", - "", - "", - "", - "", - "", - "" - ], - [ - "Highest Income Month", - 13305, - "", - "", - "", - "Shopping", - 2855, - "", - "", - "", - "", - "", - "", - "" - ], - [ - "Lowest Income Month", - 8245, - "", - "", - "", - "Travel", - 2500, - "", - "", - "", - "", - "", - "", - "" - ], - [ - "Highest Expense Month", - 7060, - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "" - ], - [ - "Lowest Expense Month", - 5060, - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "" - ], - [ - "Annual Savings Rate", - 0.40217811075924925, - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "" - ] - ] - } - ] - } -] \ No newline at end of file From 2c972f1934632e149b7b3b44e7c4d66fccb5933d Mon Sep 17 00:00:00 2001 From: Robert Yates Date: Mon, 9 Feb 2026 17:20:03 -0500 Subject: [PATCH 4/4] linter ! --- src/interpreter/pipeline-execution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interpreter/pipeline-execution.ts b/src/interpreter/pipeline-execution.ts index 3d774278..599cee3c 100644 --- a/src/interpreter/pipeline-execution.ts +++ b/src/interpreter/pipeline-execution.ts @@ -50,7 +50,7 @@ export async function executePipeline( if (isMultiCommandPipeline) { // Clear $_ for each pipeline command - they each get fresh subshell context ctx.state.lastArg = ""; - + // After the first command, clear groupStdin so subsequent commands // only see stdin from the pipeline (even if empty), not the original groupStdin // This prevents commands like head from incorrectly falling back to groupStdin