From b5722bf11ce8e2da302d0bf8c5118e7360e350e0 Mon Sep 17 00:00:00 2001 From: sanish chirayath Date: Thu, 4 Dec 2025 00:23:06 +0530 Subject: [PATCH] fix: wrap script in async IIFE to create isolated scope (#6229) * fix: lexical scope for running scripts * review fixes --- packages/bruno-cli/src/utils/collection.js | 60 ++++++++++++++++-- .../bruno-electron/src/utils/collection.js | 61 +++++++++++++++++-- 2 files changed, 111 insertions(+), 10 deletions(-) diff --git a/packages/bruno-cli/src/utils/collection.js b/packages/bruno-cli/src/utils/collection.js index 6323ec3d3..72b6e5676 100644 --- a/packages/bruno-cli/src/utils/collection.js +++ b/packages/bruno-cli/src/utils/collection.js @@ -201,6 +201,23 @@ const mergeVars = (collection, request, requestTreePath) => { } }; +/** + * Wraps a script in an IIFE closure to isolate its scope + * @param {string} script - The script code to wrap + * @returns {string} The wrapped script + */ +const wrapScriptInClosure = (script) => { + if (!script || script.trim() === '') { + return ''; + } + // Wrap script in async IIFE to create isolated scope + // This prevents variable re-declaration errors and allows early returns + // to only affect the current script segment + return `await (async () => { +${script} +})();`; +}; + const mergeScripts = (collection, request, requestTreePath, scriptFlow) => { const collectionRoot = collection?.draft?.root || collection?.root || {}; let collectionPreReqScript = get(collectionRoot, 'request.script.req', ''); @@ -230,18 +247,51 @@ const mergeScripts = (collection, request, requestTreePath, scriptFlow) => { } } - request.script.req = compact([collectionPreReqScript, ...combinedPreReqScript, request?.script?.req || '']).join(os.EOL); + // Wrap each script segment in its own closure and join them + // This allows each script to run separately with its own scope, + // preventing variable re-declaration errors and allowing early returns + // to only affect that specific script segment + const preReqScripts = [ + collectionPreReqScript, + ...combinedPreReqScript, + request?.script?.req || '' + ]; + request.script.req = compact(preReqScripts.map(wrapScriptInClosure)).join(os.EOL + os.EOL); + // Handle post-response scripts based on scriptFlow if (scriptFlow === 'sequential') { - request.script.res = compact([collectionPostResScript, ...combinedPostResScript, request?.script?.res || '']).join(os.EOL); + const postResScripts = [ + collectionPostResScript, + ...combinedPostResScript, + request?.script?.res || '' + ]; + request.script.res = compact(postResScripts.map(wrapScriptInClosure)).join(os.EOL + os.EOL); } else { - request.script.res = compact([request?.script?.res || '', ...combinedPostResScript.reverse(), collectionPostResScript]).join(os.EOL); + // Reverse order for non-sequential flow + const postResScripts = [ + request?.script?.res || '', + ...[...combinedPostResScript].reverse(), + collectionPostResScript + ]; + request.script.res = compact(postResScripts.map(wrapScriptInClosure)).join(os.EOL + os.EOL); } + // Handle tests based on scriptFlow if (scriptFlow === 'sequential') { - request.tests = compact([collectionTests, ...combinedTests, request?.tests || '']).join(os.EOL); + const testScripts = [ + collectionTests, + ...combinedTests, + request?.tests || '' + ]; + request.tests = compact(testScripts.map(wrapScriptInClosure)).join(os.EOL + os.EOL); } else { - request.tests = compact([request?.tests || '', ...combinedTests.reverse(), collectionTests]).join(os.EOL); + // Reverse order for non-sequential flow + const testScripts = [ + request?.tests || '', + ...[...combinedTests].reverse(), + collectionTests + ]; + request.tests = compact(testScripts.map(wrapScriptInClosure)).join(os.EOL + os.EOL); } }; diff --git a/packages/bruno-electron/src/utils/collection.js b/packages/bruno-electron/src/utils/collection.js index e779c6906..888e63fa4 100644 --- a/packages/bruno-electron/src/utils/collection.js +++ b/packages/bruno-electron/src/utils/collection.js @@ -97,6 +97,24 @@ const mergeVars = (collection, request, requestTreePath = []) => { } }; +/** + * Wraps a script in an IIFE closure to isolate its scope + * @param {string} script - The script code to wrap + * @returns {string} The wrapped script + */ +const wrapScriptInClosure = (script) => { + if (!script || script.trim() === '') { + return ''; + } + + // Wrap script in async IIFE to create isolated scope + // This prevents variable re-declaration errors and allows early returns + // to only affect the current script segment + return `await (async () => { +${script} +})();`; +}; + const mergeScripts = (collection, request, requestTreePath, scriptFlow) => { const collectionRoot = collection?.draft?.root || collection?.root || {}; let collectionPreReqScript = get(collectionRoot, 'request.script.req', ''); @@ -126,18 +144,51 @@ const mergeScripts = (collection, request, requestTreePath, scriptFlow) => { } } - request.script.req = compact([collectionPreReqScript, ...combinedPreReqScript, request?.script?.req || '']).join(os.EOL); + // Wrap each script segment in its own closure and join them + // This allows each script to run separately with its own scope, + // preventing variable re-declaration errors and allowing early returns + // to only affect that specific script segment + const preReqScripts = [ + collectionPreReqScript, + ...combinedPreReqScript, + request?.script?.req || '' + ]; + request.script.req = compact(preReqScripts.map(wrapScriptInClosure)).join(os.EOL + os.EOL); + // Handle post-response scripts based on scriptFlow if (scriptFlow === 'sequential') { - request.script.res = compact([collectionPostResScript, ...combinedPostResScript, request?.script?.res || '']).join(os.EOL); + const postResScripts = [ + collectionPostResScript, + ...combinedPostResScript, + request?.script?.res || '' + ]; + request.script.res = compact(postResScripts.map(wrapScriptInClosure)).join(os.EOL + os.EOL); } else { - request.script.res = compact([request?.script?.res || '', ...combinedPostResScript.reverse(), collectionPostResScript]).join(os.EOL); + // Reverse order for non-sequential flow + const postResScripts = [ + request?.script?.res || '', + ...[...combinedPostResScript].reverse(), + collectionPostResScript + ]; + request.script.res = compact(postResScripts.map(wrapScriptInClosure)).join(os.EOL + os.EOL); } + // Handle tests based on scriptFlow if (scriptFlow === 'sequential') { - request.tests = compact([collectionTests, ...combinedTests, request?.tests || '']).join(os.EOL); + const testScripts = [ + collectionTests, + ...combinedTests, + request?.tests || '' + ]; + request.tests = compact(testScripts.map(wrapScriptInClosure)).join(os.EOL + os.EOL); } else { - request.tests = compact([request?.tests || '', ...combinedTests.reverse(), collectionTests]).join(os.EOL); + // Reverse order for non-sequential flow + const testScripts = [ + request?.tests || '', + ...[...combinedTests].reverse(), + collectionTests + ]; + request.tests = compact(testScripts.map(wrapScriptInClosure)).join(os.EOL + os.EOL); } };