Merge branch 'main' into revert/rename-item-watcher-fix

This commit is contained in:
lohit 2024-12-16 14:52:52 +05:30 committed by GitHub
commit 0d126abfbd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 108 additions and 90 deletions

View File

@ -34,7 +34,7 @@ const RenameCollectionItem = ({ collection, item, onClose }) => {
} }
dispatch(renameItem(values.name, item.uid, collection.uid)) dispatch(renameItem(values.name, item.uid, collection.uid))
.then(() => { .then(() => {
dispatch(closeTabs({ tabUids: [item.uid] })); isFolder && dispatch(closeTabs({ tabUids: [item.uid] }));
toast.success(isFolder ? 'Folder renamed' : 'Request renamed'); toast.success(isFolder ? 'Folder renamed' : 'Request renamed');
onClose(); onClose();
}) })

View File

@ -86,11 +86,12 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
} catch (err) {} } catch (err) {}
} }
} else if (contentType === 'multipart/form-data') { } else if (contentType === 'multipart/form-data') {
if (typeof request.data === 'object' && !(request?.data instanceof FormData)) { if (Array.isArray(request?.data) && !(request.data instanceof FormData)) {
try { try {
forOwn(request?.data, (value, key) => { request.data = request?.data?.map(d => ({
request.data[key] = _interpolate(value); ...d,
}); value: _interpolate(d?.value)
}));
} catch (err) {} } catch (err) {}
} }
} else { } else {

View File

@ -2,35 +2,7 @@ const { get, each, filter } = require('lodash');
const decomment = require('decomment'); const decomment = require('decomment');
const crypto = require('node:crypto'); const crypto = require('node:crypto');
const { mergeHeaders, mergeScripts, mergeVars, getTreePathFromCollectionToItem } = require('../utils/collection'); const { mergeHeaders, mergeScripts, mergeVars, getTreePathFromCollectionToItem } = require('../utils/collection');
const { createFormData } = require('../utils/form-data');
const createFormData = (datas, collectionPath) => {
// make axios work in node using form data
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
const form = new FormData();
datas.forEach((item) => {
const value = item.value;
const name = item.name;
let options = {};
if (item.contentType) {
options.contentType = item.contentType;
}
if (item.type === 'file') {
const filePaths = value || [];
filePaths.forEach((filePath) => {
let trimmedFilePath = filePath.trim();
if (!path.isAbsolute(trimmedFilePath)) {
trimmedFilePath = path.join(collectionPath, trimmedFilePath);
}
options.filename = path.basename(trimmedFilePath);
form.append(name, fs.createReadStream(trimmedFilePath), options);
});
} else {
form.append(name, value, options);
}
});
return form;
};
const prepareRequest = (item = {}, collection = {}) => { const prepareRequest = (item = {}, collection = {}) => {
const request = item?.request; const request = item?.request;
@ -164,7 +136,8 @@ const prepareRequest = (item = {}, collection = {}) => {
if (request.body.mode === 'multipartForm') { if (request.body.mode === 'multipartForm') {
axiosRequest.headers['content-type'] = 'multipart/form-data'; axiosRequest.headers['content-type'] = 'multipart/form-data';
const enabledParams = filter(request.body.multipartForm, (p) => p.enabled); const enabledParams = filter(request.body.multipartForm, (p) => p.enabled);
axiosRequest.data = createFormData(enabledParams); const collectionPath = process.cwd();
axiosRequest.data = createFormData(enabledParams, collectionPath);
} }
if (request.body.mode === 'graphql') { if (request.body.mode === 'graphql') {

View File

@ -19,8 +19,9 @@ const { makeAxiosInstance } = require('../utils/axios-instance');
const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-helper'); const { addAwsV4Interceptor, resolveAwsV4Credentials } = require('./awsv4auth-helper');
const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../utils/proxy-util'); const { shouldUseProxy, PatchedHttpsProxyAgent } = require('../utils/proxy-util');
const path = require('path'); const path = require('path');
const { createFormData, parseDataFromResponse } = require('../utils/common'); const { parseDataFromResponse } = require('../utils/common');
const { getCookieStringForUrl, saveCookies, shouldUseCookies } = require('../utils/cookies'); const { getCookieStringForUrl, saveCookies, shouldUseCookies } = require('../utils/cookies');
const { createFormData } = require('../utils/form-data');
const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/; const protocolRegex = /^([-+\w]{1,25})(:?\/\/|:)/;
const onConsoleLog = (type, args) => { const onConsoleLog = (type, args) => {

View File

@ -20,30 +20,6 @@ const rpad = (str, width) => {
return paddedStr; return paddedStr;
}; };
const createFormData = (datas, collectionPath) => {
// make axios work in node using form data
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
const form = new FormData();
forOwn(datas, (value, key) => {
if (typeof value == 'string') {
form.append(key, value);
return;
}
const filePaths = value || [];
filePaths?.forEach?.((filePath) => {
let trimmedFilePath = filePath.trim();
if (!path.isAbsolute(trimmedFilePath)) {
trimmedFilePath = path.join(collectionPath, trimmedFilePath);
}
form.append(key, fs.createReadStream(trimmedFilePath), path.basename(trimmedFilePath));
});
});
return form;
};
const parseDataFromResponse = (response, disableParsingResponseJson = false) => { const parseDataFromResponse = (response, disableParsingResponseJson = false) => {
// Parse the charset from content type: https://stackoverflow.com/a/33192813 // Parse the charset from content type: https://stackoverflow.com/a/33192813
const charsetMatch = /charset=([^()<>@,;:"/[\]?.=\s]*)/i.exec(response.headers['content-type'] || ''); const charsetMatch = /charset=([^()<>@,;:"/[\]?.=\s]*)/i.exec(response.headers['content-type'] || '');
@ -73,6 +49,5 @@ const parseDataFromResponse = (response, disableParsingResponseJson = false) =>
module.exports = { module.exports = {
lpad, lpad,
rpad, rpad,
createFormData,
parseDataFromResponse parseDataFromResponse
}; };

View File

@ -0,0 +1,42 @@
const { forEach } = require('lodash');
const FormData = require('form-data');
const fs = require('fs');
const path = require('path');
const createFormData = (data, collectionPath) => {
// make axios work in node using form data
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
const form = new FormData();
forEach(data, (datum) => {
const { name, type, value, contentType } = datum;
let options = {};
if (contentType) {
options.contentType = contentType;
}
if (type === 'text') {
if (Array.isArray(value)) {
value.forEach((val) => form.append(name, val, options));
} else {
form.append(name, value, options);
}
return;
}
if (type === 'file') {
const filePaths = value || [];
filePaths.forEach((filePath) => {
let trimmedFilePath = filePath.trim();
if (!path.isAbsolute(trimmedFilePath)) {
trimmedFilePath = path.join(collectionPath, trimmedFilePath);
}
options.filename = path.basename(trimmedFilePath);
form.append(name, fs.createReadStream(trimmedFilePath), options);
});
}
});
return form;
};
module.exports = {
createFormData
}

View File

@ -350,7 +350,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
ipcMain.handle('renderer:rename-item', async (event, oldPath, newPath, newName) => { ipcMain.handle('renderer:rename-item', async (event, oldPath, newPath, newName) => {
const tempDir = path.join(os.tmpdir(), `temp-folder-${Date.now()}`); const tempDir = path.join(os.tmpdir(), `temp-folder-${Date.now()}`);
// const parentDir = path.dirname(oldPath); // const parentDir = path.dirname(oldPath);
const isWindowsOSAndNotWSLAndItemHasSubDirectories = isWindowsOS() && !isWSLPath(oldPath) && hasSubDirectories(oldPath); const isWindowsOSAndNotWSLAndItemHasSubDirectories = isDirectory(oldPath) && isWindowsOS() && !isWSLPath(oldPath) && hasSubDirectories(oldPath);
// let parentDirUnwatched = false; // let parentDirUnwatched = false;
// let parentDirRewatched = false; // let parentDirRewatched = false;

View File

@ -86,11 +86,12 @@ const interpolateVars = (request, envVariables = {}, runtimeVariables = {}, proc
} catch (err) {} } catch (err) {}
} }
} else if (contentType === 'multipart/form-data') { } else if (contentType === 'multipart/form-data') {
if (typeof request.data === 'object' && !(request.data instanceof FormData)) { if (Array.isArray(request?.data) && !(request.data instanceof FormData)) {
try { try {
forOwn(request?.data, (value, key) => { request.data = request?.data?.map(d => ({
request.data[key] = _interpolate(value); ...d,
}); value: _interpolate(d?.value)
}));
} catch (err) {} } catch (err) {}
} }
} else { } else {

View File

@ -249,7 +249,7 @@ const prepareRequest = (item, collection) => {
axiosRequest.headers['content-type'] = 'multipart/form-data'; axiosRequest.headers['content-type'] = 'multipart/form-data';
} }
const enabledParams = filter(request.body.multipartForm, (p) => p.enabled); const enabledParams = filter(request.body.multipartForm, (p) => p.enabled);
axiosRequest.data = createFormData(enabledParams); axiosRequest.data = enabledParams;
} }
if (request.body.mode === 'graphql') { if (request.body.mode === 'graphql') {
@ -268,6 +268,10 @@ const prepareRequest = (item, collection) => {
axiosRequest.script = request.script; axiosRequest.script = request.script;
} }
if (request.tests) {
axiosRequest.tests = request.tests;
}
axiosRequest.vars = request.vars; axiosRequest.vars = request.vars;
axiosRequest.collectionVariables = request.collectionVariables; axiosRequest.collectionVariables = request.collectionVariables;
axiosRequest.folderVariables = request.folderVariables; axiosRequest.folderVariables = request.folderVariables;

View File

@ -1,4 +1,4 @@
const { forOwn } = require('lodash'); const { forEach } = require('lodash');
const FormData = require('form-data'); const FormData = require('form-data');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
@ -27,13 +27,16 @@ const createFormData = (data, collectionPath) => {
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427 // reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
const form = new FormData(); const form = new FormData();
forEach(data, (datum) => { forEach(data, (datum) => {
const { name, type, value } = datum; const { name, type, value, contentType } = datum;
let options = {};
if (contentType) {
options.contentType = contentType;
}
if (type === 'text') { if (type === 'text') {
if (Array.isArray(value)) { if (Array.isArray(value)) {
value.forEach((val) => form.append(name, val)); value.forEach((val) => form.append(name, val, options));
} else { } else {
form.append(name, value); form.append(name, value, options);
} }
return; return;
} }
@ -42,12 +45,11 @@ const createFormData = (data, collectionPath) => {
const filePaths = value || []; const filePaths = value || [];
filePaths.forEach((filePath) => { filePaths.forEach((filePath) => {
let trimmedFilePath = filePath.trim(); let trimmedFilePath = filePath.trim();
if (!path.isAbsolute(trimmedFilePath)) { if (!path.isAbsolute(trimmedFilePath)) {
trimmedFilePath = path.join(collectionPath, trimmedFilePath); trimmedFilePath = path.join(collectionPath, trimmedFilePath);
} }
options.filename = path.basename(trimmedFilePath);
form.append(name, fs.createReadStream(trimmedFilePath), path.basename(trimmedFilePath)); form.append(name, fs.createReadStream(trimmedFilePath), options);
}); });
} }
}); });

View File

@ -18,8 +18,8 @@ const JS_KEYWORDS = `
* ```js * ```js
* res.data.pets.map(pet => pet.name.toUpperCase()) * res.data.pets.map(pet => pet.name.toUpperCase())
* *
* function(context) { * function(__bruno__functionInnerContext) {
* const { res, pet } = context; * const { res, pet } = __bruno__functionInnerContext;
* return res.data.pets.map(pet => pet.name.toUpperCase()) * return res.data.pets.map(pet => pet.name.toUpperCase())
* } * }
* ``` * ```
@ -45,9 +45,11 @@ const compileJsExpression = (expr) => {
globals: globals.map((name) => ` ${name} = ${name} ?? globalThis.${name};`).join('') globals: globals.map((name) => ` ${name} = ${name} ?? globalThis.${name};`).join('')
}; };
const body = `let { ${code.vars} } = context; ${code.globals}; return ${expr}`; // param name that is unlikely to show up as a var in an expression
const param = `__bruno__functionInnerContext`;
const body = `let { ${code.vars} } = ${param}; ${code.globals}; return ${expr}`;
return new Function('context', body); return new Function(param, body);
}; };
const internalExpressionCache = new Map(); const internalExpressionCache = new Map();

View File

@ -5,7 +5,9 @@ describe('utils', () => {
describe('expression evaluation', () => { describe('expression evaluation', () => {
const context = { const context = {
res: { res: {
data: { pets: ['bruno', 'max'] } data: { pets: ['bruno', 'max'] },
context: 'testContext',
__bruno__functionInnerContext: 0
} }
}; };
@ -45,32 +47,32 @@ describe('utils', () => {
it('should identify top level variables', () => { it('should identify top level variables', () => {
const expr = 'res.data.pets[0].toUpperCase()'; const expr = 'res.data.pets[0].toUpperCase()';
evaluateJsExpression(expr, context); evaluateJsExpression(expr, context);
expect(cache.get(expr).toString()).toContain('let { res } = context;'); expect(cache.get(expr).toString()).toContain('let { res } = __bruno__functionInnerContext;');
}); });
it('should not duplicate variables', () => { it('should not duplicate variables', () => {
const expr = 'res.data.pets[0] + res.data.pets[1]'; const expr = 'res.data.pets[0] + res.data.pets[1]';
evaluateJsExpression(expr, context); evaluateJsExpression(expr, context);
expect(cache.get(expr).toString()).toContain('let { res } = context;'); expect(cache.get(expr).toString()).toContain('let { res } = __bruno__functionInnerContext;');
}); });
it('should exclude js keywords like true false from vars', () => { it('should exclude js keywords like true false from vars', () => {
const expr = 'res.data.pets.length > 0 ? true : false'; const expr = 'res.data.pets.length > 0 ? true : false';
evaluateJsExpression(expr, context); evaluateJsExpression(expr, context);
expect(cache.get(expr).toString()).toContain('let { res } = context;'); expect(cache.get(expr).toString()).toContain('let { res } = __bruno__functionInnerContext;');
}); });
it('should exclude numbers from vars', () => { it('should exclude numbers from vars', () => {
const expr = 'res.data.pets.length + 10'; const expr = 'res.data.pets.length + 10';
evaluateJsExpression(expr, context); evaluateJsExpression(expr, context);
expect(cache.get(expr).toString()).toContain('let { res } = context;'); expect(cache.get(expr).toString()).toContain('let { res } = __bruno__functionInnerContext;');
}); });
it('should pick variables from complex expressions', () => { it('should pick variables from complex expressions', () => {
const expr = 'res.data.pets.map(pet => pet.length)'; const expr = 'res.data.pets.map(pet => pet.length)';
const result = evaluateJsExpression(expr, context); const result = evaluateJsExpression(expr, context);
expect(result).toEqual([5, 3]); expect(result).toEqual([5, 3]);
expect(cache.get(expr).toString()).toContain('let { res, pet } = context;'); expect(cache.get(expr).toString()).toContain('let { res, pet } = __bruno__functionInnerContext;');
}); });
it('should be ok picking extra vars from strings', () => { it('should be ok picking extra vars from strings', () => {
@ -78,7 +80,7 @@ describe('utils', () => {
const result = evaluateJsExpression(expr, context); const result = evaluateJsExpression(expr, context);
expect(result).toBe('hello bruno'); expect(result).toBe('hello bruno');
// extra var hello is harmless // extra var hello is harmless
expect(cache.get(expr).toString()).toContain('let { hello, res } = context;'); expect(cache.get(expr).toString()).toContain('let { hello, res } = __bruno__functionInnerContext;');
}); });
it('should evaluate expressions referencing globals', () => { it('should evaluate expressions referencing globals', () => {
@ -112,6 +114,20 @@ describe('utils', () => {
expect(result).toBe(startTime); expect(result).toBe(startTime);
}); });
it('should allow "context" as a var name', () => {
const expr = 'res["context"].toUpperCase()';
evaluateJsExpression(expr, context);
expect(cache.get(expr).toString()).toContain('let { res, context } = __bruno__functionInnerContext;');
});
it('should throw an error when we use "__bruno__functionInnerContext" as a var name', () => {
const expr = 'res["__bruno__functionInnerContext"].toUpperCase()';
expect(() => evaluateJsExpression(expr, context)).toThrow(SyntaxError);
expect(() => evaluateJsExpression(expr, context)).toThrow(
"Identifier '__bruno__functionInnerContext' has already been declared"
);
});
}); });
describe('response parser', () => { describe('response parser', () => {

View File

@ -162,7 +162,7 @@ const mapRequestParams = (pairList = [], type) => {
const multipartExtractContentType = (pair) => { const multipartExtractContentType = (pair) => {
if (_.isString(pair.value)) { if (_.isString(pair.value)) {
const match = pair.value.match(/^(.*?)\s*\(Content-Type=(.*?)\)\s*$/); const match = pair.value.match(/^(.*?)\s*@contentType\((.*?)\)\s*$/);
if (match != null && match.length > 2) { if (match != null && match.length > 2) {
pair.value = match[1]; pair.value = match[1];
pair.contentType = match[2]; pair.contentType = match[2];

View File

@ -281,7 +281,7 @@ ${indentString(body.sparql)}
.map((item) => { .map((item) => {
const enabled = item.enabled ? '' : '~'; const enabled = item.enabled ? '' : '~';
const contentType = const contentType =
item.contentType && item.contentType !== '' ? ' (Content-Type=' + item.contentType + ')' : ''; item.contentType && item.contentType !== '' ? ' @contentType(' + item.contentType + ')' : '';
if (item.type === 'text') { if (item.type === 'text') {
return `${enabled}${item.name}: ${getValueString(item.value)}${contentType}`; return `${enabled}${item.name}: ${getValueString(item.value)}${contentType}`;

View File

@ -12,13 +12,13 @@ post {
body:multipart-form { body:multipart-form {
param1: test param1: test
param2: {"test":"i am json"} (Content-Type=application/json) param2: {"test":"i am json"} @contentType(application/json)
param3: @file(multipart/small.png) param3: @file(multipart/small.png)
} }
assert { assert {
res.status: eq 200 res.status: eq 200
res.body.find(p=>p.name === 'param1').contentType: isUndefined res.body.find(p=>p.name === 'param1').contentType: isUndefined
res.body.find(p=>p.name === 'param2').contentType: eq application/json res.body.find(p=>p.name === 'param2').contentType: eq application/json
res.body.find(p=>p.name === 'param3').contentType: eq image/png res.body.find(p=>p.name === 'param3').contentType: eq image/png
} }

View File

@ -5,6 +5,7 @@ const formDataParser = require('./multipart/form-data-parser');
const authRouter = require('./auth'); const authRouter = require('./auth');
const echoRouter = require('./echo'); const echoRouter = require('./echo');
const xmlParser = require('./utils/xmlParser'); const xmlParser = require('./utils/xmlParser');
const multipartRouter = require('./multipart');
const app = new express(); const app = new express();
const port = process.env.PORT || 8080; const port = process.env.PORT || 8080;