diff --git a/eslint.config.js b/eslint.config.js
index 40f6c3351..0e742fcdf 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -38,4 +38,4 @@ module.exports = defineConfig([
"no-undef": "error",
},
}
-]);
+]);
\ No newline at end of file
diff --git a/package.json b/package.json
index b1329d2ee..aba14755d 100644
--- a/package.json
+++ b/package.json
@@ -71,4 +71,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/bruno-app/src/components/CodeEditor/index.js b/packages/bruno-app/src/components/CodeEditor/index.js
index f8c13462e..160891542 100644
--- a/packages/bruno-app/src/components/CodeEditor/index.js
+++ b/packages/bruno-app/src/components/CodeEditor/index.js
@@ -87,7 +87,8 @@ if (!SERVER_RENDERED) {
'bru.runner',
'bru.runner.setNextRequest(requestName)',
'bru.runner.skipRequest()',
- 'bru.runner.stopExecution()'
+ 'bru.runner.stopExecution()',
+ 'bru.interpolate(str)'
];
CodeMirror.registerHelper('hint', 'brunoJS', (editor, options) => {
const cursor = editor.getCursor();
@@ -365,7 +366,7 @@ export default class CodeEditor extends React.Component {
let variables = getAllVariables(this.props.collection, this.props.item);
this.variables = variables;
- defineCodeMirrorBrunoVariablesMode(variables, mode);
+ defineCodeMirrorBrunoVariablesMode(variables, mode, false, this.props.enableVariableHighlighting);
this.editor.setOption('mode', 'brunovariables');
};
diff --git a/packages/bruno-app/src/components/MultiLineEditor/index.js b/packages/bruno-app/src/components/MultiLineEditor/index.js
index a44caf4ba..1a6709813 100644
--- a/packages/bruno-app/src/components/MultiLineEditor/index.js
+++ b/packages/bruno-app/src/components/MultiLineEditor/index.js
@@ -130,7 +130,7 @@ class MultiLineEditor extends Component {
addOverlay = (variables) => {
this.variables = variables;
- defineCodeMirrorBrunoVariablesMode(variables, 'text/plain');
+ defineCodeMirrorBrunoVariablesMode(variables, 'text/plain', false, true);
this.editor.setOption('mode', 'brunovariables');
};
diff --git a/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js b/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js
index eaac6f204..d490d8579 100644
--- a/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js
+++ b/packages/bruno-app/src/components/RequestPane/GraphQLVariables/index.js
@@ -67,6 +67,7 @@ const GraphQLVariables = ({ variables, item, collection }) => {
mode="javascript"
onRun={onRun}
onSave={onSave}
+ enableVariableHighlighting={true}
/>
>
);
diff --git a/packages/bruno-app/src/components/RequestPane/RequestBody/index.js b/packages/bruno-app/src/components/RequestPane/RequestBody/index.js
index 8f7fa8465..a0cc8729e 100644
--- a/packages/bruno-app/src/components/RequestPane/RequestBody/index.js
+++ b/packages/bruno-app/src/components/RequestPane/RequestBody/index.js
@@ -49,7 +49,7 @@ const RequestBody = ({ item, collection }) => {
{
onRun={onRun}
onSave={onSave}
mode={codeMirrorMode[bodyMode]}
+ enableVariableHighlighting={true}
/>
);
}
if (bodyMode === 'file') {
- return
+ return ;
}
if (bodyMode === 'formUrlEncoded') {
@@ -77,4 +78,4 @@ const RequestBody = ({ item, collection }) => {
return No Body;
};
-export default RequestBody;
\ No newline at end of file
+export default RequestBody;
diff --git a/packages/bruno-app/src/components/SingleLineEditor/index.js b/packages/bruno-app/src/components/SingleLineEditor/index.js
index 16413bdf3..30c079a36 100644
--- a/packages/bruno-app/src/components/SingleLineEditor/index.js
+++ b/packages/bruno-app/src/components/SingleLineEditor/index.js
@@ -146,7 +146,7 @@ class SingleLineEditor extends Component {
addOverlay = (variables) => {
this.variables = variables;
- defineCodeMirrorBrunoVariablesMode(variables, 'text/plain', this.props.highlightPathParams);
+ defineCodeMirrorBrunoVariablesMode(variables, 'text/plain', this.props.highlightPathParams, true);
this.editor.setOption('mode', 'brunovariables');
};
diff --git a/packages/bruno-app/src/utils/common/codemirror.js b/packages/bruno-app/src/utils/common/codemirror.js
index 661b84433..b1a3d5a8a 100644
--- a/packages/bruno-app/src/utils/common/codemirror.js
+++ b/packages/bruno-app/src/utils/common/codemirror.js
@@ -74,11 +74,11 @@ export class MaskedEditor {
} else {
for (let line = 0; line < lineCount; line++) {
const lineLength = this.editor.getLine(line).length;
- const maskedNode = document.createTextNode('*'.repeat(lineLength));
+ const maskedNode = document.createTextNode('*'.repeat(lineLength));
this.editor.markText(
{ line, ch: 0 },
{ line, ch: lineLength },
- { replacedWith: maskedNode, handleMouseEvents: false }
+ { replacedWith: maskedNode, handleMouseEvents: false }
);
}
}
@@ -86,7 +86,18 @@ export class MaskedEditor {
};
}
-export const defineCodeMirrorBrunoVariablesMode = (_variables, mode, highlightPathParams) => {
+/**
+ * Defines a custom CodeMirror mode for Bruno variables highlighting.
+ * This function creates a specialized mode that can highlight both Bruno template
+ * variables (in the format {{variable}}) and URL path parameters (in the format /:param).
+ *
+ * @param {Object} _variables - The variables object containing data to validate against
+ * @param {string} mode - The base CodeMirror mode to extend (e.g., 'javascript', 'application/json')
+ * @param {boolean} highlightPathParams - Whether to highlight URL path parameters
+ * @param {boolean} highlightVariables - Whether to highlight template variables
+ * @returns {void} - Registers the mode with CodeMirror for later use
+ */
+export const defineCodeMirrorBrunoVariablesMode = (_variables, mode, highlightPathParams, highlightVariables) => {
CodeMirror.defineMode('brunovariables', function (config, parserConfig) {
const { pathParams = {}, ...variables } = _variables || {};
const variablesOverlay = {
@@ -139,13 +150,15 @@ export const defineCodeMirrorBrunoVariablesMode = (_variables, mode, highlightPa
}
};
- let baseMode = CodeMirror.overlayMode(CodeMirror.getMode(config, parserConfig.backdrop || mode), variablesOverlay);
+ let baseMode = CodeMirror.getMode(config, parserConfig.backdrop || mode);
- if (highlightPathParams) {
- return CodeMirror.overlayMode(baseMode, urlPathParamsOverlay);
- } else {
- return baseMode;
+ if (highlightVariables) {
+ baseMode = CodeMirror.overlayMode(baseMode, variablesOverlay);
}
+ if (highlightPathParams) {
+ baseMode = CodeMirror.overlayMode(baseMode, urlPathParamsOverlay);
+ }
+ return baseMode;
});
};
diff --git a/packages/bruno-converters/src/postman/postman-translations.js b/packages/bruno-converters/src/postman/postman-translations.js
index a7bb02fcd..252c4c2d3 100644
--- a/packages/bruno-converters/src/postman/postman-translations.js
+++ b/packages/bruno-converters/src/postman/postman-translations.js
@@ -5,6 +5,7 @@ const replacements = {
'pm\\.environment\\.set\\(': 'bru.setEnvVar(',
'pm\\.variables\\.get\\(': 'bru.getVar(',
'pm\\.variables\\.set\\(': 'bru.setVar(',
+ 'pm\\.variables\\.replaceIn\\(': 'bru.interpolate(',
'pm\\.collectionVariables\\.get\\(': 'bru.getVar(',
'pm\\.collectionVariables\\.set\\(': 'bru.setVar(',
'pm\\.collectionVariables\\.has\\(': 'bru.hasVar(',
diff --git a/packages/bruno-converters/src/utils/jscode-shift-translator.js b/packages/bruno-converters/src/utils/jscode-shift-translator.js
index 6a892e516..92ccf97ba 100644
--- a/packages/bruno-converters/src/utils/jscode-shift-translator.js
+++ b/packages/bruno-converters/src/utils/jscode-shift-translator.js
@@ -52,7 +52,7 @@ const simpleTranslations = {
'pm.variables.get': 'bru.getVar',
'pm.variables.set': 'bru.setVar',
'pm.variables.has': 'bru.hasVar',
-
+ 'pm.variables.replaceIn': 'bru.interpolate',
// Collection variables
'pm.collectionVariables.get': 'bru.getVar',
'pm.collectionVariables.set': 'bru.setVar',
diff --git a/packages/bruno-converters/tests/postman/postman-translations/postman-comments.spec.js b/packages/bruno-converters/tests/postman/postman-translations/postman-comments.spec.js
index 1c1686bf2..fed9f2931 100644
--- a/packages/bruno-converters/tests/postman/postman-translations/postman-comments.spec.js
+++ b/packages/bruno-converters/tests/postman/postman-translations/postman-comments.spec.js
@@ -16,8 +16,8 @@ describe('postmanTranslations - comment handling', () => {
});
test('should comment non-translated pm commands', () => {
- const inputScript = "pm.test('random test', () => postman.variables.replaceIn('{{$guid}}'));";
- const expectedOutput = "// test('random test', () => pm.variables.replaceIn('{{$guid}}'));";
+ const inputScript = "pm.test('random test', () => pm.cookies.get('cookieName'));";
+ const expectedOutput = "// test('random test', () => pm.cookies.get('cookieName'));";
expect(postmanTranslation(inputScript)).toBe(expectedOutput);
});
diff --git a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/variables.test.js b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/variables.test.js
index b4439f826..fe0f80593 100644
--- a/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/variables.test.js
+++ b/packages/bruno-converters/tests/postman/postman-translations/transpiler-tests/variables.test.js
@@ -5,55 +5,104 @@ describe('Variables Translation', () => {
it('should translate pm.variables.get', () => {
const code = 'pm.variables.get("test");';
const translatedCode = translateCode(code);
+
expect(translatedCode).toBe('bru.getVar("test");');
});
it('should translate pm.variables.set', () => {
const code = 'pm.variables.set("test", "value");';
const translatedCode = translateCode(code);
+
expect(translatedCode).toBe('bru.setVar("test", "value");');
});
it('should translate pm.variables.has', () => {
const code = 'pm.variables.has("userId");';
const translatedCode = translateCode(code);
+
expect(translatedCode).toBe('bru.hasVar("userId");');
});
+ it('should translate pm.variables.replaceIn', () => {
+ const code = 'pm.variables.replaceIn("Hello {{name}}");';
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toBe('bru.interpolate("Hello {{name}}");');
+ });
+
+ it('should translate pm.variables.replaceIn with variables and expressions', () => {
+ const code = 'const greeting = pm.variables.replaceIn("Hello {{name}}, your user id is {{userId}}");';
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toBe('const greeting = bru.interpolate("Hello {{name}}, your user id is {{userId}}");');
+ });
+
+ it('should translate pm.variables.replaceIn within complex expressions', () => {
+ const code = 'const url = baseUrl + pm.variables.replaceIn("/users/{{userId}}/profile");';
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toBe('const url = baseUrl + bru.interpolate("/users/{{userId}}/profile");');
+ });
+
+ it('should translate pm.variables.replaceIn with multiple nested variable references', () => {
+ const code = 'const template = pm.variables.replaceIn("{{prefix}}-{{env}}-{{suffix}}");';
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toBe('const template = bru.interpolate("{{prefix}}-{{env}}-{{suffix}}");');
+ });
+
+ it('should translate aliased variables.replaceIn', () => {
+ const code = `
+ const variables = pm.variables;
+ const message = variables.replaceIn("Welcome, {{username}}!");
+ `;
+ const translatedCode = translateCode(code);
+
+ expect(translatedCode).toBe(`
+ const message = bru.interpolate("Welcome, {{username}}!");
+ `);
+ });
+
// Collection variables tests
it('should translate pm.collectionVariables.get', () => {
const code = 'pm.collectionVariables.get("apiUrl");';
const translatedCode = translateCode(code);
+
expect(translatedCode).toBe('bru.getVar("apiUrl");');
});
it('should translate pm.collectionVariables.set', () => {
const code = 'pm.collectionVariables.set("token", jsonData.token);';
const translatedCode = translateCode(code);
+
expect(translatedCode).toBe('bru.setVar("token", jsonData.token);');
});
it('should translate pm.collectionVariables.has', () => {
const code = 'pm.collectionVariables.has("authToken");';
const translatedCode = translateCode(code);
+
expect(translatedCode).toBe('bru.hasVar("authToken");');
});
it('should translate pm.collectionVariables.unset', () => {
const code = 'pm.collectionVariables.unset("tempVar");';
const translatedCode = translateCode(code);
+
expect(translatedCode).toBe('bru.deleteVar("tempVar");');
});
it('should handle pm.globals.get', () => {
const code = 'pm.globals.get("test");';
const translatedCode = translateCode(code);
+
expect(translatedCode).toBe('bru.getGlobalEnvVar("test");');
});
it('should handle pm.globals.set', () => {
const code = 'pm.globals.set("test", "value");';
const translatedCode = translateCode(code);
+
expect(translatedCode).toBe('bru.setGlobalEnvVar("test", "value");');
});
@@ -66,6 +115,7 @@ describe('Variables Translation', () => {
const get = vars.get("test");
`;
const translatedCode = translateCode(code);
+
expect(translatedCode).toBe(`
const has = bru.hasVar("test");
const set = bru.setVar("test", "value");
@@ -83,6 +133,7 @@ describe('Variables Translation', () => {
const unset = collVars.unset("test");
`;
const translatedCode = translateCode(code);
+
expect(translatedCode).toBe(`
const has = bru.hasVar("test");
const set = bru.setVar("test", "value");
@@ -98,6 +149,7 @@ describe('Variables Translation', () => {
const set = globals.set("test", "value");
`;
const translatedCode = translateCode(code);
+
expect(translatedCode).toBe(`
const get = bru.getGlobalEnvVar("test");
const set = bru.setGlobalEnvVar("test", "value");
@@ -108,6 +160,7 @@ describe('Variables Translation', () => {
it('should handle conditional expressions with variable calls', () => {
const code = 'const userStatus = pm.variables.has("userId") ? "logged-in" : "guest";';
const translatedCode = translateCode(code);
+
expect(translatedCode).toBe('const userStatus = bru.hasVar("userId") ? "logged-in" : "guest";');
});
@@ -148,6 +201,7 @@ describe('Variables Translation', () => {
it('should handle more complex nested expressions with variables', () => {
const code = 'pm.collectionVariables.set("fullPath", pm.environment.get("baseUrl") + pm.variables.get("endpoint"));';
const translatedCode = translateCode(code);
+
expect(translatedCode).toBe('bru.setVar("fullPath", bru.getEnvVar("baseUrl") + bru.getVar("endpoint"));');
});
});
\ No newline at end of file
diff --git a/packages/bruno-js/src/bru.js b/packages/bruno-js/src/bru.js
index 77255b3a1..d38d28983 100644
--- a/packages/bruno-js/src/bru.js
+++ b/packages/bruno-js/src/bru.js
@@ -1,5 +1,5 @@
const { cloneDeep } = require('lodash');
-const { interpolate } = require('@usebruno/common');
+const { interpolate: _interpolate } = require('@usebruno/common');
const variableNameRegex = /^[\w-.]*$/;
@@ -28,10 +28,10 @@ class Bru {
};
}
- _interpolate = (str) => {
- if (!str || !str.length || typeof str !== 'string') {
- return str;
- }
+ interpolate = (strOrObj) => {
+ if (!strOrObj) return strOrObj;
+ const isObj = typeof strOrObj === 'object';
+ const strToInterpolate = isObj ? JSON.stringify(strOrObj) : strOrObj;
const combinedVars = {
...this.globalEnvironmentVariables,
@@ -48,7 +48,8 @@ class Bru {
}
};
- return interpolate(str, combinedVars);
+ const interpolatedStr = _interpolate(strToInterpolate, combinedVars);
+ return isObj ? JSON.parse(interpolatedStr) : interpolatedStr;
};
cwd() {
@@ -68,7 +69,7 @@ class Bru {
}
getEnvVar(key) {
- return this._interpolate(this.envVariables[key]);
+ return this.interpolate(this.envVariables[key]);
}
setEnvVar(key, value) {
@@ -84,7 +85,7 @@ class Bru {
}
getGlobalEnvVar(key) {
- return this._interpolate(this.globalEnvironmentVariables[key]);
+ return this.interpolate(this.globalEnvironmentVariables[key]);
}
setGlobalEnvVar(key, value) {
@@ -96,7 +97,7 @@ class Bru {
}
getOauth2CredentialVar(key) {
- return this._interpolate(this.oauth2CredentialVariables[key]);
+ return this.interpolate(this.oauth2CredentialVariables[key]);
}
hasVar(key) {
@@ -111,7 +112,7 @@ class Bru {
if (variableNameRegex.test(key) === false) {
throw new Error(
`Variable name: "${key}" contains invalid characters!` +
- ' Names must only contain alpha-numeric characters, "-", "_", "."'
+ ' Names must only contain alpha-numeric characters, "-", "_", "."'
);
}
@@ -122,11 +123,11 @@ class Bru {
if (variableNameRegex.test(key) === false) {
throw new Error(
`Variable name: "${key}" contains invalid characters!` +
- ' Names must only contain alpha-numeric characters, "-", "_", "."'
+ ' Names must only contain alpha-numeric characters, "-", "_", "."'
);
}
- return this._interpolate(this.runtimeVariables[key]);
+ return this.interpolate(this.runtimeVariables[key]);
}
deleteVar(key) {
@@ -142,15 +143,15 @@ class Bru {
}
getCollectionVar(key) {
- return this._interpolate(this.collectionVariables[key]);
+ return this.interpolate(this.collectionVariables[key]);
}
getFolderVar(key) {
- return this._interpolate(this.folderVariables[key]);
+ return this.interpolate(this.folderVariables[key]);
}
getRequestVar(key) {
- return this._interpolate(this.requestVariables[key]);
+ return this.interpolate(this.requestVariables[key]);
}
setNextRequest(nextRequest) {
diff --git a/packages/bruno-js/src/runtime/script-runtime.js b/packages/bruno-js/src/runtime/script-runtime.js
index 1d9680bab..a8f2abbee 100644
--- a/packages/bruno-js/src/runtime/script-runtime.js
+++ b/packages/bruno-js/src/runtime/script-runtime.js
@@ -98,7 +98,7 @@ class ScriptRuntime {
};
}
- if(runRequestByItemPathname) {
+ if (runRequestByItemPathname) {
context.bru.runRequest = runRequestByItemPathname;
}
@@ -151,7 +151,7 @@ class ScriptRuntime {
chai,
'node-fetch': fetch,
'crypto-js': CryptoJS,
- 'xml2js': xml2js,
+ xml2js: xml2js,
cheerio,
tv4,
...whitelistedModules,
@@ -235,7 +235,7 @@ class ScriptRuntime {
};
}
- if(runRequestByItemPathname) {
+ if (runRequestByItemPathname) {
context.bru.runRequest = runRequestByItemPathname;
}
diff --git a/packages/bruno-js/src/sandbox/quickjs/shims/bru.js b/packages/bruno-js/src/sandbox/quickjs/shims/bru.js
index b8ffa76ab..8439d7206 100644
--- a/packages/bruno-js/src/sandbox/quickjs/shims/bru.js
+++ b/packages/bruno-js/src/sandbox/quickjs/shims/bru.js
@@ -29,6 +29,12 @@ const addBruShimToContext = (vm, bru) => {
vm.setProp(bruObject, 'getProcessEnv', getProcessEnv);
getProcessEnv.dispose();
+ let interpolate = vm.newFunction('interpolate', function (str) {
+ return marshallToVm(bru.interpolate(vm.dump(str)), vm);
+ });
+ vm.setProp(bruObject, 'interpolate', interpolate);
+ interpolate.dispose();
+
let hasEnvVar = vm.newFunction('hasEnvVar', function (key) {
return marshallToVm(bru.hasEnvVar(vm.dump(key)), vm);
});
@@ -157,7 +163,8 @@ const addBruShimToContext = (vm, bru) => {
let getTestResults = vm.newFunction('getTestResults', () => {
const promise = vm.newPromise();
- bru.getTestResults()
+ bru
+ .getTestResults()
.then((results) => {
promise.resolve(marshallToVm(cleanJson(results), vm));
})
@@ -178,7 +185,8 @@ const addBruShimToContext = (vm, bru) => {
let getAssertionResults = vm.newFunction('getAssertionResults', () => {
const promise = vm.newPromise();
- bru.getAssertionResults()
+ bru
+ .getAssertionResults()
.then((results) => {
promise.resolve(marshallToVm(cleanJson(results), vm));
})
@@ -199,7 +207,8 @@ const addBruShimToContext = (vm, bru) => {
let runRequestHandle = vm.newFunction('runRequest', (args) => {
const promise = vm.newPromise();
- bru.runRequest(vm.dump(args))
+ bru
+ .runRequest(vm.dump(args))
.then((response) => {
const { status, headers, data, dataBuffer, size, statusText } = response || {};
promise.resolve(marshallToVm(cleanJson({ status, statusText, headers, data, dataBuffer, size }), vm));
diff --git a/packages/bruno-tests/collection/scripting/api/bru/interpolate.bru b/packages/bruno-tests/collection/scripting/api/bru/interpolate.bru
new file mode 100644
index 000000000..a8e6dff76
--- /dev/null
+++ b/packages/bruno-tests/collection/scripting/api/bru/interpolate.bru
@@ -0,0 +1,39 @@
+meta {
+ name: interpolate
+ type: http
+ seq: 13
+}
+
+get {
+ url: {{host}}/ping
+ body: none
+ auth: none
+}
+
+tests {
+ test("should interpolate envs", function() {
+ const interpolated = bru.interpolate("url: {{host}}")
+ expect(interpolated).to.equal("url: https://testbench-sanity.usebruno.com");
+ });
+
+ test("should interpolate random variables", function() {
+ const a = bru.interpolate("{{$randomInt}}")
+ const b = bru.interpolate("{{$randomInt}}")
+ expect(a).to.not.equal(b)
+ });
+
+ const randomObj = {
+ host: "{{host}}",
+ int: "{{$randomInt}}",
+ timestamp: "{{$timestamp}}"
+ }
+
+ test("should interpolate objects with vars, random vars", function() {
+ const objA = bru.interpolate(randomObj)
+ const objB = bru.interpolate(randomObj)
+
+ expect(objA).to.be.an("object")
+ expect(objB).to.be.an("object")
+ expect(objA).to.not.deep.eql(objB)
+ });
+}