From 60cf012cbe64911a3c8b0e4e853b2164067f697f Mon Sep 17 00:00:00 2001 From: Mateusz Pietryga Date: Mon, 6 May 2024 12:48:53 +0200 Subject: [PATCH 01/61] Fix: CLI Test Runner ignores auth:"inherit" in requests (#2206) (#2243) #1667 - None means None, but inherit means inherit --- packages/bruno-cli/src/runner/prepare-request.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bruno-cli/src/runner/prepare-request.js b/packages/bruno-cli/src/runner/prepare-request.js index 9dcd8d704..8b1b249db 100644 --- a/packages/bruno-cli/src/runner/prepare-request.js +++ b/packages/bruno-cli/src/runner/prepare-request.js @@ -33,7 +33,7 @@ const prepareRequest = (request, collectionRoot) => { }; const collectionAuth = get(collectionRoot, 'request.auth'); - if (collectionAuth && request.auth.mode === 'none') { + if (collectionAuth && request.auth.mode === 'inherit') { if (collectionAuth.mode === 'basic') { axiosRequest.auth = { username: get(collectionAuth, 'basic.username'), From 411289daa837bc547a7630b18289c3ad690b44c7 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Mon, 6 May 2024 16:24:00 +0530 Subject: [PATCH 02/61] chore: bumped cli version --- package-lock.json | 97 +++++++++++++++++++++++++++++++-- packages/bruno-cli/package.json | 4 +- packages/bruno-js/package.json | 2 +- 3 files changed, 95 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index a29edccf8..5dab86d0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18685,12 +18685,12 @@ }, "packages/bruno-cli": { "name": "@usebruno/cli", - "version": "1.14.0", + "version": "1.16.0", "license": "MIT", "dependencies": { "@aws-sdk/credential-providers": "3.525.0", "@usebruno/common": "0.1.0", - "@usebruno/js": "0.11.0", + "@usebruno/js": "0.12.0", "@usebruno/lang": "0.12.0", "aws4-axios": "^3.3.0", "axios": "^1.5.1", @@ -19729,7 +19729,7 @@ }, "packages/bruno-electron": { "name": "bruno", - "version": "v1.14.0", + "version": "v1.16.1", "dependencies": { "@aws-sdk/credential-providers": "3.525.0", "@usebruno/common": "0.1.0", @@ -20762,6 +20762,48 @@ "node": ">=14.0.0" } }, + "packages/bruno-electron/node_modules/@usebruno/js": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@usebruno/js/-/js-0.11.0.tgz", + "integrity": "sha512-csbKAnFtLTupG46aMIahRWeNW8/rdDeaZPtvr4kIzcHDPNmKNowcFcrIR4VSpceh47Ltpm8n0sgzrHzw8P8Yjg==", + "dependencies": { + "@usebruno/query": "0.1.0", + "ajv": "^8.12.0", + "ajv-formats": "^2.1.1", + "atob": "^2.1.2", + "axios": "^1.5.1", + "btoa": "^1.2.1", + "chai": "^4.3.7", + "chai-string": "^1.5.0", + "crypto-js": "^4.1.1", + "handlebars": "^4.7.8", + "json-query": "^2.2.2", + "lodash": "^4.17.21", + "moment": "^2.29.4", + "nanoid": "3.3.4", + "node-fetch": "2.*", + "node-vault": "^0.10.2", + "uuid": "^9.0.0" + }, + "peerDependencies": { + "@n8n/vm2": "^3.9.23" + } + }, + "packages/bruno-electron/node_modules/ajv": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "packages/bruno-electron/node_modules/fs-extra": { "version": "10.1.0", "license": "MIT", @@ -20774,6 +20816,11 @@ "node": ">=12" } }, + "packages/bruno-electron/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "packages/bruno-graphql-docs": { "name": "@usebruno/graphql-docs", "version": "0.1.0", @@ -20803,7 +20850,7 @@ }, "packages/bruno-js": { "name": "@usebruno/js", - "version": "0.11.0", + "version": "0.12.0", "license": "MIT", "dependencies": { "@usebruno/query": "0.1.0", @@ -24326,7 +24373,7 @@ "requires": { "@aws-sdk/credential-providers": "3.525.0", "@usebruno/common": "0.1.0", - "@usebruno/js": "0.11.0", + "@usebruno/js": "0.12.0", "@usebruno/lang": "0.12.0", "aws4-axios": "^3.3.0", "axios": "^1.5.1", @@ -26992,6 +27039,41 @@ "tslib": "^2.5.0" } }, + "@usebruno/js": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@usebruno/js/-/js-0.11.0.tgz", + "integrity": "sha512-csbKAnFtLTupG46aMIahRWeNW8/rdDeaZPtvr4kIzcHDPNmKNowcFcrIR4VSpceh47Ltpm8n0sgzrHzw8P8Yjg==", + "requires": { + "@usebruno/query": "0.1.0", + "ajv": "^8.12.0", + "ajv-formats": "^2.1.1", + "atob": "^2.1.2", + "axios": "^1.5.1", + "btoa": "^1.2.1", + "chai": "^4.3.7", + "chai-string": "^1.5.0", + "crypto-js": "^4.1.1", + "handlebars": "^4.7.8", + "json-query": "^2.2.2", + "lodash": "^4.17.21", + "moment": "^2.29.4", + "nanoid": "3.3.4", + "node-fetch": "2.*", + "node-vault": "^0.10.2", + "uuid": "^9.0.0" + } + }, + "ajv": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "requires": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + } + }, "fs-extra": { "version": "10.1.0", "requires": { @@ -26999,6 +27081,11 @@ "jsonfile": "^6.0.1", "universalify": "^2.0.0" } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" } } }, diff --git a/packages/bruno-cli/package.json b/packages/bruno-cli/package.json index 17820d9d0..c8e3b72ce 100644 --- a/packages/bruno-cli/package.json +++ b/packages/bruno-cli/package.json @@ -1,6 +1,6 @@ { "name": "@usebruno/cli", - "version": "1.14.0", + "version": "1.16.0", "license": "MIT", "main": "src/index.js", "bin": { @@ -26,7 +26,7 @@ "dependencies": { "@aws-sdk/credential-providers": "3.525.0", "@usebruno/common": "0.1.0", - "@usebruno/js": "0.11.0", + "@usebruno/js": "0.12.0", "@usebruno/lang": "0.12.0", "aws4-axios": "^3.3.0", "axios": "^1.5.1", diff --git a/packages/bruno-js/package.json b/packages/bruno-js/package.json index c5b992629..8682608d2 100644 --- a/packages/bruno-js/package.json +++ b/packages/bruno-js/package.json @@ -1,6 +1,6 @@ { "name": "@usebruno/js", - "version": "0.11.0", + "version": "0.12.0", "license": "MIT", "main": "src/index.js", "files": [ From e149c8dc9a3b49aa9f4e8a947de5135ee0fca92b Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Tue, 7 May 2024 23:17:24 +0530 Subject: [PATCH 03/61] release: v1.17.0 --- package-lock.json | 36 +--- .../bruno-app/src/components/Sidebar/index.js | 2 +- .../src/providers/App/useTelemetry.js | 2 +- packages/bruno-electron/package.json | 4 +- .../collection/echo/supermetrics.bru | 139 ++++++++++++++ packages/bruno-tests/collection/lib/notes.js | 180 ++++++++++++++++++ 6 files changed, 327 insertions(+), 36 deletions(-) create mode 100644 packages/bruno-tests/collection/echo/supermetrics.bru create mode 100644 packages/bruno-tests/collection/lib/notes.js diff --git a/package-lock.json b/package-lock.json index 5dab86d0f..a9f140c6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19729,11 +19729,11 @@ }, "packages/bruno-electron": { "name": "bruno", - "version": "v1.16.1", + "version": "v1.17.0", "dependencies": { "@aws-sdk/credential-providers": "3.525.0", "@usebruno/common": "0.1.0", - "@usebruno/js": "0.11.0", + "@usebruno/js": "0.12.0", "@usebruno/lang": "0.12.0", "@usebruno/schema": "0.7.0", "about-window": "^1.15.2", @@ -20762,33 +20762,6 @@ "node": ">=14.0.0" } }, - "packages/bruno-electron/node_modules/@usebruno/js": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@usebruno/js/-/js-0.11.0.tgz", - "integrity": "sha512-csbKAnFtLTupG46aMIahRWeNW8/rdDeaZPtvr4kIzcHDPNmKNowcFcrIR4VSpceh47Ltpm8n0sgzrHzw8P8Yjg==", - "dependencies": { - "@usebruno/query": "0.1.0", - "ajv": "^8.12.0", - "ajv-formats": "^2.1.1", - "atob": "^2.1.2", - "axios": "^1.5.1", - "btoa": "^1.2.1", - "chai": "^4.3.7", - "chai-string": "^1.5.0", - "crypto-js": "^4.1.1", - "handlebars": "^4.7.8", - "json-query": "^2.2.2", - "lodash": "^4.17.21", - "moment": "^2.29.4", - "nanoid": "3.3.4", - "node-fetch": "2.*", - "node-vault": "^0.10.2", - "uuid": "^9.0.0" - }, - "peerDependencies": { - "@n8n/vm2": "^3.9.23" - } - }, "packages/bruno-electron/node_modules/ajv": { "version": "8.13.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", @@ -26185,7 +26158,7 @@ "requires": { "@aws-sdk/credential-providers": "3.525.0", "@usebruno/common": "0.1.0", - "@usebruno/js": "0.11.0", + "@usebruno/js": "0.12.0", "@usebruno/lang": "0.12.0", "@usebruno/schema": "0.7.0", "about-window": "^1.15.2", @@ -27040,8 +27013,7 @@ } }, "@usebruno/js": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@usebruno/js/-/js-0.11.0.tgz", + "version": "https://registry.npmjs.org/@usebruno/js/-/js-0.11.0.tgz", "integrity": "sha512-csbKAnFtLTupG46aMIahRWeNW8/rdDeaZPtvr4kIzcHDPNmKNowcFcrIR4VSpceh47Ltpm8n0sgzrHzw8P8Yjg==", "requires": { "@usebruno/query": "0.1.0", diff --git a/packages/bruno-app/src/components/Sidebar/index.js b/packages/bruno-app/src/components/Sidebar/index.js index 7902eac65..00ce4345c 100644 --- a/packages/bruno-app/src/components/Sidebar/index.js +++ b/packages/bruno-app/src/components/Sidebar/index.js @@ -129,7 +129,7 @@ const Sidebar = () => { Star */} -
v1.16.1
+
v1.17.0
diff --git a/packages/bruno-app/src/providers/App/useTelemetry.js b/packages/bruno-app/src/providers/App/useTelemetry.js index c3588c010..98ba63fc3 100644 --- a/packages/bruno-app/src/providers/App/useTelemetry.js +++ b/packages/bruno-app/src/providers/App/useTelemetry.js @@ -60,7 +60,7 @@ const trackStart = () => { event: 'start', properties: { os: platformLib.os.family, - version: '1.16.1' + version: '1.17.0' } }); }; diff --git a/packages/bruno-electron/package.json b/packages/bruno-electron/package.json index 866cb70ab..3c187d145 100644 --- a/packages/bruno-electron/package.json +++ b/packages/bruno-electron/package.json @@ -1,5 +1,5 @@ { - "version": "v1.16.1", + "version": "v1.17.0", "name": "bruno", "description": "Opensource API Client for Exploring and Testing APIs", "homepage": "https://www.usebruno.com", @@ -21,7 +21,7 @@ "dependencies": { "@aws-sdk/credential-providers": "3.525.0", "@usebruno/common": "0.1.0", - "@usebruno/js": "0.11.0", + "@usebruno/js": "0.12.0", "@usebruno/lang": "0.12.0", "@usebruno/schema": "0.7.0", "about-window": "^1.15.2", diff --git a/packages/bruno-tests/collection/echo/supermetrics.bru b/packages/bruno-tests/collection/echo/supermetrics.bru new file mode 100644 index 000000000..20c4eb281 --- /dev/null +++ b/packages/bruno-tests/collection/echo/supermetrics.bru @@ -0,0 +1,139 @@ +meta { + name: supermetrics json visualize + type: http + seq: 5 +} + +post { + url: https://testbench-sanity.usebruno.com/api/echo/json + body: json + auth: none +} + +headers { + foo: bar +} + +auth:basic { + username: asd + password: j +} + +auth:bearer { + token: +} + +body:json { + { + "notes": { + "runtime_sec": 5, + "result_rows": 4, + "query_count": 6 + }, + "fields": [ + { + "name": "Count", + "type": "number", + "data_type": "number" + }, + { + "name": "Name", + "type": "string", + "data_type": "string" + }, + { + "name": "Age", + "type": "number", + "data_type": "number" + }, + { + "name": "Email", + "type": "email", + "data_type": "string" + }, + { + "name": "Place", + "type": "string", + "data_type": "string" + } + ], + "data": [ + { + "id": null, + "name": "Name", + "age": "Age", + "email": "Email", + "city": "City" + }, + { + "id": 1, + "name": "John Doe", + "age": 30, + "email": "john@example.com", + "city": "New York" + }, + { + "id": 2, + "name": "Jane Smith", + "age": 25, + "email": "jane@example.com", + "city": "Los Angeles" + }, + { + "id": 3, + "name": "Bob Johnson", + "age": 35, + "email": "bob@example.com", + "city": "Chicago" + }, + { + "id": 4, + "name": "Alice Williams", + "age": 28, + "email": "alice@example.com", + "city": "San Francisco" + } + ] + } + +} + +assert { + res.status: eq 200 +} + +script:pre-request { + const { URL } = require('url'); + + const reqUrl = new URL(req.url); + + reqUrl.searchParams.delete('returnQueryIDafterSeconds'); + reqUrl.searchParams.delete('separateFetchQuery'); + reqUrl.searchParams.delete('pretty'); + reqUrl.searchParams.delete('displayLog'); + reqUrl.searchParams.delete('triggerID'); + reqUrl.searchParams.delete('hashForQIDFDB'); + reqUrl.searchParams.delete('queryType'); + reqUrl.searchParams.delete('queryCount'); + + reqUrl.searchParams.append("cacheBuster", new Date().toISOString()); + + req.setUrl(reqUrl.href); + +} + +script:post-response { + const visualizeNotes = require("./lib/notes"); + + bru.visualize(visualizeNotes(res)); +} + +tests { + test("should return json", function() { + const data = res.getBody(); + expect(res.getBody()).to.eql({ + "hello": "bruno" + }); + }); + +} diff --git a/packages/bruno-tests/collection/lib/notes.js b/packages/bruno-tests/collection/lib/notes.js new file mode 100644 index 000000000..800a49a25 --- /dev/null +++ b/packages/bruno-tests/collection/lib/notes.js @@ -0,0 +1,180 @@ +const visualizeNotes = (res) => { + let response = res.body; + + let notes = response?.notes || {}; + let responseRuntime = notes.runtime_sec || 0; + + notes.runtime = new Date(responseRuntime * 1000).toISOString().substr(11, 8); + + if (typeof response?.data === 'undefined' && typeof response?.rows === 'object') { + response.data = response?.rows?.map(function (data) { + return data?.values; + }); + } + + const templateScript = ` + + `; + + const mainScript = ` + + `; + + const style = ` + + `; + + const htmlString = ` + + + ${style} + + + + ${templateScript} + ${mainScript} + + + `; + + return htmlString; +}; + +module.exports = visualizeNotes; From 4df78910f50230ec3498f72aa91280831f0794e2 Mon Sep 17 00:00:00 2001 From: Lallu Anthoor Date: Mon, 13 May 2024 21:22:05 +0530 Subject: [PATCH 04/61] feat: allow translation of more postman scripts (#2054) --- .../src/utils/importers/translators/index.spec.js | 11 ++++++++--- .../importers/translators/postman_translation.js | 6 +++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/bruno-app/src/utils/importers/translators/index.spec.js b/packages/bruno-app/src/utils/importers/translators/index.spec.js index bbe82bcb5..6f9d2fb1a 100644 --- a/packages/bruno-app/src/utils/importers/translators/index.spec.js +++ b/packages/bruno-app/src/utils/importers/translators/index.spec.js @@ -9,6 +9,8 @@ describe('postmanTranslation function', () => { pm.variables.set('key', 'value'); pm.collectionVariables.get('key'); pm.collectionVariables.set('key', 'value'); + const data = pm.response.json(); + pm.expect(pm.environment.has('key')).to.be.true; `; const expectedOutput = ` bru.getEnvVar('key'); @@ -17,6 +19,8 @@ describe('postmanTranslation function', () => { bru.setVar('key', 'value'); bru.getVar('key'); bru.setVar('key', 'value'); + const data = res.getBody(); + expect(bru.getEnvVar('key') !== undefined && bru.getEnvVar('key') !== null).to.be.true; `; expect(postmanTranslation(inputScript)).toBe(expectedOutput); }); @@ -36,13 +40,14 @@ describe('postmanTranslation function', () => { }); test('should comment non-translated pm commands', () => { - const inputScript = "pm.test('random test', () => pm.response.json());"; - const expectedOutput = "// test('random test', () => pm.response.json());"; + const inputScript = "pm.test('random test', () => pm.variables.replaceIn('{{$guid}}'));"; + const expectedOutput = "// test('random test', () => pm.variables.replaceIn('{{$guid}}'));"; expect(postmanTranslation(inputScript)).toBe(expectedOutput); }); test('should handle multiple pm commands on the same line', () => { const inputScript = "pm.environment.get('key'); pm.environment.set('key', 'value');"; - const expectedOutput = "bru.getEnv(var); bru.setEnvVar(var, 'value');"; + const expectedOutput = "bru.getEnvVar('key'); bru.setEnvVar('key', 'value');"; + expect(postmanTranslation(inputScript)).toBe(expectedOutput); }); test('should handle comments and other JavaScript code', () => { const inputScript = ` diff --git a/packages/bruno-app/src/utils/importers/translators/postman_translation.js b/packages/bruno-app/src/utils/importers/translators/postman_translation.js index 95f906516..3c00ed382 100644 --- a/packages/bruno-app/src/utils/importers/translators/postman_translation.js +++ b/packages/bruno-app/src/utils/importers/translators/postman_translation.js @@ -7,7 +7,11 @@ const replacements = { 'pm\\.collectionVariables\\.set\\(': 'bru.setVar(', 'pm\\.setNextRequest\\(': 'bru.setNextRequest(', 'pm\\.test\\(': 'test(', - 'pm.response.to.have\\.status\\(': 'expect(res.getStatus()).to.equal(' + 'pm.response.to.have\\.status\\(': 'expect(res.getStatus()).to.equal(', + 'pm\\.response\\.to\\.have\\.status\\(': 'expect(res.getStatus()).to.equal(', + 'pm\\.response\\.json\\(': 'res.getBody(', + 'pm\\.expect\\(': 'expect(', + 'pm\\.environment\\.has\\(([^)]+)\\)': 'bru.getEnvVar($1) !== undefined && bru.getEnvVar($1) !== null' }; const compiledReplacements = Object.entries(replacements).map(([pattern, replacement]) => ({ From 4f115b06fb2625fc39ccca76969e26fd1ece3291 Mon Sep 17 00:00:00 2001 From: Sanjai Kumar <84461672+sanjai0py@users.noreply.github.com> Date: Wed, 22 May 2024 14:04:52 +0530 Subject: [PATCH 05/61] Now added the ability to import and export js and json values. changes made in item schema. (#2296) * Now added the ability to import and export js values. changes made in item schema. * Improvements upon review * Fixes. * refactor: removed the copyRequest function and wrote the logic directly inside the copyItems function. * refactor: Update getBrunoJsonConfig function to remove unnecessary parameter * refactor: Update getBrunoJsonConfig function to remove unnecessary parameter and handle auth object dynamically * refactor: Update OAuth2 grantType handling in transformCollectionToSaveToExportAsFile function * refactor: Update getBrunoJsonConfig function to remove unnecessary async --- .../bruno-app/src/utils/collections/index.js | 187 +++++++++++------- packages/bruno-electron/src/ipc/collection.js | 33 +++- .../bruno-schema/src/collections/index.js | 10 +- 3 files changed, 147 insertions(+), 83 deletions(-) diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index f59d9e2d5..3ca18bd95 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -267,6 +267,10 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {} const copyItems = (sourceItems, destItems) => { each(sourceItems, (si) => { + if (!isItemAFolder(si) && !isItemARequest(si) && si.type !== 'js') { + return; + } + const di = { uid: si.uid, type: si.type, @@ -274,80 +278,109 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {} seq: si.seq }; - // if items is draft, then take data from draft to save - // The condition "!options.ignoreDraft" may appear confusing - // When saving a collection, this option allows the caller to specify to ignore any draft changes while still saving rest of the collection. - // This is useful for performing rename request/collections while still leaving changes in draft not making its way into the indexeddb - if (si.draft && !options.ignoreDraft) { - if (si.draft.request) { - di.request = { - url: si.draft.request.url, - method: si.draft.request.method, - headers: copyHeaders(si.draft.request.headers), - params: copyQueryParams(si.draft.request.params), - body: { - mode: si.draft.request.body.mode, - json: si.draft.request.body.json, - text: si.draft.request.body.text, - xml: si.draft.request.body.xml, - graphql: si.draft.request.body.graphql, - sparql: si.draft.request.body.sparql, - formUrlEncoded: copyFormUrlEncodedParams(si.draft.request.body.formUrlEncoded), - multipartForm: copyMultipartFormParams(si.draft.request.body.multipartForm) - }, - auth: { - mode: get(si.draft.request, 'auth.mode', 'none'), - basic: { - username: get(si.draft.request, 'auth.basic.username', ''), - password: get(si.draft.request, 'auth.basic.password', '') - }, - bearer: { - token: get(si.draft.request, 'auth.bearer.token', '') - } - }, - script: si.draft.request.script, - vars: si.draft.request.vars, - assertions: si.draft.request.assertions, - tests: si.draft.request.tests - }; - } - } else { - if (si.request) { - di.request = { - url: si.request.url, - method: si.request.method, - headers: copyHeaders(si.request.headers), - params: copyQueryParams(si.request.params), - body: { - mode: si.request.body.mode, - json: si.request.body.json, - text: si.request.body.text, - xml: si.request.body.xml, - graphql: si.request.body.graphql, - sparql: si.request.body.sparql, - formUrlEncoded: copyFormUrlEncodedParams(si.request.body.formUrlEncoded), - multipartForm: copyMultipartFormParams(si.request.body.multipartForm) - }, - auth: { - mode: get(si.request, 'auth.mode', 'none'), - basic: { - username: get(si.request, 'auth.basic.username', ''), - password: get(si.request, 'auth.basic.password', '') - }, - bearer: { - token: get(si.request, 'auth.bearer.token', '') - } - }, - script: si.request.script, - vars: si.request.vars, - assertions: si.request.assertions, - tests: si.request.tests - }; - } - } + if (si.request) { + di.request = { + url: si.request.url, + method: si.request.method, + headers: copyHeaders(si.request.headers), + params: copyQueryParams(si.request.params), + body: { + mode: si.request.body.mode, + json: si.request.body.json, + text: si.request.body.text, + xml: si.request.body.xml, + graphql: si.request.body.graphql, + sparql: si.request.body.sparql, + formUrlEncoded: copyFormUrlEncodedParams(si.request.body.formUrlEncoded), + multipartForm: copyMultipartFormParams(si.request.body.multipartForm) + }, + script: si.request.script, + vars: si.request.vars, + assertions: si.request.assertions, + tests: si.request.tests + }; - if (di.request && di.request.body.mode === 'json') { - di.request.body.json = replaceTabsWithSpaces(di.request.body.json); + // Handle auth object dynamically + di.request.auth = { + mode: get(si.request, 'auth.mode', 'none') + }; + + switch (di.request.auth.mode) { + case 'awsv4': + di.request.auth.awsv4 = { + accessKeyId: get(si.request, 'auth.awsv4.accessKeyId', ''), + secretAccessKey: get(si.request, 'auth.awsv4.secretAccessKey', ''), + sessionToken: get(si.request, 'auth.awsv4.sessionToken', ''), + service: get(si.request, 'auth.awsv4.service', ''), + region: get(si.request, 'auth.awsv4.region', ''), + profileName: get(si.request, 'auth.awsv4.profileName', '') + }; + break; + case 'basic': + di.request.auth.basic = { + username: get(si.request, 'auth.basic.username', ''), + password: get(si.request, 'auth.basic.password', '') + }; + break; + case 'bearer': + di.request.auth.bearer = { + token: get(si.request, 'auth.bearer.token', '') + }; + break; + case 'digest': + di.request.auth.digest = { + username: get(si.request, 'auth.digest.username', ''), + password: get(si.request, 'auth.digest.password', '') + }; + break; + case 'oauth2': + let grantType = get(si.request, 'auth.oauth2.grantType', ''); + switch (grantType) { + case 'password': + di.request.auth.oauth2 = { + grantType: grantType, + accessTokenUrl: get(si.request, 'auth.oauth2.accessTokenUrl', ''), + username: get(si.request, 'auth.oauth2.username', ''), + password: get(si.request, 'auth.oauth2.password', ''), + clientId: get(si.request, 'auth.oauth2.clientId', ''), + clientSecret: get(si.request, 'auth.oauth2.clientSecret', ''), + scope: get(si.request, 'auth.oauth2.scope', '') + }; + break; + case 'authorization_code': + di.request.auth.oauth2 = { + grantType: grantType, + callbackUrl: get(si.request, 'auth.oauth2.callbackUrl', ''), + authorizationUrl: get(si.request, 'auth.oauth2.authorizationUrl', ''), + accessTokenUrl: get(si.request, 'auth.oauth2.accessTokenUrl', ''), + clientId: get(si.request, 'auth.oauth2.clientId', ''), + clientSecret: get(si.request, 'auth.oauth2.clientSecret', ''), + scope: get(si.request, 'auth.oauth2.scope', ''), + pkce: get(si.request, 'auth.oauth2.pkce', false) + }; + break; + case 'client_credentials': + di.request.auth.oauth2 = { + grantType: grantType, + accessTokenUrl: get(si.request, 'auth.oauth2.accessTokenUrl', ''), + clientId: get(si.request, 'auth.oauth2.clientId', ''), + clientSecret: get(si.request, 'auth.oauth2.clientSecret', ''), + scope: get(si.request, 'auth.oauth2.scope', '') + }; + break; + } + break; + default: + break; + } + + if (si.type === 'js') { + di.fileContent = si.raw; + } + + if (di.request.body.mode === 'json') { + di.request.body.json = replaceTabsWithSpaces(di.request.body.json); + } } destItems.push(di); @@ -369,8 +402,14 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {} collectionToSave.activeEnvironmentUid = collection.activeEnvironmentUid; collectionToSave.environments = collection.environments || []; - copyItems(collection.items, collectionToSave.items); + collectionToSave.brunoConfig = cloneDeep(collection?.brunoConfig); + // delete proxy password if present + if (collectionToSave?.brunoConfig?.proxy?.auth?.password) { + delete collectionToSave.brunoConfig.proxy.auth.password; + } + + copyItems(collection.items, collectionToSave.items); return collectionToSave; }; diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index ff570ca9f..bdba1bc42 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -428,6 +428,11 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection parseCollectionItems(item.items, folderPath); } } + // Handle items of type 'js' + if (item.type === 'js') { + const filePath = path.join(currentPath, `${item.name}.js`); + fs.writeFileSync(filePath, item.raw); + } }); }; @@ -444,17 +449,29 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection }); }; + const getBrunoJsonConfig = (collection) => { + let brunoConfig = collection.brunoConfig; + + if (!brunoConfig) { + brunoConfig = { + version: '1', + name: collection.name, + type: 'collection', + ignore: ['node_modules', '.git'] + }; + } + + return brunoConfig; + }; + await createDirectory(collectionPath); const uid = generateUidBasedOnHash(collectionPath); - const brunoConfig = { - version: '1', - name: collectionName, - type: 'collection', - ignore: ['node_modules', '.git'] - }; - const content = await stringifyJson(brunoConfig); - await writeFile(path.join(collectionPath, 'bruno.json'), content); + const brunoConfig = getBrunoJsonConfig(collection); + const stringifiedBrunoConfig = await stringifyJson(brunoConfig); + + // Write the Bruno configuration to a file + await writeFile(path.join(collectionPath, 'bruno.json'), stringifiedBrunoConfig); mainWindow.webContents.send('main:collection-opened', collectionPath, uid, brunoConfig); ipcMain.emit('main:collection-opened', mainWindow, collectionPath, uid, brunoConfig); diff --git a/packages/bruno-schema/src/collections/index.js b/packages/bruno-schema/src/collections/index.js index 033e68277..6bf8dd2e4 100644 --- a/packages/bruno-schema/src/collections/index.js +++ b/packages/bruno-schema/src/collections/index.js @@ -217,13 +217,21 @@ const requestSchema = Yup.object({ const itemSchema = Yup.object({ uid: uidSchema, - type: Yup.string().oneOf(['http-request', 'graphql-request', 'folder']).required('type is required'), + type: Yup.string().oneOf(['http-request', 'graphql-request', 'folder', 'js']).required('type is required'), seq: Yup.number().min(1), name: Yup.string().min(1, 'name must be at least 1 character').required('name is required'), request: requestSchema.when('type', { is: (type) => ['http-request', 'graphql-request'].includes(type), then: (schema) => schema.required('request is required when item-type is request') }), + fileContent: Yup.string().when('type', { + // If the type is 'js', the fileContent field is expected to be a string. + // This can include an empty string, indicating that the JS file may not have any content. + is: 'js', + then: Yup.string(), + // For all other types, the fileContent field is not required and can be null. + otherwise: Yup.string().nullable() + }), items: Yup.lazy(() => Yup.array().of(itemSchema)), filename: Yup.string().nullable(), pathname: Yup.string().nullable() From f05389ca725bc9b6e1b469e4b29be1ac6781e6e9 Mon Sep 17 00:00:00 2001 From: Anoop M D Date: Wed, 22 May 2024 14:08:14 +0530 Subject: [PATCH 06/61] fix: fixed issue related to js files not being exported --- packages/bruno-app/src/utils/collections/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/bruno-app/src/utils/collections/index.js b/packages/bruno-app/src/utils/collections/index.js index 3ca18bd95..615d81952 100644 --- a/packages/bruno-app/src/utils/collections/index.js +++ b/packages/bruno-app/src/utils/collections/index.js @@ -374,15 +374,15 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {} break; } - if (si.type === 'js') { - di.fileContent = si.raw; - } - if (di.request.body.mode === 'json') { di.request.body.json = replaceTabsWithSpaces(di.request.body.json); } } + if (si.type === 'js') { + di.fileContent = si.raw; + } + destItems.push(di); if (si.items && si.items.length) { From e56fb74801260814973d3e20a6ccd49b12b4724c Mon Sep 17 00:00:00 2001 From: lohit Date: Wed, 22 May 2024 14:16:09 +0530 Subject: [PATCH 07/61] Filter out non-printable characters, control characters and ZWNBSP character from the response body (#2346) * fix(#1003): content type for client_credentials & password grant types * feature(#1003): added client is & secret for password credentials grant type * fix: filter out non-printable control character and ZWNBSP character * fix: filter out non-printable control character and ZWNBSP character --- packages/bruno-electron/src/ipc/network/index.js | 4 +++- .../bruno-tests/collection/echo/echo bom json.bru | 11 +++++++++++ packages/bruno-tests/src/echo/index.js | 12 ++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 packages/bruno-tests/collection/echo/echo bom json.bru diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 73d8a5923..6b4c4a12f 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -265,7 +265,9 @@ const parseDataFromResponse = (response) => { let data = dataBuffer.toString(charset || 'utf-8'); // Try to parse response to JSON, this can quietly fail try { - data = JSON.parse(response.data); + // Filter out control characters and ZWNBSP character + data = data.replace(/[\x00-\x08\x0E-\x1F\x7F\uFEFF]/g, ''); + data = JSON.parse(data); } catch {} return { data, dataBuffer }; diff --git a/packages/bruno-tests/collection/echo/echo bom json.bru b/packages/bruno-tests/collection/echo/echo bom json.bru new file mode 100644 index 000000000..117c329ba --- /dev/null +++ b/packages/bruno-tests/collection/echo/echo bom json.bru @@ -0,0 +1,11 @@ +meta { + name: echo bom json + type: http + seq: 1 +} + +get { + url: {{host}}/api/echo/bom-json-test + body: none + auth: none +} \ No newline at end of file diff --git a/packages/bruno-tests/src/echo/index.js b/packages/bruno-tests/src/echo/index.js index 36a37102e..2a2b52eb3 100644 --- a/packages/bruno-tests/src/echo/index.js +++ b/packages/bruno-tests/src/echo/index.js @@ -19,4 +19,16 @@ router.post('/xml-raw', (req, res) => { return res.send(req.rawBody); }); +router.get('/bom-json-test', (req, res) => { + const jsonData = { + message: 'Hello!', + success: true + }; + const jsonString = JSON.stringify(jsonData); + const bom = '\uFEFF'; + const jsonWithBom = bom + jsonString; + res.set('Content-Type', 'application/json; charset=utf-8'); + return res.send(jsonWithBom); +}); + module.exports = router; From e0b8de5337a6c66d00083f51f07efbd00c7a1205 Mon Sep 17 00:00:00 2001 From: lohit Date: Wed, 22 May 2024 18:42:25 +0530 Subject: [PATCH 08/61] fix: Filter out the ZWNBSP character from response body, fixed charset parse logic (#2351) * fix(#1003): content type for client_credentials & password grant types * feature(#1003): added client is & secret for password credentials grant type * fix: filter out non-printable control character and ZWNBSP character * fix: filter out non-printable control character and ZWNBSP character * remove ZWNBSP character from response body --------- Co-authored-by: Anoop M D --- packages/bruno-electron/src/ipc/network/index.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/bruno-electron/src/ipc/network/index.js b/packages/bruno-electron/src/ipc/network/index.js index 6b4c4a12f..a74584461 100644 --- a/packages/bruno-electron/src/ipc/network/index.js +++ b/packages/bruno-electron/src/ipc/network/index.js @@ -260,13 +260,16 @@ const configureRequest = async ( const parseDataFromResponse = (response) => { const dataBuffer = Buffer.from(response.data); // Parse the charset from content type: https://stackoverflow.com/a/33192813 - const charset = /charset=([^()<>@,;:"/[\]?.=\s]*)/i.exec(response.headers['Content-Type'] || ''); + const charsetMatch = /charset=([^()<>@,;:"/[\]?.=\s]*)/i.exec(response.headers['content-type'] || ''); + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec#using_exec_with_regexp_literals + const charsetValue = charsetMatch?.[1]; // Overwrite the original data for backwards compatibility - let data = dataBuffer.toString(charset || 'utf-8'); + let data = dataBuffer.toString(charsetValue || 'utf-8'); // Try to parse response to JSON, this can quietly fail try { - // Filter out control characters and ZWNBSP character - data = data.replace(/[\x00-\x08\x0E-\x1F\x7F\uFEFF]/g, ''); + // Filter out ZWNBSP character + // https://gist.github.com/antic183/619f42b559b78028d1fe9e7ae8a1352d + data = data.replace(/^\uFEFF/, ''); data = JSON.parse(data); } catch {} From 0e2e3eff5392f7b72f94b75f1825612d101eab11 Mon Sep 17 00:00:00 2001 From: Arnaud <56150873+arnaduga@users.noreply.github.com> Date: Wed, 22 May 2024 15:12:59 +0200 Subject: [PATCH 09/61] fix: minor French translation update (#2344) --- docs/readme/readme_fr.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/readme/readme_fr.md b/docs/readme/readme_fr.md index 0c5a41bf2..ebf2dc006 100644 --- a/docs/readme/readme_fr.md +++ b/docs/readme/readme_fr.md @@ -85,7 +85,7 @@ Ou n'importe quel système de gestion de sources ### Soutien ❤️ -Ouaf! Si vous aimez le projet, cliquez sur le bouton ⭐ !! +Si vous aimez Bruno et que vous souhaitez soutenir le travail _opensource_, pensez à devenir un sponsor via la page [Github Sponsors](https://github.com/sponsors/helloanoop). ### Partage de témoignages 📣 @@ -93,7 +93,7 @@ Si Bruno vous a aidé dans votre travail, au sein de votre équipe, merci de pen ### Publier Bruno sur un nouveau gestionnaire de paquets -Veuillez regarder [ici](/publishing.md) pour plus d'information. +Veuillez regarder [ici](../publishing/publishing_fr.md) pour plus d'information. ### Contribuer 👩‍💻🧑‍💻 From ff3ea3397980a956f62b5dca2a1df7da91353b30 Mon Sep 17 00:00:00 2001 From: Mateusz Pietryga Date: Wed, 22 May 2024 15:19:01 +0200 Subject: [PATCH 10/61] Fix: broken unit tests (#2334) --- .../collection/echo/supermetrics.bru | 139 ------------------ 1 file changed, 139 deletions(-) delete mode 100644 packages/bruno-tests/collection/echo/supermetrics.bru diff --git a/packages/bruno-tests/collection/echo/supermetrics.bru b/packages/bruno-tests/collection/echo/supermetrics.bru deleted file mode 100644 index 20c4eb281..000000000 --- a/packages/bruno-tests/collection/echo/supermetrics.bru +++ /dev/null @@ -1,139 +0,0 @@ -meta { - name: supermetrics json visualize - type: http - seq: 5 -} - -post { - url: https://testbench-sanity.usebruno.com/api/echo/json - body: json - auth: none -} - -headers { - foo: bar -} - -auth:basic { - username: asd - password: j -} - -auth:bearer { - token: -} - -body:json { - { - "notes": { - "runtime_sec": 5, - "result_rows": 4, - "query_count": 6 - }, - "fields": [ - { - "name": "Count", - "type": "number", - "data_type": "number" - }, - { - "name": "Name", - "type": "string", - "data_type": "string" - }, - { - "name": "Age", - "type": "number", - "data_type": "number" - }, - { - "name": "Email", - "type": "email", - "data_type": "string" - }, - { - "name": "Place", - "type": "string", - "data_type": "string" - } - ], - "data": [ - { - "id": null, - "name": "Name", - "age": "Age", - "email": "Email", - "city": "City" - }, - { - "id": 1, - "name": "John Doe", - "age": 30, - "email": "john@example.com", - "city": "New York" - }, - { - "id": 2, - "name": "Jane Smith", - "age": 25, - "email": "jane@example.com", - "city": "Los Angeles" - }, - { - "id": 3, - "name": "Bob Johnson", - "age": 35, - "email": "bob@example.com", - "city": "Chicago" - }, - { - "id": 4, - "name": "Alice Williams", - "age": 28, - "email": "alice@example.com", - "city": "San Francisco" - } - ] - } - -} - -assert { - res.status: eq 200 -} - -script:pre-request { - const { URL } = require('url'); - - const reqUrl = new URL(req.url); - - reqUrl.searchParams.delete('returnQueryIDafterSeconds'); - reqUrl.searchParams.delete('separateFetchQuery'); - reqUrl.searchParams.delete('pretty'); - reqUrl.searchParams.delete('displayLog'); - reqUrl.searchParams.delete('triggerID'); - reqUrl.searchParams.delete('hashForQIDFDB'); - reqUrl.searchParams.delete('queryType'); - reqUrl.searchParams.delete('queryCount'); - - reqUrl.searchParams.append("cacheBuster", new Date().toISOString()); - - req.setUrl(reqUrl.href); - -} - -script:post-response { - const visualizeNotes = require("./lib/notes"); - - bru.visualize(visualizeNotes(res)); -} - -tests { - test("should return json", function() { - const data = res.getBody(); - expect(res.getBody()).to.eql({ - "hello": "bruno" - }); - }); - -} From 91b5d0123e343bd6af66fa005f4c566282dcf6a7 Mon Sep 17 00:00:00 2001 From: Baptiste Poulain <64689165+bpoulaindev@users.noreply.github.com> Date: Wed, 22 May 2024 15:23:12 +0200 Subject: [PATCH 11/61] Feat/UI feedback : Visual + console feedback for failed postman translated imports (#2316) * feat(translation-feedback): console log incomplete postman import translations with stats and details * feat(translation-feedback): warn instead of log, reformat layout * feat(translation-feedback): optional callback function, update index.spec.js * feat(ui-feedback): display translation errors in the UI before choosing import location * feat(ui-feedback): syntax fix --------- Co-authored-by: bpoulaindev --- .../Sidebar/ImportCollectionLocation/index.js | 108 +++++++++++++++++- .../src/components/Sidebar/TitleBar/index.js | 5 +- .../bruno-app/src/components/Welcome/index.js | 5 +- .../src/utils/importers/postman-collection.js | 56 ++++++++- .../translators/postman_translation.js | 9 +- 5 files changed, 168 insertions(+), 15 deletions(-) diff --git a/packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/index.js b/packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/index.js index 62a02bdd5..96fade0db 100644 --- a/packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/index.js +++ b/packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/index.js @@ -1,11 +1,110 @@ -import React, { useRef, useEffect } from 'react'; +import React, { useRef, useEffect, useState } from 'react'; import { useDispatch } from 'react-redux'; import { useFormik } from 'formik'; import * as Yup from 'yup'; import { browseDirectory } from 'providers/ReduxStore/slices/collections/actions'; import Modal from 'components/Modal'; +import { IconAlertTriangle, IconArrowRight, IconCaretDown, IconCaretRight, IconCopy } from '@tabler/icons'; +import toast from 'react-hot-toast'; -const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName }) => { +const TranslationLog = ({ translationLog }) => { + const [showDetails, setShowDetails] = useState(false); + const preventSetShowDetails = (e) => { + e.stopPropagation(); + e.preventDefault(); + setShowDetails(!showDetails); + }; + const copyClipboard = (e, value) => { + e.stopPropagation(); + e.preventDefault(); + navigator.clipboard.writeText(value); + toast.success('Copied to clipboard'); + }; + return ( +
+
+
+
+
+
+

+ Warning: Some commands were not translated.{' '} +

+
+
+
+ + {showDetails && ( +
+ + Impacted Collections: {Object.keys(translationLog || {}).length} + + + Impacted Lines:{' '} + {Object.values(translationLog || {}).reduce( + (acc, curr) => acc + (curr.script?.length || 0) + (curr.test?.length || 0), + 0 + )} + + + The numbers after 'script' and 'test' indicate the line numbers of incomplete translations. + +
    + {Object.entries(translationLog || {}).map(([name, value]) => ( +
  • +
    + + {name} +
    +
    + {value.script && ( +
    + + test : + {value.script.map((scriptValue, index) => ( + + {scriptValue} + {index < value.script.length - 1 && <> - } + + ))} + +
    + )} + {value.test && ( +
    + test : + {value.test.map((testValue, index) => ( +
    + {testValue} + {index < value.test.length - 1 && <> - } +
    + ))} +
    + )} +
    +
  • + ))} +
+ +
+ )} +
+ ); +}; + +const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName, translationLog }) => { const inputRef = useRef(); const dispatch = useDispatch(); @@ -24,7 +123,6 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName }) => handleSubmit(values.collectionLocation); } }); - const browse = () => { dispatch(browseDirectory()) .then((dirPath) => { @@ -52,7 +150,9 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName }) => Name
{collectionName}
- + {translationLog && Object.keys(translationLog).length > 0 && ( + + )} <>