Feat/grpc scripting#8754
Conversation
WalkthroughThis change centralizes HTTP and gRPC scripting phases, adds gRPC script execution and APIs, updates serialization and runtimes, and makes editors, Redux state, request panes, response panes, and test displays phase-aware. ChangesPhase-aware scripting
Estimated code review effort: 5 (Critical) | ~120 minutes Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/bruno-schema/src/collections/index.js (1)
517-553: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winTrailing comma on line 537.
afterCallEnd: Yup.string().nullable(),is followed directly by the closing}), leaving a trailing comma on a changed line.As per coding guidelines, "Do not use trailing commas."🧹 Proposed fix
script: Yup.object({ beforeCallStart: Yup.string().nullable(), beforeMessageSend: Yup.string().nullable(), afterMessageReceive: Yup.string().nullable(), - afterCallEnd: Yup.string().nullable(), + afterCallEnd: Yup.string().nullable() })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/bruno-schema/src/collections/index.js` around lines 517 - 553, Remove the trailing comma after the afterCallEnd property in the script schema within grpcRequestSchema, leaving the closing object syntax unchanged.Source: Coding guidelines
🧹 Nitpick comments (3)
packages/bruno-common/src/scripting/phases.ts (1)
41-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
deepFreezedoesn't surface immutability at the type level.It returns
Trather than aReadonly/DeepReadonly<T>, so TypeScript won't catch a consumer attempting to mutateSCRIPT_PHASESat compile time, even though the runtime freeze would silently no-op (or throw in strict mode).♻️ Suggested typing
-const deepFreeze = <T>(obj: T): T => { +type DeepReadonly<T> = { readonly [K in keyof T]: DeepReadonly<T[K]> }; + +const deepFreeze = <T>(obj: T): DeepReadonly<T> => { Object.values(obj as Record<string, unknown>).forEach((value) => { if (value && typeof value === 'object') { deepFreeze(value); } }); - return Object.freeze(obj); + return Object.freeze(obj) as DeepReadonly<T>; };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/bruno-common/src/scripting/phases.ts` around lines 41 - 48, Update deepFreeze so its return type expresses recursive readonly immutability, using the project’s existing DeepReadonly type if available or defining an equivalent type near deepFreeze. Ensure SCRIPT_PHASES receives that deeply readonly type while preserving the existing runtime freezing behavior.packages/bruno-converters/src/opencollection/common/scripts.ts (1)
6-62: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftDuplicate phase-iteration script (de)serialization logic across packages. Both files independently implement the same "loop
getPhasesByRequestType, read/writescript[phase.FIELD]againstphase.YML_TYPE" logic; both already depend on@usebruno/commonfor the phase registry, making a shared helper there a natural consolidation point.
packages/bruno-converters/src/opencollection/common/scripts.ts#L6-L62: extracttoOpenCollectionScripts/fromOpenCollectionScripts's phase-loop logic into a shared@usebruno/commonhelper and delegate here.packages/bruno-filestore/src/formats/yml/common/scripts.ts#L6-L66: extracttoOpenCollectionScripts/toBrunoScripts's equivalent phase-loop logic and delegate to the same shared helper.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/bruno-converters/src/opencollection/common/scripts.ts` around lines 6 - 62, Extract the duplicated phase-based script serialization and deserialization logic from toOpenCollectionScripts/fromOpenCollectionScripts in packages/bruno-converters/src/opencollection/common/scripts.ts and the equivalent toOpenCollectionScripts/toBrunoScripts logic in packages/bruno-filestore/src/formats/yml/common/scripts.ts into shared helpers in `@usebruno/common`. Make both files delegate to those helpers while preserving request-type phase selection, phase.FIELD/YML_TYPE mapping, and tests handling.packages/bruno-js/src/runtime/test-runtime.js (1)
57-57: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse the shared
SCRIPT_PHASESconstant for the gRPC phase.In
packages/bruno-js/src/runtime/test-runtime.js, importSCRIPT_PHASESfrom@usebruno/commonand replace'afterCallEnd'withSCRIPT_PHASES.GRPC.AFTER_CALL_END.FIELDso gRPC tests keep using the canonical phase field.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/bruno-js/src/runtime/test-runtime.js` at line 57, Update the gRPC phase assignment in the runtime test setup to import and use the shared SCRIPT_PHASES constant from `@usebruno/common`, replacing the hardcoded 'afterCallEnd' value with SCRIPT_PHASES.GRPC.AFTER_CALL_END.FIELD while preserving the existing non-gRPC behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js`:
- Around line 150-152: Update the ScriptErrorIcon invocation in GrpcResponsePane
to pass itemUid={item.uid} instead of item, while preserving the existing
onClick behavior and conditional rendering.
In `@packages/bruno-common/src/scripting/phases.ts`:
- Around line 102-112: Update the ERROR_STATE_KEY value in the
AFTER_MESSAGE_RECEIVE phase configuration to include “Receive,” matching its
FIELD and TEST_RESULTS_KEY naming pattern while leaving the other phase metadata
unchanged.
In `@packages/bruno-electron/src/ipc/network/grpc-event-handlers.js`:
- Around line 562-597: Update the gRPC after-message lifecycle around
afterMessageReceive and afterCallEnd so receive scripts are serialized or
awaited before afterCallEnd begins. Preserve ordering of shared
envVars/runtimeVariables side effects and ensure afterCallEnd observes any
afterMessageReceive error before running.
In `@packages/bruno-js/src/runtime/script-runtime.js`:
- Around line 353-383: Update the beforeMessageSend flow around the phaseData
object and final return so the script result uses the message stored on the
phase object after sandbox execution, rather than the original outgoingMessage
parameter. Preserve the existing script result handling and return the mutated
phaseData.message value for downstream runBeforeMessageSend processing.
In `@packages/bruno-lang/v2/src/bruToJson.js`:
- Around line 1132-1135: Update the script semantic action to warn when
FIELD_BY_BRU_TYPE has no entry for scriptType.sourceString before returning the
existing empty object. Preserve the current mapped-script behavior, and include
the unrecognized script type in the warning so discarded script content is
visible to users.
In `@packages/bruno-lang/v2/src/jsonToBru.js`:
- Around line 737-749: Update the requestType assignment in the script
serialization block to detect gRPC using grpc && grpc.url, matching the existing
condition in the surrounding conversion logic. Ensure truthy grpc objects
without a URL are treated as HTTP so script.req and script.res phases remain
serialized.
---
Outside diff comments:
In `@packages/bruno-schema/src/collections/index.js`:
- Around line 517-553: Remove the trailing comma after the afterCallEnd property
in the script schema within grpcRequestSchema, leaving the closing object syntax
unchanged.
---
Nitpick comments:
In `@packages/bruno-common/src/scripting/phases.ts`:
- Around line 41-48: Update deepFreeze so its return type expresses recursive
readonly immutability, using the project’s existing DeepReadonly type if
available or defining an equivalent type near deepFreeze. Ensure SCRIPT_PHASES
receives that deeply readonly type while preserving the existing runtime
freezing behavior.
In `@packages/bruno-converters/src/opencollection/common/scripts.ts`:
- Around line 6-62: Extract the duplicated phase-based script serialization and
deserialization logic from toOpenCollectionScripts/fromOpenCollectionScripts in
packages/bruno-converters/src/opencollection/common/scripts.ts and the
equivalent toOpenCollectionScripts/toBrunoScripts logic in
packages/bruno-filestore/src/formats/yml/common/scripts.ts into shared helpers
in `@usebruno/common`. Make both files delegate to those helpers while preserving
request-type phase selection, phase.FIELD/YML_TYPE mapping, and tests handling.
In `@packages/bruno-js/src/runtime/test-runtime.js`:
- Line 57: Update the gRPC phase assignment in the runtime test setup to import
and use the shared SCRIPT_PHASES constant from `@usebruno/common`, replacing the
hardcoded 'afterCallEnd' value with SCRIPT_PHASES.GRPC.AFTER_CALL_END.FIELD
while preserving the existing non-gRPC behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: ad46095d-b139-4547-b097-819679da7456
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (50)
packages/bruno-app/src/components/CodeEditor/index.jspackages/bruno-app/src/components/RequestPane/GrpcRequestPane/index.jspackages/bruno-app/src/components/RequestPane/HttpRequestPane/index.jspackages/bruno-app/src/components/RequestPane/Script/index.jspackages/bruno-app/src/components/RequestPane/Tests/index.jspackages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.jspackages/bruno-app/src/components/ResponsePane/ScriptError/index.jspackages/bruno-app/src/components/ResponsePane/TestResults/index.jspackages/bruno-app/src/components/ResponsePane/TestResultsLabel/index.jspackages/bruno-app/src/components/ResponsePane/index.jspackages/bruno-app/src/components/RunnerResults/ResponsePane/index.jspackages/bruno-app/src/providers/ReduxStore/slices/collections/index.jspackages/bruno-app/src/utils/codemirror/autocomplete.jspackages/bruno-app/src/utils/network/index.jspackages/bruno-cli/src/runner/run-single-request.jspackages/bruno-common/src/index.tspackages/bruno-common/src/scripting/phases.tspackages/bruno-converters/src/opencollection/common/scripts.tspackages/bruno-converters/src/opencollection/items/grpc.tspackages/bruno-electron/src/ipc/network/grpc-event-handlers.jspackages/bruno-electron/src/ipc/network/index.jspackages/bruno-electron/src/ipc/network/prepare-grpc-request.jspackages/bruno-electron/src/utils/collection.jspackages/bruno-filestore/src/formats/yml/common/scripts.tspackages/bruno-filestore/src/formats/yml/items/parseGraphQLRequest.tspackages/bruno-filestore/src/formats/yml/items/parseGrpcRequest.tspackages/bruno-filestore/src/formats/yml/items/parseHttpRequest.tspackages/bruno-filestore/src/formats/yml/items/parseWebsocketRequest.tspackages/bruno-filestore/src/formats/yml/items/stringifyGrpcRequest.tspackages/bruno-filestore/src/formats/yml/parseCollection.tspackages/bruno-filestore/src/formats/yml/parseFolder.tspackages/bruno-js/src/bru.jspackages/bruno-js/src/bruno-onmessage.jspackages/bruno-js/src/grpc-script-api.jspackages/bruno-js/src/runtime/assert-runtime.jspackages/bruno-js/src/runtime/script-runtime.jspackages/bruno-js/src/runtime/test-runtime.jspackages/bruno-js/src/runtime/vars-runtime.jspackages/bruno-js/src/sandbox/quickjs/shims/bru.jspackages/bruno-js/src/sandbox/quickjs/shims/bruno-grpc.jspackages/bruno-js/src/sandbox/quickjs/shims/bruno-response.jspackages/bruno-js/src/utils.jspackages/bruno-js/src/utils/error-formatter.jspackages/bruno-js/tests/runtime.spec.jspackages/bruno-js/tests/script-runtime-scripted-entries.spec.jspackages/bruno-lang/v2/src/bruToJson.jspackages/bruno-lang/v2/src/jsonToBru.jspackages/bruno-requests/src/grpc/grpc-client.jspackages/bruno-schema-types/src/common/scripts.tspackages/bruno-schema/src/collections/index.js
| {hasScriptOrTestError && !showScriptErrorCard && ( | ||
| <ScriptErrorIcon item={item} onClick={() => setShowScriptErrorCard(true)} /> | ||
| )} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate files =="
fd -a 'GrpcResponsePane|ScriptErrorIcon' . | sed 's#^\./##'
echo "== relevant GrpcResponsePane section =="
file="$(fd 'index.js' packages/bruno-app/src/components/ResponsePane/GrpcResponsePane | head -n1 || true)"
if [ -n "${file:-}" ]; then
wc -l "$file"
sed -n '120,170p' "$file" | cat -n
fi
echo "== ScriptErrorIcon definitions/usages =="
rg -n "function ScriptErrorIcon|const ScriptErrorIcon|ScriptErrorIcon|itemUid|toolhintId|id=" packages/bruno-app/src -g '*.js' -g '*.jsx' -g '*.ts' -g '*.tsx'Repository: usebruno/bruno
Length of output: 50371
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate GrpcResponsePane and ScriptErrorIcon =="
find packages/bruno-app/src/components/ResponsePane \( -path '*/GrpcResponsePane/*' -o -path '*/ScriptErrorIcon/*' \) -type f | sort
echo "== ScriptErrorIcon =="
fi="$(fd 'index.js|index.jsx' packages/bruno-app/src/components/ResponsePane/ScriptErrorIcon | head -n1 || true)"
if [ -n "${fi:-}" ]; then
wc -l "$fi"
cat -n "$fi" | sed -n '1,180p'
fi
echo "== GrpcResponsePane imports/render around Icon =="
gf="$(fd 'index.js|index.jsx' packages/bruno-app/src/components/ResponsePane/GrpcResponsePane | head -n1 || true)"
if [ -n "${gf:-}" ]; then
rg -n "ScriptErrorIcon|hasScriptOrTestError|showScriptErrorCard|setShowScriptErrorCard|import ScriptErrorIcon|useIpc" "$gf" -A 6 -B 6
fiRepository: usebruno/bruno
Length of output: 2634
🏁 Script executed:
#!/bin/bash
set -euo pipefail
gf="$(fd 'index.js|index.jsx' packages/bruno-app/src/components/ResponsePane/GrpcResponsePane | head -n1 || true)"
if [ -n "${gf:-}" ]; then
echo "== GrpcResponsePane =="
wc -l "$gf"
cat -n "$gf" | sed -n '1,220p'
echo "== focused ScriptErrorIcon occurrences =="
rg -n "ScriptErrorIcon|hasScriptOrTestError|showScriptErrorCard|itemUid" "$gf" -A 4 -B 4 || true
echo "== sibling ScriptErrorIcon usages =="
rg -n "ScriptErrorIcon" packages/bruno-app/src/components/ResponsePane packages/bruno-app/src/components/RunnerResults/ResponsePane -A 2 -B 2 || true
fi
echo "== read-only prop destructuring behavior =="
node - <<'JS'
function scriptErrorIconFromArgs(args) {
const { itemUid, onClick, className } = args;
return `script-error-icon-${itemUid}`;
}
console.log(JSON.stringify({
itemProp: scriptErrorIconFromArgs({ item: { uid: 'req-1' } }),
itemUidProp: scriptErrorIconFromArgs({ itemUid: 'req-1' })
}, null, 2));
JSRepository: usebruno/bruno
Length of output: 5765
Pass itemUid into ScriptErrorIcon.
ScriptErrorIcon destructures itemUid, not item, so the tooltip anchor becomes script-error-icon-undefined. Match the other ResponsePane callers with itemUid={item.uid}.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js`
around lines 150 - 152, Update the ScriptErrorIcon invocation in
GrpcResponsePane to pass itemUid={item.uid} instead of item, while preserving
the existing onClick behavior and conditional rendering.
| AFTER_MESSAGE_RECEIVE: { | ||
| REQUEST_TYPE: 'grpc-request', | ||
| FIELD: 'afterMessageReceive', | ||
| YML_TYPE: 'grpc:after-message-receive', | ||
| BRU_TYPE: 'grpc:after-message-receive', | ||
| SCRIPT_TYPE: 'grpc:after-message-receive', | ||
| LABEL: 'After Message', | ||
| HINTS: ['bru'], | ||
| ERROR_STATE_KEY: 'afterMessageScriptError', | ||
| TEST_RESULTS_KEY: 'afterMessageReceiveTestResults' | ||
| }, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
ERROR_STATE_KEY typo breaks the naming pattern for AFTER_MESSAGE_RECEIVE.
Every other GRPC phase's ERROR_STATE_KEY mirrors its FIELD (beforeCallStartScriptError, beforeMessageSendScriptError, afterCallEndScriptError), and this phase's own TEST_RESULTS_KEY correctly uses the full name (afterMessageReceiveTestResults). ERROR_STATE_KEY: 'afterMessageScriptError' drops "Receive", which looks like a typo.
🐛 Proposed fix
LABEL: 'After Message',
HINTS: ['bru'],
- ERROR_STATE_KEY: 'afterMessageScriptError',
+ ERROR_STATE_KEY: 'afterMessageReceiveScriptError',
TEST_RESULTS_KEY: 'afterMessageReceiveTestResults'📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| AFTER_MESSAGE_RECEIVE: { | |
| REQUEST_TYPE: 'grpc-request', | |
| FIELD: 'afterMessageReceive', | |
| YML_TYPE: 'grpc:after-message-receive', | |
| BRU_TYPE: 'grpc:after-message-receive', | |
| SCRIPT_TYPE: 'grpc:after-message-receive', | |
| LABEL: 'After Message', | |
| HINTS: ['bru'], | |
| ERROR_STATE_KEY: 'afterMessageScriptError', | |
| TEST_RESULTS_KEY: 'afterMessageReceiveTestResults' | |
| }, | |
| AFTER_MESSAGE_RECEIVE: { | |
| REQUEST_TYPE: 'grpc-request', | |
| FIELD: 'afterMessageReceive', | |
| YML_TYPE: 'grpc:after-message-receive', | |
| BRU_TYPE: 'grpc:after-message-receive', | |
| SCRIPT_TYPE: 'grpc:after-message-receive', | |
| LABEL: 'After Message', | |
| HINTS: ['bru'], | |
| ERROR_STATE_KEY: 'afterMessageReceiveScriptError', | |
| TEST_RESULTS_KEY: 'afterMessageReceiveTestResults' | |
| }, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/bruno-common/src/scripting/phases.ts` around lines 102 - 112, Update
the ERROR_STATE_KEY value in the AFTER_MESSAGE_RECEIVE phase configuration to
include “Receive,” matching its FIELD and TEST_RESULTS_KEY naming pattern while
leaving the other phase metadata unchanged.
| let afterMessageReceiveErrored = false; | ||
|
|
||
| // ── After Message Receive ────────────────────────────────────────────── | ||
| const hasAfterMessageReceiveScript = !!get(preparedRequest, `script.${SCRIPT_PHASES.GRPC.AFTER_MESSAGE_RECEIVE.FIELD}`)?.length; | ||
| const afterMessageReceive = hasAfterMessageReceiveScript | ||
| ? (message) => { | ||
| const messageReceivedAt = new Date(); | ||
| runAfterMessageReceive({ ...scriptContext, message, messageReceivedAt }) | ||
| .then(({ scriptError }) => { | ||
| if (scriptError) afterMessageReceiveErrored = true; | ||
| }) | ||
| .catch((err) => { | ||
| console.error('Error running gRPC afterMessageReceive script:', err); | ||
| }); | ||
| } | ||
| : undefined; | ||
|
|
||
| // ── After Call End ───────────────────────────────────────────────────── | ||
| const hasAfterCallEndScript = !!get(preparedRequest, `script.${SCRIPT_PHASES.GRPC.AFTER_CALL_END.FIELD}`)?.length; | ||
| const testFile = get(preparedRequest, 'tests'); | ||
| const hasTests = typeof testFile === 'string' && testFile.length > 0; | ||
| const afterCallEnd = (hasAfterCallEndScript || hasTests) | ||
| ? (response = {}) => { | ||
| if (afterMessageReceiveErrored) return; | ||
| (async () => { | ||
| if (hasAfterCallEndScript) { | ||
| await runAfterCallEnd({ ...scriptContext, response }); | ||
| } | ||
| if (hasTests) { | ||
| await runTestFile({ ...scriptContext, response }); | ||
| } | ||
| })().catch((err) => { | ||
| console.error('Error running gRPC afterCallEnd script/tests:', err); | ||
| }); | ||
| } | ||
| : undefined; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect how startConnection sequences onAfterMessageReceive vs onAfterCallEnd
fd -t f 'grpc-client.js' packages/bruno-requests --exec cat -n {}Repository: usebruno/bruno
Length of output: 48809
🏁 Script executed:
#!/bin/bash
# Inspect the gRPC event handler implementation and relevant helper callers/definitions.
fd -t f 'grpc-event-handlers.js' packages/bruno-electron --exec sh -c '
file="$1"
echo "===== $file outline ====="
ast-grep outline "$file" --match startGrpcOrRestRequest --view compact || true
echo "===== lines 530-610 ====="
sed -n "530,610p" "$file" | nl -ba -v530
' sh {}
echo "===== relevant references ====="
rg -n "runAfterMessageReceive|runAfterCallEnd|runTestFile|afterMessageReceiveErrored|afterMessageReceive|afterCallEnd|SCRIPT_PHASES\.GRPC|onAfterMessageReceive|onAfterCallEnd" packages/bruno-electron/src packages/bruno-requests/src -SRepository: usebruno/bruno
Length of output: 8590
Serialize or await afterMessageReceive before afterCallEnd runs.
The gRPC client calls onAfterMessageReceive synchronously on data responses and on onAfterCallEnd when the call completes, but the IPC handler starts runAfterMessageReceive(...) as fire-and-forget and only stores its error via .then. If a receive script is async/await, afterCallEnd can start before that error is recorded and can also run before receive scripts’ side-effects on shared envVars/runtimeVariables are visible for the same response. Queue the receive handlers or await them before invoking afterCallEnd.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/bruno-electron/src/ipc/network/grpc-event-handlers.js` around lines
562 - 597, Update the gRPC after-message lifecycle around afterMessageReceive
and afterCallEnd so receive scripts are serialized or awaited before
afterCallEnd begins. Preserve ordering of shared envVars/runtimeVariables side
effects and ensure afterCallEnd observes any afterMessageReceive error before
running.
| phaseType: SCRIPT_PHASES.GRPC.BEFORE_MESSAGE_SEND.FIELD, | ||
| phaseData: { message: outgoingMessage } | ||
| }); | ||
|
|
||
| const { __brunoTestResults, test } = createBruTestResultMethods(bru, assertionResults, chai); | ||
|
|
||
| const context = { | ||
| bru, | ||
| test, | ||
| expect: chai.expect, | ||
| assert: chai.assert, | ||
| __brunoTestResults: __brunoTestResults, | ||
| __bruSetScope: createScopeSetter(bru) | ||
| }; | ||
|
|
||
| const bruConsole = this.#buildConsole(onConsoleLog); | ||
| if (bruConsole) context.console = bruConsole; | ||
|
|
||
| bindRunRequest(bru, runRequestByItemPathname); | ||
|
|
||
| const buildRequestScriptResult = () => this.#buildScriptResult({ | ||
| primary: { request }, | ||
| bru, | ||
| testResults: __brunoTestResults, | ||
| envVariables, | ||
| runtimeVariables, | ||
| globalEnvironmentVariables | ||
| }); | ||
|
|
||
| const result = await this.#executeInSandbox({ script, context, collectionPath, scriptingConfig, scriptPath }, buildRequestScriptResult); | ||
| return { ...result, message: outgoingMessage }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
beforeMessageSend edits are silently dropped.
phaseData: { message: outgoingMessage } is a fresh object. When the script mutates the message, GrpcMessage.set/update reassign phaseData.message — not the outgoingMessage parameter. The return then hands back the original outgoingMessage, so any edits are lost. Downstream, runBeforeMessageSend in grpc-event-handlers.js writes scriptResult.message to the wire, so the transformed payload never leaves the app.
Read the value back off the phase object:
🐛 Proposed fix
+ const phaseData = { message: outgoingMessage };
const bru = new Bru({
runtime: this.runtime,
envVariables,
runtimeVariables,
processEnvVars,
collectionPath,
collectionVariables,
folderVariables,
requestVariables,
globalEnvironmentVariables,
oauth2CredentialVariables,
collectionName,
promptVariables,
certsAndProxyConfig,
requestUrl: request?.url,
request,
phaseType: SCRIPT_PHASES.GRPC.BEFORE_MESSAGE_SEND.FIELD,
- phaseData: { message: outgoingMessage }
+ phaseData
});
@@
const result = await this.#executeInSandbox({ script, context, collectionPath, scriptingConfig, scriptPath }, buildRequestScriptResult);
- return { ...result, message: outgoingMessage };
+ return { ...result, message: phaseData.message };📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| phaseType: SCRIPT_PHASES.GRPC.BEFORE_MESSAGE_SEND.FIELD, | |
| phaseData: { message: outgoingMessage } | |
| }); | |
| const { __brunoTestResults, test } = createBruTestResultMethods(bru, assertionResults, chai); | |
| const context = { | |
| bru, | |
| test, | |
| expect: chai.expect, | |
| assert: chai.assert, | |
| __brunoTestResults: __brunoTestResults, | |
| __bruSetScope: createScopeSetter(bru) | |
| }; | |
| const bruConsole = this.#buildConsole(onConsoleLog); | |
| if (bruConsole) context.console = bruConsole; | |
| bindRunRequest(bru, runRequestByItemPathname); | |
| const buildRequestScriptResult = () => this.#buildScriptResult({ | |
| primary: { request }, | |
| bru, | |
| testResults: __brunoTestResults, | |
| envVariables, | |
| runtimeVariables, | |
| globalEnvironmentVariables | |
| }); | |
| const result = await this.#executeInSandbox({ script, context, collectionPath, scriptingConfig, scriptPath }, buildRequestScriptResult); | |
| return { ...result, message: outgoingMessage }; | |
| phaseType: SCRIPT_PHASES.GRPC.BEFORE_MESSAGE_SEND.FIELD, | |
| phaseData | |
| }); | |
| const { __brunoTestResults, test } = createBruTestResultMethods(bru, assertionResults, chai); | |
| const context = { | |
| bru, | |
| test, | |
| expect: chai.expect, | |
| assert: chai.assert, | |
| __brunoTestResults: __brunoTestResults, | |
| __bruSetScope: createScopeSetter(bru) | |
| }; | |
| const bruConsole = this.#buildConsole(onConsoleLog); | |
| if (bruConsole) context.console = bruConsole; | |
| bindRunRequest(bru, runRequestByItemPathname); | |
| const buildRequestScriptResult = () => this.#buildScriptResult({ | |
| primary: { request }, | |
| bru, | |
| testResults: __brunoTestResults, | |
| envVariables, | |
| runtimeVariables, | |
| globalEnvironmentVariables | |
| }); | |
| const result = await this.#executeInSandbox({ script, context, collectionPath, scriptingConfig, scriptPath }, buildRequestScriptResult); | |
| return { ...result, message: phaseData.message }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/bruno-js/src/runtime/script-runtime.js` around lines 353 - 383,
Update the beforeMessageSend flow around the phaseData object and final return
so the script result uses the message stored on the phase object after sandbox
execution, rather than the original outgoingMessage parameter. Preserve the
existing script result handling and return the mutated phaseData.message value
for downstream runBeforeMessageSend processing.
| script(_1, scriptType, _2, _3, _4, textblock, _5) { | ||
| const field = FIELD_BY_BRU_TYPE[scriptType.sourceString]; | ||
| return field ? { script: { [field]: outdentString(textblock.sourceString) } } : {}; | ||
| }, |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Unrecognized script:* types now silently vanish instead of failing the parse.
Previously an unmatched script header failed the OHM grammar match (loud error). Now scripttype matches any letters/digits/hyphens/colons, and the semantic action quietly discards the block ({}) when FIELD_BY_BRU_TYPE has no entry — e.g. a typo'd phase name (script:pre-reqeust) will silently drop the whole script body with no error, instead of failing loudly as before. Consider at least logging a warning so users notice lost content.
🐛 Proposed fix: warn on unmatched script type
script(_1, scriptType, _2, _3, _4, textblock, _5) {
const field = FIELD_BY_BRU_TYPE[scriptType.sourceString];
+ if (!field) {
+ console.warn(`Unknown script type "${scriptType.sourceString}" — its content was ignored.`);
+ }
return field ? { script: { [field]: outdentString(textblock.sourceString) } } : {};
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| script(_1, scriptType, _2, _3, _4, textblock, _5) { | |
| const field = FIELD_BY_BRU_TYPE[scriptType.sourceString]; | |
| return field ? { script: { [field]: outdentString(textblock.sourceString) } } : {}; | |
| }, | |
| script(_1, scriptType, _2, _3, _4, textblock, _5) { | |
| const field = FIELD_BY_BRU_TYPE[scriptType.sourceString]; | |
| if (!field) { | |
| console.warn(`Unknown script type "${scriptType.sourceString}" — its content was ignored.`); | |
| } | |
| return field ? { script: { [field]: outdentString(textblock.sourceString) } } : {}; | |
| }, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/bruno-lang/v2/src/bruToJson.js` around lines 1132 - 1135, Update the
script semantic action to warn when FIELD_BY_BRU_TYPE has no entry for
scriptType.sourceString before returning the existing empty object. Preserve the
current mapped-script behavior, and include the unrecognized script type in the
warning so discarded script content is visible to users.
| if (script) { | ||
| const requestType = grpc ? REQUEST_TYPES.GRPC : REQUEST_TYPES.HTTP; | ||
| for (const { FIELD, BRU_TYPE } of getPhasesByRequestType(requestType)) { | ||
| const code = script[FIELD]; | ||
| if (code && code.length) { | ||
| bru += `script:${BRU_TYPE} { | ||
| ${indentString(code)} | ||
| } | ||
|
|
||
| `; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Align gRPC detection with the rest of the file (grpc && grpc.url).
requestType is derived from bare grpc truthiness, but every other gRPC branch in this same function (line 63: if (grpc && grpc.url)) treats a grpc object without a url as "not a grpc request." If json.grpc is ever a truthy-but-empty object, this mismatch would classify the item as GRPC and only serialize GRPC-phase scripts, silently dropping script.req/script.res.
🐛 Proposed fix
- const requestType = grpc ? REQUEST_TYPES.GRPC : REQUEST_TYPES.HTTP;
+ const requestType = grpc?.url ? REQUEST_TYPES.GRPC : REQUEST_TYPES.HTTP;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (script) { | |
| const requestType = grpc ? REQUEST_TYPES.GRPC : REQUEST_TYPES.HTTP; | |
| for (const { FIELD, BRU_TYPE } of getPhasesByRequestType(requestType)) { | |
| const code = script[FIELD]; | |
| if (code && code.length) { | |
| bru += `script:${BRU_TYPE} { | |
| ${indentString(code)} | |
| } | |
| `; | |
| } | |
| } | |
| } | |
| if (script) { | |
| const requestType = grpc?.url ? REQUEST_TYPES.GRPC : REQUEST_TYPES.HTTP; | |
| for (const { FIELD, BRU_TYPE } of getPhasesByRequestType(requestType)) { | |
| const code = script[FIELD]; | |
| if (code && code.length) { | |
| bru += `script:${BRU_TYPE} { | |
| ${indentString(code)} | |
| } | |
| `; | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/bruno-lang/v2/src/jsonToBru.js` around lines 737 - 749, Update the
requestType assignment in the script serialization block to detect gRPC using
grpc && grpc.url, matching the existing condition in the surrounding conversion
logic. Ensure truthy grpc objects without a URL are treated as HTTP so
script.req and script.res phases remain serialized.
…message send, after message receive, after call end
9f1f34c to
558ab40
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/bruno-app/src/components/RequestPane/Script/index.js (1)
95-98: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
buildAiContextPayloadis undefined — confirmed by lint failure.The lint check flags
'buildAiContextPayload' is not definedat this line, which will throw aReferenceErroron every render of this component (it's called unconditionally in a top-leveluseMemo), crashing the Script tab for both HTTP and gRPC requests. It appears the import for this function is missing from the top of the file.🐛 Add the missing import
+import { buildAiContextPayload } from 'utils/ai/buildAiContextPayload'; // adjust path to actual modulePlease run a search to confirm the correct module path:
#!/bin/bash rg -n 'buildAiContextPayload' --type=js -C2🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/bruno-app/src/components/RequestPane/Script/index.js` around lines 95 - 98, Import buildAiContextPayload from its existing module, first locating the canonical export path with a repository search. Add the import alongside the other dependencies in the Script component so the top-level useMemo can resolve the function without changing its existing behavior.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/bruno-app/src/components/RequestPane/Script/index.js`:
- Around line 42-45: Update getDefaultTab to guard SCRIPT_PHASES[0] before
accessing its FIELD property, returning the existing fallback behavior when no
phases are registered. Preserve the current first-phase script selection when
SCRIPT_PHASES[0] exists.
- Around line 153-161: Update the error-state lookup used by the StatusDot in
the hasScript rendering block to construct the key from phase.ERROR_STATE_KEY
followed by “Message”, matching the pattern used by GrpcRequestPane. Preserve
the existing error/default StatusDot behavior while ensuring pre-request and
other script phases resolve their actual item error fields.
- Around line 72-79: Move the useFocusErrorLine invocations out of the
SCRIPT_PHASES.forEach loop in the Script component. Preserve focus behavior for
each script phase while complying with the Rules of Hooks by calling the hook at
a stable component level or delegating each phase to a child component with its
own hook call; continue passing the corresponding item.uid, editor ref, script
phase, and visibility state.
In `@packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js`:
- Line 70: Update hasScriptError and the related effect in ResponsePane to
derive script errors from getPhasesByRequestType(item?.type), while also
including the legacy test error. Keep both the error icon and error-card
behavior consistent with ScriptError so gRPC-only phase errors are detected.
---
Outside diff comments:
In `@packages/bruno-app/src/components/RequestPane/Script/index.js`:
- Around line 95-98: Import buildAiContextPayload from its existing module,
first locating the canonical export path with a repository search. Add the
import alongside the other dependencies in the Script component so the top-level
useMemo can resolve the function without changing its existing behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: ef1b9d56-c095-4213-a8ff-268751b15acb
📒 Files selected for processing (13)
packages/bruno-app/src/components/CodeEditor/index.jspackages/bruno-app/src/components/RequestPane/GrpcRequestPane/index.jspackages/bruno-app/src/components/RequestPane/HttpRequestPane/index.jspackages/bruno-app/src/components/RequestPane/Script/index.jspackages/bruno-app/src/components/RequestPane/Tests/index.jspackages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.jspackages/bruno-app/src/components/ResponsePane/ScriptError/index.jspackages/bruno-app/src/components/ResponsePane/TestResults/index.jspackages/bruno-app/src/components/ResponsePane/TestResultsLabel/index.jspackages/bruno-app/src/components/ResponsePane/index.jspackages/bruno-app/src/components/RunnerResults/ResponsePane/index.jspackages/bruno-app/src/providers/ReduxStore/slices/collections/index.jspackages/bruno-app/src/utils/codemirror/autocomplete.js
🚧 Files skipped from review as they are similar to previous changes (10)
- packages/bruno-app/src/components/RequestPane/Tests/index.js
- packages/bruno-app/src/components/ResponsePane/TestResultsLabel/index.js
- packages/bruno-app/src/components/ResponsePane/TestResults/index.js
- packages/bruno-app/src/components/CodeEditor/index.js
- packages/bruno-app/src/components/RequestPane/HttpRequestPane/index.js
- packages/bruno-app/src/components/ResponsePane/GrpcResponsePane/index.js
- packages/bruno-app/src/components/ResponsePane/index.js
- packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js
- packages/bruno-app/src/components/RequestPane/GrpcRequestPane/index.js
- packages/bruno-app/src/utils/codemirror/autocomplete.js
| const getDefaultTab = () => { | ||
| const hasPreRequestScript = requestScript && requestScript.trim().length > 0; | ||
| return hasPreRequestScript ? 'pre-request' : 'post-response'; | ||
| const hasFirstScript = getScript(SCRIPT_PHASES[0].FIELD); | ||
| return hasFirstScript ? SCRIPT_PHASES[0].SCRIPT_TYPE : SCRIPT_PHASES[1]?.SCRIPT_TYPE; | ||
| }; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Unguarded access to SCRIPT_PHASES[0] can throw if the array is empty.
getDefaultTab reads SCRIPT_PHASES[0].FIELD without an existence check, while the very next line safely uses SCRIPT_PHASES[1]?.SCRIPT_TYPE. If item?.type doesn't match any registered phase (e.g. getPhasesByRequestType returns []), this throws a TypeError on render.
🛡️ Guard against an empty phases array
const getDefaultTab = () => {
- const hasFirstScript = getScript(SCRIPT_PHASES[0].FIELD);
+ if (!SCRIPT_PHASES.length) return undefined;
+ const hasFirstScript = getScript(SCRIPT_PHASES[0].FIELD);
return hasFirstScript ? SCRIPT_PHASES[0].SCRIPT_TYPE : SCRIPT_PHASES[1]?.SCRIPT_TYPE;
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const getDefaultTab = () => { | |
| const hasPreRequestScript = requestScript && requestScript.trim().length > 0; | |
| return hasPreRequestScript ? 'pre-request' : 'post-response'; | |
| const hasFirstScript = getScript(SCRIPT_PHASES[0].FIELD); | |
| return hasFirstScript ? SCRIPT_PHASES[0].SCRIPT_TYPE : SCRIPT_PHASES[1]?.SCRIPT_TYPE; | |
| }; | |
| const getDefaultTab = () => { | |
| if (!SCRIPT_PHASES.length) return undefined; | |
| const hasFirstScript = getScript(SCRIPT_PHASES[0].FIELD); | |
| return hasFirstScript ? SCRIPT_PHASES[0].SCRIPT_TYPE : SCRIPT_PHASES[1]?.SCRIPT_TYPE; | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/bruno-app/src/components/RequestPane/Script/index.js` around lines
42 - 45, Update getDefaultTab to guard SCRIPT_PHASES[0] before accessing its
FIELD property, returning the existing fallback behavior when no phases are
registered. Preserve the current first-phase script selection when
SCRIPT_PHASES[0] exists.
| SCRIPT_PHASES.forEach(({ SCRIPT_TYPE }) => { | ||
| useFocusErrorLine({ | ||
| uid: item.uid, | ||
| editorRef: getEditorRef(SCRIPT_TYPE), | ||
| scriptPhase: SCRIPT_TYPE, | ||
| isVisible: activeTab === SCRIPT_TYPE | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check current signature/impl of useFocusErrorLine to gauge refactor scope
fd useFocusErrorLine -t f
cat -n packages/bruno-app/src/hooks/useFocusErrorLine/index.jsRepository: usebruno/bruno
Length of output: 2736
🏁 Script executed:
#!/bin/bash
set -eu
echo "== locate Script component =="
fd 'index\.js$' packages/bruno-app/src/components/RequestPane/Script -t f
rg -n "SCRIPT_PHASES|useFocusErrorLine|getEditorRef|export const" packages/bruno-app/src/components/RequestPane/Script packages/bruno-app/src -g '*.js' -g '*.ts' -g '*.tsx' -g '*.jsx' | head -200
echo "== relevant script component slice =="
fd 'index\.js$' packages/bruno-app/src/components/RequestPane/Script -t f | while read f; do
echo "--- $f ---"
wc -l "$f"
sed -n '1,140p' "$f" | cat -n
doneRepository: usebruno/bruno
Length of output: 29015
🏁 Script executed:
#!/bin/bash
set -eu
echo "== useFocusErrorLine uses =="
rg -n "useFocusErrorLine\\(" packages/bruno-app/src -g '*.js' -g '*.ts' -g '*.tsx' -g '*.jsx'
echo "== package lint config / eslint plugin versions =="
fd 'eslint|package.json|\.eslintrc|rules-of-hooks' . -t f | sed 's#^\./##' | head -200 | rg -n 'eslint|package\.json|rules-of-hooks|rules' || trueRepository: usebruno/bruno
Length of output: 2151
Move useFocusErrorLine calls outside the forEach.
useFocusErrorLine uses useRef and React hooks internally, so calling it per SCRIPT_PHASES entry from a forEach callback violates the Rules of Hooks. Call it once with all editor refs/phases/config, or handle each visible phase with its own component hook call rather than in this loop.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/bruno-app/src/components/RequestPane/Script/index.js` around lines
72 - 79, Move the useFocusErrorLine invocations out of the SCRIPT_PHASES.forEach
loop in the Script component. Preserve focus behavior for each script phase
while complying with the Rules of Hooks by calling the hook at a stable
component level or delegating each phase to a child component with its own hook
call; continue passing the corresponding item.uid, editor ref, script phase, and
visibility state.
| {hasScript && ( | ||
| <StatusDot | ||
| type={ | ||
| item?.[`${phase.SCRIPT_TYPE}ScriptErrorMessage`] | ||
| ? 'error' | ||
| : 'default' | ||
| } | ||
| /> | ||
| )} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Wrong key used for script-error lookup — error dot will never render.
item?.[${phase.SCRIPT_TYPE}ScriptErrorMessage] builds a key like pre-requestScriptErrorMessage or grpc:before-call-startScriptErrorMessage, neither of which matches any real field on item. The correct pattern — used in GrpcRequestPane for the same phases — is item[${phase.ERROR_STATE_KEY}Message] (e.g. preRequestScriptErrorMessage). As written, the StatusDot will always render type="default" and never surface script errors on this tab.
🐛 Fix the key construction
<StatusDot
type={
- item?.[`${phase.SCRIPT_TYPE}ScriptErrorMessage`]
+ item?.[`${phase.ERROR_STATE_KEY}Message`]
? 'error'
: 'default'
}
/>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {hasScript && ( | |
| <StatusDot | |
| type={ | |
| item?.[`${phase.SCRIPT_TYPE}ScriptErrorMessage`] | |
| ? 'error' | |
| : 'default' | |
| } | |
| /> | |
| )} | |
| {hasScript && ( | |
| <StatusDot | |
| type={ | |
| item?.[`${phase.ERROR_STATE_KEY}Message`] | |
| ? 'error' | |
| : 'default' | |
| } | |
| /> | |
| )} |
🧰 Tools
🪛 ast-grep (0.44.1)
[warning] 153-159: A list component should have a key to prevent re-rendering
Context: <StatusDot
type={
item?.[${phase.SCRIPT_TYPE}ScriptErrorMessage]
? 'error'
: 'default'
}
/>
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.
(list-component-needs-key)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/bruno-app/src/components/RequestPane/Script/index.js` around lines
153 - 161, Update the error-state lookup used by the StatusDot in the hasScript
rendering block to construct the key from phase.ERROR_STATE_KEY followed by
“Message”, matching the pattern used by GrpcRequestPane. Preserve the existing
error/default StatusDot behavior while ensuring pre-request and other script
phases resolve their actual item error fields.
| preRequestTestResults={preRequestTestResults} | ||
| postResponseTestResults={postResponseTestResults} | ||
| /> | ||
| <TestResults item={item} /> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Make runner script-error visibility phase-aware.
TestResults and TestResultsLabel now consume all phase fields, but the nearby hasScriptError and effect still only check legacy pre-request/post-response/test keys. A gRPC-only phase error can therefore omit the error icon and card. Derive both from getPhasesByRequestType(item?.type) plus the legacy test error, matching ScriptError.
Also applies to: 108-108
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/bruno-app/src/components/RunnerResults/ResponsePane/index.js` at
line 70, Update hasScriptError and the related effect in ResponsePane to derive
script errors from getPhasesByRequestType(item?.type), while also including the
legacy test error. Keep both the error icon and error-card behavior consistent
with ScriptError so gRPC-only phase errors are detected.
JIRA
Description
gRPC requests now support scripts and tests via four lifecycle phases — beforeCallStart, beforeMessageSend, afterMessageReceive, afterCallEnd — each exposing a namespaced bru.grpc.* API (request.messages/metadata/message, response.messages/message/trailers/statusCode), plus the common bru.* helpers. Backed by a shared script-phase registry in @usebruno/common, wired through the JS runtime, QuickJS sandbox, and Electron gRPC handlers, with autocomplete for the Script/Tests tabs.
Contribution Checklist:
Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.
Publishing to New Package Managers
Please see here for more information.
Summary by CodeRabbit
Summary by CodeRabbit
New Features
bru.grpcscripting in supported runtimes.Bug Fixes