mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
feat: auto-require Postman sandbox globals during import (#7878)
This commit is contained in:
@@ -670,6 +670,71 @@ function processTransformations(ast, transformedNodes) {
|
||||
});
|
||||
}
|
||||
|
||||
// Postman provides these as sandbox globals. Bruno requires explicit require()
|
||||
const POSTMAN_LIBRARY_GLOBALS = {
|
||||
CryptoJS: 'crypto-js',
|
||||
_: 'lodash',
|
||||
moment: 'moment',
|
||||
cheerio: 'cheerio',
|
||||
tv4: 'tv4'
|
||||
};
|
||||
|
||||
/**
|
||||
* Inject require() for Postman sandbox globals (CryptoJS, _, moment, cheerio,
|
||||
* tv4) used as X.foo or X(...) and not visible in any enclosing scope at the
|
||||
* usage site. Requires are prepended to the program body, sorted alphabetically.
|
||||
*
|
||||
* @param {Collection} ast - jscodeshift AST
|
||||
*/
|
||||
function injectLibraryRequires(ast) {
|
||||
const libraryNames = new Set(Object.keys(POSTMAN_LIBRARY_GLOBALS));
|
||||
const usedLibraries = new Set();
|
||||
|
||||
ast.find(j.Identifier).forEach((path) => {
|
||||
const name = path.value.name;
|
||||
if (!libraryNames.has(name)) return;
|
||||
|
||||
const parent = path.parent.value;
|
||||
|
||||
// check for library usage: X.foo / X['foo'] / X[expr] (X is object) or X(...) (X is callee)
|
||||
const isLibraryUsage
|
||||
= (parent.type === 'MemberExpression' && parent.object === path.value)
|
||||
|| (parent.type === 'CallExpression' && parent.callee === path.value);
|
||||
if (!isLibraryUsage) return;
|
||||
|
||||
// skip if the name is bound in any enclosing scope at this position
|
||||
if (path.scope && path.scope.lookup(name)) return;
|
||||
|
||||
usedLibraries.add(name);
|
||||
});
|
||||
|
||||
if (usedLibraries.size === 0) return;
|
||||
|
||||
const declarations = [...usedLibraries].sort().map((name) =>
|
||||
j.variableDeclaration('const', [
|
||||
j.variableDeclarator(
|
||||
j.identifier(name),
|
||||
j.callExpression(j.identifier('require'), [j.literal(POSTMAN_LIBRARY_GLOBALS[name])])
|
||||
)
|
||||
])
|
||||
);
|
||||
|
||||
// insert after directive prologue if present
|
||||
const body = ast.get().value.program.body;
|
||||
let insertIndex = 0;
|
||||
while (insertIndex < body.length) {
|
||||
const node = body[insertIndex];
|
||||
const isDirective
|
||||
= node.type === 'ExpressionStatement'
|
||||
&& node.expression
|
||||
&& node.expression.type === 'Literal'
|
||||
&& typeof node.expression.value === 'string';
|
||||
if (!isDirective) break;
|
||||
insertIndex++;
|
||||
}
|
||||
body.splice(insertIndex, 0, ...declarations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates Postman script code to Bruno script code
|
||||
* @param {string} code - The Postman script code to translate
|
||||
@@ -700,6 +765,9 @@ function translateCode(code) {
|
||||
// Handle special Postman syntax patterns
|
||||
handleTestsBracketNotation(ast);
|
||||
|
||||
// Inject require() for Postman sandbox globals
|
||||
injectLibraryRequires(ast);
|
||||
|
||||
return ast.toSource();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import translateCode from '../../../../src/utils/postman-to-bruno-translator';
|
||||
|
||||
describe('Postman Library Globals Auto-Require Injection', () => {
|
||||
it('injects require for member-position usage (CryptoJS)', () => {
|
||||
const out = translateCode(`const h = CryptoJS.MD5('hello').toString();`);
|
||||
expect(out).toContain(`const CryptoJS = require("crypto-js")`);
|
||||
expect(out).toContain(`CryptoJS.MD5('hello')`);
|
||||
});
|
||||
|
||||
it('injects require for call-position usage (moment)', () => {
|
||||
const out = translateCode(`const now = moment().format('YYYY');`);
|
||||
expect(out).toContain(`const moment = require("moment")`);
|
||||
expect(out).toContain(`moment().format('YYYY')`);
|
||||
});
|
||||
|
||||
it('injects require for computed member access (CryptoJS["MD5"])', () => {
|
||||
const out = translateCode(`const h = CryptoJS['MD5']('hello').toString();`);
|
||||
expect(out).toContain(`const CryptoJS = require("crypto-js")`);
|
||||
});
|
||||
|
||||
it('injects multiple requires in alphabetical order', () => {
|
||||
const out = translateCode(`
|
||||
const h = CryptoJS.MD5('x').toString();
|
||||
const c = _.chunk([1,2,3], 2);
|
||||
const t = moment().unix();
|
||||
const v = tv4.validate({}, {});
|
||||
`);
|
||||
const idxCrypto = out.indexOf(`require("crypto-js")`);
|
||||
const idxLodash = out.indexOf(`require("lodash")`);
|
||||
const idxMoment = out.indexOf(`require("moment")`);
|
||||
const idxTv4 = out.indexOf(`require("tv4")`);
|
||||
expect(idxCrypto).toBeGreaterThan(-1);
|
||||
expect(idxCrypto).toBeLessThan(idxLodash);
|
||||
expect(idxLodash).toBeLessThan(idxMoment);
|
||||
expect(idxMoment).toBeLessThan(idxTv4);
|
||||
});
|
||||
|
||||
it('does not duplicate when require already exists', () => {
|
||||
const input = `const CryptoJS = require('crypto-js'); const h = CryptoJS.MD5('x');`;
|
||||
const out = translateCode(input);
|
||||
const matches = out.match(/require\(["']crypto-js["']\)/g) || [];
|
||||
expect(matches).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('does not inject when library name is only a property access', () => {
|
||||
const out = translateCode(`obj.CryptoJS.doThing();`);
|
||||
expect(out).not.toContain(`require("crypto-js")`);
|
||||
});
|
||||
|
||||
it('does not inject on script with no library usage', () => {
|
||||
const input = `const x = 1 + 2;`;
|
||||
const out = translateCode(input);
|
||||
expect(out).not.toContain('require(');
|
||||
expect(out.trim()).toBe(input.trim());
|
||||
});
|
||||
|
||||
it('still injects when name is shadowed only in an inner function/IIFE scope', () => {
|
||||
const out = translateCode(`
|
||||
(() => { const _ = 1; return _; })();
|
||||
_.chunk([1, 2], 1);
|
||||
`);
|
||||
expect(out).toContain(`const _ = require("lodash")`);
|
||||
});
|
||||
|
||||
it('preserves "use strict" directive prologue when injecting requires', () => {
|
||||
const out = translateCode(`'use strict';\nmoment().format('YYYY');`);
|
||||
const directiveIdx = out.indexOf('\'use strict\'');
|
||||
const requireIdx = out.indexOf(`require("moment")`);
|
||||
expect(directiveIdx).toBeGreaterThanOrEqual(0);
|
||||
expect(requireIdx).toBeGreaterThan(directiveIdx);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user