UNPKG

@usebruno/converters

Version:

The converters package is responsible for converting collections from one format to a Bruno collection. It can be used as a standalone package or as a part of the Bruno framework.

369 lines (337 loc) 10.2 kB
import map from 'lodash/map'; import { deleteSecretsInEnvs, deleteUidsInEnvs, deleteUidsInItems, isItemARequest } from '../common'; /** * Transforms a given URL string into an object representing the protocol, host, path, query, and variables. * * @param {string} url - The raw URL to be transformed. * @param {Object} params - The params object. * @returns {Object|null} An object containing the URL's protocol, host, path, query, and variables, or {} if an error occurs. */ export const transformUrl = (url, params) => { if (typeof url !== 'string' || !url.trim()) { url = ""; console.error("Invalid URL input:", url); } const urlRegexPatterns = { protocolAndRestSeparator: /:\/\//, hostAndPathSeparator: /\/(.+)/, domainSegmentSeparator: /\./, pathSegmentSeparator: /\//, queryStringSeparator: /\?/ }; const postmanUrl = { raw: url }; /** * Splits a URL into its protocol, host and path. * * @param {string} url - The URL to be split. * @returns {Object} An object containing the protocol and the raw host/path string. */ const splitUrl = (url) => { const urlParts = url.split(urlRegexPatterns.protocolAndRestSeparator); if (urlParts.length === 1) { return { protocol: '', rawHostAndPath: urlParts[0] }; } else if (urlParts.length === 2) { const [hostAndPath, _] = urlParts[1].split(urlRegexPatterns.queryStringSeparator); return { protocol: urlParts[0], rawHostAndPath: hostAndPath }; } else { throw new Error(`Invalid URL format: ${url}`); } }; /** * Splits the host and path from a raw host/path string. * * @param {string} rawHostAndPath - The raw host and path string to be split. * @returns {Object} An object containing the host and path. */ const splitHostAndPath = (rawHostAndPath) => { const [host, path = ''] = rawHostAndPath.split(urlRegexPatterns.hostAndPathSeparator); return { host, path }; }; try { const { protocol, rawHostAndPath } = splitUrl(url); postmanUrl.protocol = protocol; const { host, path } = splitHostAndPath(rawHostAndPath); postmanUrl.host = host ? host.split(urlRegexPatterns.domainSegmentSeparator) : []; postmanUrl.path = path ? path.split(urlRegexPatterns.pathSegmentSeparator) : []; } catch (error) { console.error(error.message); return {}; } // Construct query params. postmanUrl.query = params .filter((param) => param.type === 'query') .map(({ name, value, description }) => ({ key: name, value, description })); // Construct path params. postmanUrl.variable = params .filter((param) => param.type === 'path') .map(({ name, value, description }) => ({ key: name, value, description })); return postmanUrl; }; /** * Collapses multiple consecutive slashes (`//`) into a single slash, while skipping the protocol (e.g., `http://` or `https://`). * * @param {String} url - A URL string * @returns {String} The sanitized URL * */ const collapseDuplicateSlashes = (url) => { return url.replace(/(?<!:)\/{2,}/g, '/'); }; /** * Replaces all `\\` (backslashes) with `//` (forward slashes) and collapses multiple slashes into one. * * @param {string} url - The URL to sanitize. * @returns {string} The sanitized URL. * */ export const sanitizeUrl = (url) => { let sanitizedUrl = collapseDuplicateSlashes(url.replace(/\\/g, '//')); return sanitizedUrl; }; export const brunoToPostman = (collection) => { delete collection.uid; delete collection.processEnvVariables; deleteUidsInItems(collection.items); deleteUidsInEnvs(collection.environments); deleteSecretsInEnvs(collection.environments); const generateInfoSection = () => { return { name: collection.name, description: collection.root?.docs, schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json' }; }; const generateCollectionVars = (collection) => { const pattern = /{{[^{}]+}}/g; let listOfVars = []; const findOccurrences = (obj, results) => { if (typeof obj === 'object') { if (Array.isArray(obj)) { obj.forEach((item) => findOccurrences(item, results)); } else { for (const key in obj) { findOccurrences(obj[key], results); } } } else if (typeof obj === 'string') { obj.replace(pattern, (match) => { results.push(match.replace(/{{|}}/g, '')); }); } }; findOccurrences(collection, listOfVars); const finalArrayOfVars = [...new Set(listOfVars)]; return finalArrayOfVars.map((variable) => ({ key: variable, value: '', type: 'default' })); }; const generateEventSection = (item) => { const eventArray = []; if (item?.request?.tests?.length) { eventArray.push({ listen: 'test', script: { exec: item.request.tests.split('\n') // type: 'text/javascript' } }); } if (item?.request?.script?.req) { eventArray.push({ listen: 'prerequest', script: { exec: item.request.script.req.split('\n') // type: 'text/javascript' } }); } return eventArray; }; const generateHeaders = (headersArray) => { if (!headersArray || !Array.isArray(headersArray)) { return []; } return map(headersArray, (item) => { return { key: item.name || '', value: item.value || '', disabled: !item.enabled, type: 'default' }; }); }; const generateBody = (body) => { if (!body || !body.mode) { return { mode: 'raw', raw: '' }; } switch (body.mode) { case 'formUrlEncoded': return { mode: 'urlencoded', urlencoded: map(body.formUrlEncoded || [], (bodyItem) => { return { key: bodyItem.name || '', value: bodyItem.value || '', disabled: !bodyItem.enabled, type: 'default' }; }) }; case 'multipartForm': return { mode: 'formdata', formdata: map(body.multipartForm || [], (bodyItem) => { return { key: bodyItem.name || '', value: bodyItem.value || '', disabled: !bodyItem.enabled, type: 'default' }; }) }; case 'json': return { mode: 'raw', raw: body.json || '', options: { raw: { language: 'json' } } }; case 'xml': return { mode: 'raw', raw: body.xml || '', options: { raw: { language: 'xml' } } }; case 'text': return { mode: 'raw', raw: body.text || '', options: { raw: { language: 'text' } } }; case 'graphql': return { mode: 'graphql', graphql: body.graphql || {} }; default: return { mode: 'raw', raw: '' }; } }; const generateAuth = (itemAuth) => { switch (itemAuth?.mode) { case 'bearer': return { type: 'bearer', bearer: { key: 'token', value: itemAuth.bearer?.token || '', type: 'string' } }; case 'basic': { return { type: 'basic', basic: [ { key: 'password', value: itemAuth.basic?.password || '', type: 'string' }, { key: 'username', value: itemAuth.basic?.username || '', type: 'string' } ] }; } case 'apikey': { return { type: 'apikey', apikey: [ { key: 'key', value: itemAuth.apikey?.key || '', type: 'string' }, { key: 'value', value: itemAuth.apikey?.value || '', type: 'string' } ] }; } default: { return { type: 'noauth' }; } } }; const generateRequestSection = (itemRequest) => { if (!itemRequest) { return {}; } const requestObject = { method: itemRequest.method || 'GET', header: generateHeaders(itemRequest.headers), auth: generateAuth(itemRequest.auth), description: itemRequest.docs || '', // We sanitize the URL to make sure it's in the right format before passing it to the transformUrl func. This means changing backslashes to forward slashes and reducing multiple slashes to a single one, except in the protocol part. url: transformUrl(sanitizeUrl(itemRequest.url || ''), itemRequest.params || []) }; if (itemRequest.body && itemRequest.body.mode !== 'none') { requestObject.body = generateBody(itemRequest.body); } return requestObject; }; const generateItemSection = (itemsArray) => { if (!itemsArray || !Array.isArray(itemsArray)) { return []; } return map(itemsArray, (item) => { if (!item) { return null; } if (item.type === 'folder') { return { name: item.name || 'Untitled Folder', item: item.items && item.items.length ? generateItemSection(item.items) : [] }; } else if(isItemARequest(item)) { return { name: item.name || 'Untitled Request', event: generateEventSection(item), request: generateRequestSection(item.request) }; } return null; }).filter(Boolean); }; const collectionToExport = {}; collectionToExport.info = generateInfoSection(); collectionToExport.item = generateItemSection(collection.items); collectionToExport.variable = generateCollectionVars(collection); return collectionToExport; }; export default brunoToPostman;