diff --git a/packages/bruno-app/src/components/ShareCollection/index.js b/packages/bruno-app/src/components/ShareCollection/index.js index d3ffe083860..35419780729 100644 --- a/packages/bruno-app/src/components/ShareCollection/index.js +++ b/packages/bruno-app/src/components/ShareCollection/index.js @@ -14,7 +14,8 @@ import toast from 'react-hot-toast'; const EXPORT_FORMATS = { ZIP: 'zip', YAML: 'yaml', - POSTMAN: 'postman' + POSTMAN_V2_0: 'postman-v2-0', + POSTMAN_V2_1: 'postman-v2-1' }; const ShareCollection = ({ onClose, collectionUid }) => { @@ -62,9 +63,9 @@ const ShareCollection = ({ onClose, collectionUid }) => { exportOpenCollection(transformCollectionToSaveToExportAsFile(collectionCopy)); }; - const handleExportPostman = () => { + const handleExportPostman = (version) => { const collectionCopy = cloneDeep(collection); - exportPostmanCollection(collectionCopy); + exportPostmanCollection(collectionCopy, version); }; const handleProceed = async () => { @@ -79,8 +80,11 @@ const ShareCollection = ({ onClose, collectionUid }) => { case EXPORT_FORMATS.YAML: handleExportYaml(); break; - case EXPORT_FORMATS.POSTMAN: - handleExportPostman(); + case EXPORT_FORMATS.POSTMAN_V2_0: + handleExportPostman('2.0'); + break; + case EXPORT_FORMATS.POSTMAN_V2_1: + handleExportPostman('2.1'); break; } onClose(); @@ -165,20 +169,32 @@ const ShareCollection = ({ onClose, collectionUid }) => {
Other Format
!isDisabled && setSelectedFormat(EXPORT_FORMATS.POSTMAN)} + className={`other-format-card ${selectedFormat === EXPORT_FORMATS.POSTMAN_V2_0 ? 'selected' : ''} ${isDisabled ? 'opacity-50 cursor-not-allowed' : ''}`} + onClick={() => !isDisabled && setSelectedFormat(EXPORT_FORMATS.POSTMAN_V2_0)} + > +
+ +
+
+
Postman v2.0
+
Export for Postman Collection v2.0
+
+
+
!isDisabled && setSelectedFormat(EXPORT_FORMATS.POSTMAN_V2_1)} >
-
Postman
-
Export for Postman
+
Postman v2.1
+
Export for Postman Collection v2.1
- {selectedFormat === EXPORT_FORMATS.POSTMAN && hasNonExportableRequestTypes.has && ( + {(selectedFormat === EXPORT_FORMATS.POSTMAN_V2_0 || selectedFormat === EXPORT_FORMATS.POSTMAN_V2_1) && hasNonExportableRequestTypes.has && (
diff --git a/packages/bruno-app/src/utils/exporters/postman-collection.js b/packages/bruno-app/src/utils/exporters/postman-collection.js index 87694c65286..645745a8880 100644 --- a/packages/bruno-app/src/utils/exporters/postman-collection.js +++ b/packages/bruno-app/src/utils/exporters/postman-collection.js @@ -2,11 +2,11 @@ import * as FileSaver from 'file-saver'; import { brunoToPostman } from '@usebruno/converters'; import { filterTransientItems } from 'utils/collections'; -export const exportCollection = (collection) => { +export const exportCollection = (collection, version = '2.1') => { // Filter out transient items before export collection.items = filterTransientItems(collection.items); - const collectionToExport = brunoToPostman(collection); + const collectionToExport = brunoToPostman(collection, version); const fileName = `${collection.name}.json`; const fileBlob = new Blob([JSON.stringify(collectionToExport, null, 2)], { type: 'application/json' }); diff --git a/packages/bruno-converters/src/postman/bruno-to-postman.js b/packages/bruno-converters/src/postman/bruno-to-postman.js index c543efb46d0..bd73844efe5 100644 --- a/packages/bruno-converters/src/postman/bruno-to-postman.js +++ b/packages/bruno-converters/src/postman/bruno-to-postman.js @@ -147,18 +147,23 @@ export const sanitizeUrl = (url) => { return sanitizedUrl; }; -export const brunoToPostman = (collection) => { +export const brunoToPostman = (collection, version = '2.1') => { delete collection.uid; delete collection.processEnvVariables; deleteUidsInItems(collection.items); deleteUidsInEnvs(collection.environments); deleteSecretsInEnvs(collection.environments); + const schemaMap = { + '2.0': 'https://schema.getpostman.com/json/collection/v2.0.0/collection.json', + '2.1': 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json' + }; + const generateInfoSection = () => { return { name: collection.name, description: collection.root?.docs, - schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json' + schema: schemaMap[version] || schemaMap['2.1'] }; }; @@ -377,44 +382,85 @@ export const brunoToPostman = (collection) => { const generateAuth = (itemAuth) => { switch (itemAuth?.mode) { - case 'bearer': + case 'bearer': { + const token = itemAuth.bearer?.token || ''; + if (version === '2.0') { + return { + type: 'bearer', + bearer: { + token: token + } + }; + } return { type: 'bearer', - bearer: { - key: 'token', - value: itemAuth.bearer?.token || '', - type: 'string' - } + bearer: [ + { + key: 'token', + value: token, + type: 'string' + } + ] }; + } case 'basic': { + const username = itemAuth.basic?.username || ''; + const password = itemAuth.basic?.password || ''; + if (version === '2.0') { + return { + type: 'basic', + basic: { + username: username, + password: password + } + }; + } return { type: 'basic', basic: [ { - key: 'password', - value: itemAuth.basic?.password || '', + key: 'username', + value: username, type: 'string' }, { - key: 'username', - value: itemAuth.basic?.username || '', + key: 'password', + value: password, type: 'string' } ] }; } case 'apikey': { + const key = itemAuth.apikey?.key || ''; + const value = itemAuth.apikey?.value || ''; + const inValue = itemAuth.apikey?.in || 'header'; + if (version === '2.0') { + return { + type: 'apikey', + apikey: { + key: key, + value: value, + in: inValue + } + }; + } return { type: 'apikey', apikey: [ { key: 'key', - value: itemAuth.apikey?.key || '', + value: key, type: 'string' }, { key: 'value', - value: itemAuth.apikey?.value || '', + value: value, + type: 'string' + }, + { + key: 'in', + value: inValue, type: 'string' } ] diff --git a/packages/bruno-converters/tests/postman/bruno-to-postman.spec.js b/packages/bruno-converters/tests/postman/bruno-to-postman.spec.js index c2b372d3801..344dd0a3f1c 100644 --- a/packages/bruno-converters/tests/postman/bruno-to-postman.spec.js +++ b/packages/bruno-converters/tests/postman/bruno-to-postman.spec.js @@ -412,11 +412,13 @@ describe('brunoToPostman null checks and fallbacks', () => { const result = brunoToPostman(simpleCollection); expect(result.item[0].request.auth).toEqual({ type: 'bearer', - bearer: { - key: 'token', - value: '', - type: 'string' - } + bearer: [ + { + key: 'token', + value: '', + type: 'string' + } + ] }); }); @@ -443,12 +445,12 @@ describe('brunoToPostman null checks and fallbacks', () => { type: 'basic', basic: [ { - key: 'password', + key: 'username', value: '', type: 'string' }, { - key: 'username', + key: 'password', value: '', type: 'string' } @@ -487,6 +489,11 @@ describe('brunoToPostman null checks and fallbacks', () => { key: 'value', value: '', type: 'string' + }, + { + key: 'in', + value: 'header', + type: 'string' } ] }); @@ -1047,3 +1054,178 @@ describe('brunoToPostman item ordering', () => { expect(names).toEqual(['Apple', 'Mango', 'Zebra']); }); }); + +describe('brunoToPostman v2.0 format', () => { + it('should use v2.0 schema URL', () => { + const simpleCollection = { + name: 'Test Collection', + items: [] + }; + + const result = brunoToPostman(simpleCollection, '2.0'); + expect(result.info.schema).toBe('https://schema.getpostman.com/json/collection/v2.0.0/collection.json'); + }); + + it('should use object format for bearer auth in v2.0', () => { + const simpleCollection = { + items: [ + { + name: 'Test Request', + type: 'http-request', + request: { + method: 'GET', + url: 'https://example.com', + auth: { + mode: 'bearer', + bearer: { token: 'test-token' } + } + } + } + ] + }; + + const result = brunoToPostman(simpleCollection, '2.0'); + expect(result.item[0].request.auth).toEqual({ + type: 'bearer', + bearer: { + token: 'test-token' + } + }); + }); + + it('should use object format for basic auth in v2.0', () => { + const simpleCollection = { + items: [ + { + name: 'Test Request', + type: 'http-request', + request: { + method: 'GET', + url: 'https://example.com', + auth: { + mode: 'basic', + basic: { username: 'user', password: 'pass' } + } + } + } + ] + }; + + const result = brunoToPostman(simpleCollection, '2.0'); + expect(result.item[0].request.auth).toEqual({ + type: 'basic', + basic: { + username: 'user', + password: 'pass' + } + }); + }); + + it('should use object format for apikey auth in v2.0', () => { + const simpleCollection = { + items: [ + { + name: 'Test Request', + type: 'http-request', + request: { + method: 'GET', + url: 'https://example.com', + auth: { + mode: 'apikey', + apikey: { key: 'api-key', value: 'secret', in: 'header' } + } + } + } + ] + }; + + const result = brunoToPostman(simpleCollection, '2.0'); + expect(result.item[0].request.auth).toEqual({ + type: 'apikey', + apikey: { + key: 'api-key', + value: 'secret', + in: 'header' + } + }); + }); +}); + +describe('brunoToPostman v2.1 format', () => { + it('should use v2.1 schema URL by default', () => { + const simpleCollection = { + name: 'Test Collection', + items: [] + }; + + const result = brunoToPostman(simpleCollection); + expect(result.info.schema).toBe('https://schema.getpostman.com/json/collection/v2.1.0/collection.json'); + }); + + it('should use array format for bearer auth in v2.1', () => { + const simpleCollection = { + items: [ + { + name: 'Test Request', + type: 'http-request', + request: { + method: 'GET', + url: 'https://example.com', + auth: { + mode: 'bearer', + bearer: { token: 'test-token' } + } + } + } + ] + }; + + const result = brunoToPostman(simpleCollection, '2.1'); + expect(result.item[0].request.auth).toEqual({ + type: 'bearer', + bearer: [ + { + key: 'token', + value: 'test-token', + type: 'string' + } + ] + }); + }); + + it('should use array format for basic auth in v2.1', () => { + const simpleCollection = { + items: [ + { + name: 'Test Request', + type: 'http-request', + request: { + method: 'GET', + url: 'https://example.com', + auth: { + mode: 'basic', + basic: { username: 'user', password: 'pass' } + } + } + } + ] + }; + + const result = brunoToPostman(simpleCollection, '2.1'); + expect(result.item[0].request.auth).toEqual({ + type: 'basic', + basic: [ + { + key: 'username', + value: 'user', + type: 'string' + }, + { + key: 'password', + value: 'pass', + type: 'string' + } + ] + }); + }); +});