diff --git a/packages/bruno-cli/src/runner/run-single-request.js b/packages/bruno-cli/src/runner/run-single-request.js index 2571c983e..7dcdbd383 100644 --- a/packages/bruno-cli/src/runner/run-single-request.js +++ b/packages/bruno-cli/src/runner/run-single-request.js @@ -7,9 +7,6 @@ const prepareRequest = require('./prepare-request'); const interpolateVars = require('./interpolate-vars'); const { interpolateString, interpolateObject } = require('./interpolate-string'); const { ScriptRuntime, TestRuntime, VarsRuntime, AssertRuntime, HooksRuntime, HooksExecutor } = require('@usebruno/js'); -const HookManager = require('@usebruno/js/src/hook-manager'); -const BrunoRequest = require('@usebruno/js/src/bruno-request'); -const BrunoResponse = require('@usebruno/js/src/bruno-response'); const { stripExtension } = require('../utils/filesystem'); const { getOptions } = require('../utils/bru'); const { extractHooks, getTreePathFromCollectionToItem, HOOK_EVENTS } = require('../utils/collection'); @@ -238,7 +235,8 @@ const runSingleRequest = async function ( // Call beforeRequest hooks before running pre-request scripts // Hooks are called in registration order: collection -> folder(s) -> request - const beforeRequestEventData = { request, req: new BrunoRequest(request), collection }; + // Note: BrunoRequest is now created inside HooksRuntime for consistency with ScriptRuntime + const beforeRequestEventData = { request, collection }; const beforeRequestHooksResult = await executeAllHooksConsolidated( { collectionHooks, folderHooks, requestHooks }, @@ -717,13 +715,8 @@ const runSingleRequest = async function ( // Call afterResponse hooks after response is received but before post-response scripts // Hooks are called in registration order: collection -> folder(s) -> request // Uses consolidated execution when multiple levels have hooks (more efficient) - const afterResponseEventData = { - request, - response, - req: new BrunoRequest(request), - res: new BrunoResponse(response), - collection - }; + // Note: BrunoRequest and BrunoResponse are now created inside HooksRuntime for consistency with ScriptRuntime + const afterResponseEventData = { request, response, collection }; const afterResponseHooksResult = await executeAllHooksConsolidated( { collectionHooks, folderHooks, requestHooks }, diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index aab748130..14c68d044 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -9,9 +9,6 @@ const { ipcMain } = require('electron'); const { each, get, extend, cloneDeep, merge } = require('lodash'); const { NtlmClient } = require('axios-ntlm'); const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime, HooksRuntime, HooksExecutor } = require('@usebruno/js'); -// BrunoRequest and BrunoResponse are not exported from main index, require directly -const BrunoRequest = require('@usebruno/js/src/bruno-request'); -const BrunoResponse = require('@usebruno/js/src/bruno-response'); const { encodeUrl } = require('@usebruno/common').utils; const { extractPromptVariables } = require('@usebruno/common').utils; const { interpolateString } = require('./interpolate-string'); @@ -750,7 +747,8 @@ const registerNetworkIpc = (mainWindow) => { // Call beforeRequest hooks before running pre-request scripts // Hooks are called in registration order: collection -> folder(s) -> request - const beforeRequestEventData = { request, req: new BrunoRequest(request), collection, collectionUid }; + // Note: BrunoRequest is now created inside HooksRuntime for consistency with ScriptRuntime + const beforeRequestEventData = { request, collection, collectionUid }; const hookOptions = { request, envVars, @@ -943,14 +941,8 @@ const registerNetworkIpc = (mainWindow) => { // Call afterResponse hooks after response is received but before post-response scripts // Hooks are called in registration order: collection -> folder(s) -> request - const afterResponseEventData = { - request, - response, - req: new BrunoRequest(request), - res: new BrunoResponse(response), - collection, - collectionUid - }; + // Note: BrunoRequest and BrunoResponse are now created inside HooksRuntime for consistency with ScriptRuntime + const afterResponseEventData = { request, response, collection, collectionUid }; // Call afterResponse hooks using consolidated approach when multiple levels have hooks await executeAllHooksConsolidated( @@ -1476,7 +1468,8 @@ const registerNetworkIpc = (mainWindow) => { try { // Call beforeRequest hooks using consolidated approach when multiple levels have hooks - const beforeRequestEventData = { request, req: new BrunoRequest(request), collection, collectionUid }; + // Note: BrunoRequest is now created inside HooksRuntime for consistency with ScriptRuntime + const beforeRequestEventData = { request, collection, collectionUid }; const beforeRequestHooksResult = await executeAllHooksConsolidated( { collectionHooks, folderHooks, requestHooks }, @@ -1731,14 +1724,8 @@ const registerNetworkIpc = (mainWindow) => { } // Call afterResponse hooks using consolidated approach when multiple levels have hooks - const afterResponseEventData = { - request, - response, - req: new BrunoRequest(request), - res: new BrunoResponse(response), - collection, - collectionUid - }; + // Note: BrunoRequest and BrunoResponse are now created inside HooksRuntime for consistency with ScriptRuntime + const afterResponseEventData = { request, response, collection, collectionUid }; const afterResponseHooksResult = await executeAllHooksConsolidated( { collectionHooks, folderHooks, requestHooks }, diff --git a/packages/bruno-js/src/runtime/hooks-executor.js b/packages/bruno-js/src/runtime/hooks-executor.js index 6eeb5008d..de33b7418 100644 --- a/packages/bruno-js/src/runtime/hooks-executor.js +++ b/packages/bruno-js/src/runtime/hooks-executor.js @@ -80,11 +80,15 @@ const executeHooksForLevel = async (hooksFile, hookEvent, eventData, options) => collectionName } = options; + // Extract response from eventData if available (for afterResponse hooks) + const response = eventData?.response; + try { const hooksRuntime = new HooksRuntime({ runtime: scriptingConfig?.runtime }); const result = await hooksRuntime.runHooks({ hooksFile: decomment(hooksFile), request, + response, envVariables, runtimeVariables, collectionPath, @@ -96,7 +100,13 @@ const executeHooksForLevel = async (hooksFile, hookEvent, eventData, options) => }); if (result?.hookManager) { - await result.hookManager.call(hookEvent, eventData); + // Enrich eventData with runtime-created req/res wrappers + const enrichedEventData = { + ...eventData, + req: result.req || eventData.req, + res: result.res || eventData.res + }; + await result.hookManager.call(hookEvent, enrichedEventData); // Dispose HookManager to free VM resources if (typeof result.hookManager.dispose === 'function') { result.hookManager.dispose(); @@ -139,12 +149,16 @@ const executeConsolidatedHooks = async (extractedHooks, hookEvent, eventData, op collectionName } = options; + // Extract response from eventData if available (for afterResponse hooks) + const response = eventData?.response; + try { const hooksRuntime = new HooksRuntime({ runtime: scriptingConfig?.runtime }); const result = await hooksRuntime.runHooks({ consolidated: true, consolidatedHooks: extractedHooks, request, + response, envVariables, runtimeVariables, collectionPath, @@ -156,7 +170,13 @@ const executeConsolidatedHooks = async (extractedHooks, hookEvent, eventData, op }); if (result?.hookManager) { - await result.hookManager.call(hookEvent, eventData); + // Enrich eventData with runtime-created req/res wrappers + const enrichedEventData = { + ...eventData, + req: result.req || eventData.req, + res: result.res || eventData.res + }; + await result.hookManager.call(hookEvent, enrichedEventData); // IMPORTANT: Re-capture runner control values AFTER hooks have been called // The hooks may have called bru.runner.setNextRequest(), bru.runner.skipRequest(), etc. diff --git a/packages/bruno-js/src/runtime/hooks-runtime.js b/packages/bruno-js/src/runtime/hooks-runtime.js index 9f95185be..42c441c6a 100644 --- a/packages/bruno-js/src/runtime/hooks-runtime.js +++ b/packages/bruno-js/src/runtime/hooks-runtime.js @@ -1,5 +1,7 @@ const { runScriptInNodeVm } = require('../sandbox/node-vm'); const Bru = require('../bru'); +const BrunoRequest = require('../bruno-request'); +const BrunoResponse = require('../bruno-response'); const HookManager = require('../hook-manager'); const { cleanJson } = require('../utils'); const { executeQuickJsVmAsync } = require('../sandbox/quickjs'); @@ -54,7 +56,8 @@ class HooksRuntime { * Run hooks script to register event handlers * @param {object} options - Configuration options * @param {string} [options.hooksFile] - The hooks script content (for single-level execution) - * @param {object} options.request - The request object (used for variable extraction only) + * @param {object} options.request - The request object (used for variable extraction and BrunoRequest creation) + * @param {object} [options.response] - The response object (used for BrunoResponse creation, only for afterResponse hooks) * @param {object} options.envVariables - Environment variables * @param {object} options.runtimeVariables - Runtime variables * @param {string} options.collectionPath - Collection path @@ -69,12 +72,13 @@ class HooksRuntime { * @param {string} [options.consolidatedHooks.collectionHooks] - Collection-level hooks script * @param {Array} [options.consolidatedHooks.folderHooks] - Array of folder hooks * @param {string} [options.consolidatedHooks.requestHooks] - Request-level hooks script - * @returns {object} Result containing the hookManager instance + * @returns {object} Result containing the hookManager instance, and req/res wrapper objects */ async runHooks(options) { const { hooksFile, request, + response, envVariables, runtimeVariables, collectionPath, @@ -100,6 +104,7 @@ class HooksRuntime { return this._runConsolidatedHooks({ consolidatedHooks, request, + response, envVariables, runtimeVariables, collectionPath, @@ -122,8 +127,14 @@ class HooksRuntime { // Pass activeHookManager to Bru so it uses the same instance (whether provided or newly created) const bru = new Bru(this.runtime, envVariables, runtimeVariables, processEnvVars, collectionPath, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables, oauth2CredentialVariables, collectionName, promptVariables, activeHookManager); + // Create BrunoRequest and BrunoResponse wrappers (similar to ScriptRuntime) + const req = request ? new BrunoRequest(request) : null; + const res = response ? new BrunoResponse(response) : null; + const context = { - bru + bru, + req, + res }; if (onConsoleLog && typeof onConsoleLog === 'function') { @@ -157,7 +168,9 @@ class HooksRuntime { nextRequestName: bru.nextRequest, skipRequest: bru.skipRequest, stopExecution: bru.stopExecution, - __bru: bru + __bru: bru, + req, + res }; } @@ -184,7 +197,9 @@ class HooksRuntime { nextRequestName: bru.nextRequest, skipRequest: bru.skipRequest, stopExecution: bru.stopExecution, - __bru: bru + __bru: bru, + req, + res }; } @@ -211,7 +226,9 @@ class HooksRuntime { nextRequestName: bru.nextRequest, skipRequest: bru.skipRequest, stopExecution: bru.stopExecution, - __bru: bru + __bru: bru, + req, + res }; } @@ -225,6 +242,8 @@ class HooksRuntime { async _runConsolidatedHooks(options) { const { consolidatedHooks, + request, + response, envVariables, runtimeVariables, collectionPath, @@ -264,6 +283,9 @@ class HooksRuntime { promptVariables, activeHookManager ); + // Create BrunoRequest and BrunoResponse wrappers + const req = request ? new BrunoRequest(request) : null; + const res = response ? new BrunoResponse(response) : null; return { hookManager: activeHookManager, envVariables: cleanJson(envVariables), @@ -273,7 +295,9 @@ class HooksRuntime { nextRequestName: bru.nextRequest, skipRequest: bru.skipRequest, stopExecution: bru.stopExecution, - __bru: bru + __bru: bru, + req, + res }; } @@ -298,9 +322,15 @@ class HooksRuntime { activeHookManager ); + // Create BrunoRequest and BrunoResponse wrappers (similar to ScriptRuntime) + const req = request ? new BrunoRequest(request) : null; + const res = response ? new BrunoResponse(response) : null; + // Prepare context with error handling callback const context = { bru, + req, + res, __hookResult: null, __onHookError: (level, error) => { if (onConsoleLog) { @@ -348,7 +378,9 @@ class HooksRuntime { nextRequestName: bru.nextRequest, skipRequest: bru.skipRequest, stopExecution: bru.stopExecution, - __bru: bru + __bru: bru, + req, + res }; } @@ -375,7 +407,9 @@ class HooksRuntime { skipRequest: bru.skipRequest, stopExecution: bru.stopExecution, // Include bru reference so callers can read updated values after hook execution - __bru: bru + __bru: bru, + req, + res }; } }