const { get, each, find, isString, filter } = require('lodash'); const fs = require('fs'); const { getRequestUid, getExampleUid } = require('../cache/requestUids'); const { uuid } = require('./common'); const { posixifyPath } = require('./filesystem'); const os = require('os'); const { preferencesUtil } = require('../store/preferences'); const path = require('path'); const { DEFAULT_COLLECTION_FORMAT } = require('@usebruno/filestore'); const FORMAT_CONFIG = { yml: { ext: '.yml', collectionFile: 'opencollection.yml', folderFile: 'folder.yml' }, bru: { ext: '.bru', collectionFile: 'collection.bru', folderFile: 'folder.bru' } }; const mergeHeaders = (collection, request, requestTreePath, options = {}) => { const { includeDisabledHeaders = false } = options; let headers = new Map(); let disabledHeaders = new Map(); let collectionHeaders = collection?.draft?.root ? get(collection, 'draft.root.request.headers', []) : get(collection, 'root.request.headers', []); collectionHeaders.forEach((header) => { if (header.enabled) { if (header?.name?.toLowerCase?.() === 'content-type') { headers.set('content-type', header.value); } else { headers.set(header.name, header.value); } } else if (header.name?.length > 0) { disabledHeaders.set(header.name, header.value); } }); for (let i of requestTreePath) { if (i.type === 'folder') { const folderRoot = i?.draft || i?.root; let _headers = get(folderRoot, 'request.headers', []); _headers.forEach((header) => { if (header.enabled) { if (header.name.toLowerCase() === 'content-type') { headers.set('content-type', header.value); } else { headers.set(header.name, header.value); } } else if (header.name?.length > 0) { disabledHeaders.set(header.name, header.value); } }); } else { const _headers = i?.draft ? get(i, 'draft.request.headers', []) : get(i, 'request.headers', []); _headers.forEach((header) => { if (header.enabled) { if (header.name.toLowerCase() === 'content-type') { headers.set('content-type', header.value); } else { headers.set(header.name, header.value); } } else if (header.name?.length > 0) { disabledHeaders.set(header.name, header.value); } }); } } request.headers = [ ...Array.from(headers, ([name, value]) => ({ name, value, enabled: true })), ...(includeDisabledHeaders ? Array.from(disabledHeaders, ([name, value]) => ({ name, value, enabled: false })) : []) ]; }; const mergeVars = (collection, request, requestTreePath = []) => { let reqVars = new Map(); const collectionRoot = collection?.draft?.root || collection?.root || {}; let collectionRequestVars = get(collectionRoot, 'request.vars.req', []); let collectionVariables = {}; collectionRequestVars.forEach((_var) => { if (_var.enabled) { reqVars.set(_var.name, _var.value); collectionVariables[_var.name] = _var.value; } }); let folderVariables = {}; let requestVariables = {}; for (let i of requestTreePath) { if (i.type === 'folder') { const folderRoot = i?.draft || i?.root; let vars = get(folderRoot, 'request.vars.req', []); vars.forEach((_var) => { if (_var.enabled) { reqVars.set(_var.name, _var.value); folderVariables[_var.name] = _var.value; } }); } else { const vars = i?.draft ? get(i, 'draft.request.vars.req', []) : get(i, 'request.vars.req', []); vars.forEach((_var) => { if (_var.enabled) { reqVars.set(_var.name, _var.value); requestVariables[_var.name] = _var.value; } }); } } request.collectionVariables = collectionVariables; request.folderVariables = folderVariables; request.requestVariables = requestVariables; if (request?.vars) { request.vars.req = Array.from(reqVars, ([name, value]) => ({ name, value, enabled: true, type: 'request' })); } let resVars = new Map(); let collectionResponseVars = get(collectionRoot, 'request.vars.res', []); collectionResponseVars.forEach((_var) => { if (_var.enabled) { resVars.set(_var.name, _var.value); } }); for (let i of requestTreePath) { if (i.type === 'folder') { const folderRoot = i?.draft || i?.root; let vars = get(folderRoot, 'request.vars.res', []); vars.forEach((_var) => { if (_var.enabled) { resVars.set(_var.name, _var.value); } }); } else { const vars = i?.draft ? get(i, 'draft.request.vars.res', []) : get(i, 'request.vars.res', []); vars.forEach((_var) => { if (_var.enabled) { resVars.set(_var.name, _var.value); } }); } } if (request?.vars) { request.vars.res = Array.from(resVars, ([name, value]) => ({ name, value, enabled: true, type: 'response' })); } }; /** * Wraps a script in an IIFE closure to isolate its scope * @param {string} script - The script code to wrap * @returns {string} The wrapped script */ const wrapScriptInClosure = (script) => { if (!script || script.trim() === '') { return ''; } // Wrap script in async IIFE to create isolated scope // This prevents variable re-declaration errors and allows early returns // to only affect the current script segment return `await (async () => { ${script} })();`; }; /** * Wraps each script segment in an async IIFE, joins them with double newlines, * and records the line range of the "request" segment for stack-trace mapping. * * @param {string[]} scripts - Script segments in order (e.g. collection, folders, request). * @param {number} requestIndex - Index in scripts of the request-level segment. * @param {Array|null} segmentSources - Source file info for each segment (null for request segment). * @returns {{ code: string, metadata: { requestStartLine: number, requestEndLine: number } | null }} * * @example * ** Input ** * const scripts = ['let col = 1;', 'let fold = 2;', 'let req = 3;']; * const requestIndex = 2; * const segmentSources = [ * { source: 'collection', fileName: 'collection.bru' }, * { source: 'folder', fileName: 'folder.bru' }, * null // request segment — no source needed * ]; * * ** Output ** * { * code: * 'await (async () => {\n' // line 1 * + 'let col = 1;\n' // line 2 * + '})();\n' // line 3 * + '\n' // line 4 (blank separator) * + 'await (async () => {\n' // line 5 * + 'let fold = 2;\n' // line 6 * + '})();\n' // line 7 * + '\n' // line 8 (blank separator) * + 'await (async () => {\n' // line 9 * + 'let req = 3;\n' // line 10 * + '})();', // line 11 * metadata: { * requestStartLine: 9, * requestEndLine: 11, * segments: [ * { startLine: 1, endLine: 3, source: 'collection', fileName: 'collection.bru' }, * { startLine: 5, endLine: 7, source: 'folder', fileName: 'folder.bru' } * ] * } * } */ const wrapAndJoinScripts = (scripts, requestIndex, segmentSources = null) => { const wrapped = scripts.map((s) => wrapScriptInClosure(s)); const code = wrapped.filter(Boolean).join('\n\n'); let offset = 0; let metadata = null; const segments = []; for (let i = 0; i < scripts.length; i++) { if (!wrapped[i]) continue; const lineCount = wrapped[i].split('\n').length; const startLine = offset + 1; const endLine = offset + lineCount; if (i === requestIndex) { metadata = { requestStartLine: startLine, requestEndLine: endLine }; } if (segmentSources?.[i]) { segments.push({ startLine, endLine, ...segmentSources[i] }); } offset += lineCount + 1; } // Request-level script was empty, but collection/folder scripts produced code. // Use a zero line range to prevent stack traces from mapping to the request file. if (!metadata && code) { metadata = { requestStartLine: 0, requestEndLine: 0 }; } if (metadata && segments.length > 0) { metadata.segments = segments; } return { code, metadata }; }; const mergeScripts = (collection, request, requestTreePath, scriptFlow) => { const collectionRoot = collection?.draft?.root || collection?.root || {}; let collectionPreReqScript = get(collectionRoot, 'request.script.req', ''); let collectionPostResScript = get(collectionRoot, 'request.script.res', ''); let collectionTests = get(collectionRoot, 'request.tests', ''); // Build source file info for error trace mapping const format = collection.format || 'bru'; const config = FORMAT_CONFIG[format]; const collectionSource = { filePath: path.join(collection.pathname, config.collectionFile), displayPath: config.collectionFile }; const withContent = (source, script) => script?.trim() ? { ...source, scriptContent: script } : source; let combinedPreReqScript = []; let combinedPreReqSources = []; let combinedPostResScript = []; let combinedPostResSources = []; let combinedTests = []; let combinedTestsSources = []; for (let i of requestTreePath) { if (i.type === 'folder') { const folderRoot = i?.draft || i?.root; const folderSource = { filePath: path.join(i.pathname, config.folderFile), displayPath: posixifyPath(path.relative(collection.pathname, path.join(i.pathname, config.folderFile))) }; let preReqScript = get(folderRoot, 'request.script.req', ''); if (preReqScript && preReqScript.trim() !== '') { combinedPreReqScript.push(preReqScript); combinedPreReqSources.push(withContent(folderSource, preReqScript)); } let postResScript = get(folderRoot, 'request.script.res', ''); if (postResScript && postResScript.trim() !== '') { combinedPostResScript.push(postResScript); combinedPostResSources.push(withContent(folderSource, postResScript)); } let tests = get(folderRoot, 'request.tests', ''); if (tests && tests?.trim?.() !== '') { combinedTests.push(tests); combinedTestsSources.push(withContent(folderSource, tests)); } } } // Capture original request script content before overwriting with combined code const originalPreReqScript = request?.script?.req || ''; const originalPostResScript = request?.script?.res || ''; const originalTests = request?.tests || ''; // Wrap scripts, join them, and annotate metadata with the original request script content. // Returns { code, metadata } where metadata.requestScriptContent is set. const buildCombinedScript = (scripts, requestIndex, sources, originalScript) => { const result = wrapAndJoinScripts(scripts, requestIndex, sources); if (result.metadata) { result.metadata.requestScriptContent = originalScript; } return result; }; // Wrap each script segment in its own closure and join them // This allows each script to run separately with its own scope, // preventing variable re-declaration errors and allowing early returns // to only affect that specific script segment const collectionPreReqSource = withContent(collectionSource, collectionPreReqScript); const preReqScripts = [ collectionPreReqScript, ...combinedPreReqScript, originalPreReqScript ]; const preReqSources = [collectionPreReqSource, ...combinedPreReqSources, null]; const preReq = buildCombinedScript(preReqScripts, preReqScripts.length - 1, preReqSources, originalPreReqScript); request.script.req = preReq.code; request.script.reqMetadata = preReq.metadata; // Handle post-response scripts based on scriptFlow const collectionPostResSource = withContent(collectionSource, collectionPostResScript); if (scriptFlow === 'sequential') { const postResScripts = [ collectionPostResScript, ...combinedPostResScript, originalPostResScript ]; const postResSources = [collectionPostResSource, ...combinedPostResSources, null]; const postRes = buildCombinedScript(postResScripts, postResScripts.length - 1, postResSources, originalPostResScript); request.script.res = postRes.code; request.script.resMetadata = postRes.metadata; } else { // Reverse order for non-sequential flow const postResScripts = [ originalPostResScript, ...[...combinedPostResScript].reverse(), collectionPostResScript ]; const postResSources = [null, ...[...combinedPostResSources].reverse(), collectionPostResSource]; const postRes = buildCombinedScript(postResScripts, 0, postResSources, originalPostResScript); request.script.res = postRes.code; request.script.resMetadata = postRes.metadata; } // Handle tests based on scriptFlow const collectionTestsSource = withContent(collectionSource, collectionTests); if (scriptFlow === 'sequential') { const testScripts = [ collectionTests, ...combinedTests, originalTests ]; const testSources = [collectionTestsSource, ...combinedTestsSources, null]; const tests = buildCombinedScript(testScripts, testScripts.length - 1, testSources, originalTests); request.tests = tests.code; request.testsMetadata = tests.metadata; } else { // Reverse order for non-sequential flow const testScripts = [ originalTests, ...[...combinedTests].reverse(), collectionTests ]; const testSources = [null, ...[...combinedTestsSources].reverse(), collectionTestsSource]; const tests = buildCombinedScript(testScripts, 0, testSources, originalTests); request.tests = tests.code; request.testsMetadata = tests.metadata; } }; const flattenItems = (items = []) => { const flattenedItems = []; const flatten = (itms, flattened) => { each(itms, (i) => { flattened.push(i); if (i.items && i.items.length) { flatten(i.items, flattened); } }); }; flatten(items, flattenedItems); return flattenedItems; }; const findItem = (items = [], itemUid) => { return find(items, (i) => i.uid === itemUid); }; const findItemInCollection = (collection, itemUid) => { let flattenedItems = flattenItems(collection.items); return findItem(flattenedItems, itemUid); }; const findParentItemInCollection = (collection, itemUid) => { let flattenedItems = flattenItems(collection.items); return find(flattenedItems, (item) => { return item.items && find(item.items, (i) => i.uid === itemUid); }); }; const findParentItemInCollectionByPathname = (collection, pathname) => { let flattenedItems = flattenItems(collection.items); return find(flattenedItems, (item) => { return item.items && find(item.items, (i) => i.pathname === pathname); }); }; const getTreePathFromCollectionToItem = (collection, _item) => { let path = []; let item = findItemInCollection(collection, _item.uid); while (item) { path.unshift(item); item = findParentItemInCollection(collection, item.uid); } return path; }; const parseBruFileMeta = (data) => { try { const metaRegex = /meta\s*{\s*([\s\S]*?)\s*}/; const match = data?.match?.(metaRegex); if (match) { const metaContent = match[1].trim(); const lines = metaContent.replace(/\r\n/g, '\n').split('\n'); const metaJson = {}; lines.forEach((line) => { const [key, value] = line.split(':').map((str) => str.trim()); if (key && value) { metaJson[key] = isNaN(value) ? value : Number(value); } }); // Transform to the format expected by bruno-app let requestType = metaJson.type; if (requestType === 'http') { requestType = 'http-request'; } else if (requestType === 'graphql') { requestType = 'graphql-request'; } else { requestType = 'http-request'; } const sequence = metaJson.seq; const transformedJson = { type: requestType, name: metaJson.name, seq: !isNaN(sequence) ? Number(sequence) : 1, settings: {}, tags: metaJson.tags || [], request: { method: '', url: '', params: [], headers: [], auth: { mode: 'none' }, body: { mode: 'none' }, script: {}, vars: {}, assertions: [], tests: '', docs: '' } }; return transformedJson; } else { console.log('No "meta" block found in the file.'); return null; } } catch (err) { console.error('Error reading file:', err); return null; } }; // Parse YML file meta information const parseYmlFileMeta = (data) => { try { const yaml = require('js-yaml'); const parsed = yaml.load(data); if (!parsed || !parsed.meta) { console.log('No "meta" section found in YAML file.'); return null; } const metaJson = parsed.meta; // Transform to the format expected by bruno-app let requestType = metaJson.type; const typeMap = { http: 'http-request', graphql: 'graphql-request', grpc: 'grpc-request', ws: 'ws-request' }; requestType = typeMap[requestType] || 'http-request'; const sequence = metaJson.seq; const transformedJson = { type: requestType, name: metaJson.name, seq: !isNaN(sequence) ? Number(sequence) : 1, settings: {}, tags: metaJson.tags || [], request: { method: '', url: '', params: [], headers: [], auth: { mode: 'none' }, body: { mode: 'none' }, script: {}, vars: {}, assertions: [], tests: '', docs: '' } }; return transformedJson; } catch (err) { console.error('Error parsing YAML file meta:', err); return null; } }; // Format-aware meta parsing function const parseFileMeta = (data, format = DEFAULT_COLLECTION_FORMAT) => { if (format === 'yml') { return parseYmlFileMeta(data); } else { return parseBruFileMeta(data); } }; const hydrateRequestWithUuid = (request, pathname) => { request.uid = getRequestUid(pathname); const prefix = path.join(os.tmpdir(), 'bruno-'); request.isTransient = pathname.startsWith(prefix); const params = get(request, 'request.params', []); const headers = get(request, 'request.headers', []); const requestVars = get(request, 'request.vars.req', []); const responseVars = get(request, 'request.vars.res', []); const assertions = get(request, 'request.assertions', []); const bodyFormUrlEncoded = get(request, 'request.body.formUrlEncoded', []); const bodyMultipartForm = get(request, 'request.body.multipartForm', []); const file = get(request, 'request.body.file', []); const examples = get(request, 'examples', []); params.forEach((param) => (param.uid = uuid())); headers.forEach((header) => (header.uid = uuid())); requestVars.forEach((variable) => (variable.uid = uuid())); responseVars.forEach((variable) => (variable.uid = uuid())); assertions.forEach((assertion) => (assertion.uid = uuid())); bodyFormUrlEncoded.forEach((param) => (param.uid = uuid())); bodyMultipartForm.forEach((param) => (param.uid = uuid())); file.forEach((param) => (param.uid = uuid())); examples.forEach((example, eIndex) => { example.uid = getExampleUid(pathname, eIndex); example.itemUid = request.uid; const params = get(example, 'request.params', []); const headers = get(example, 'request.headers', []); const responseHeaders = get(example, 'response.headers', []); const bodyMultipartForm = get(example, 'request.body.multipartForm', []); const bodyFormUrlEncoded = get(example, 'request.body.formUrlEncoded', []); const file = get(example, 'request.body.file', []); params.forEach((param) => (param.uid = uuid())); headers.forEach((header) => (header.uid = uuid())); responseHeaders.forEach((header) => (header.uid = uuid())); bodyMultipartForm.forEach((param) => (param.uid = uuid())); bodyFormUrlEncoded.forEach((param) => (param.uid = uuid())); file.forEach((param) => (param.uid = uuid())); }); return request; }; const findItemByPathname = (items = [], pathname) => { return find(items, (i) => i.pathname === pathname); }; const findItemInCollectionByPathname = (collection, pathname) => { let flattenedItems = flattenItems(collection.items); return findItemByPathname(flattenedItems, pathname); }; const replaceTabsWithSpaces = (str, numSpaces = 2) => { if (!str || !str.length || !isString(str)) { return ''; } return str.replaceAll('\t', ' '.repeat(numSpaces)); }; const transformRequestToSaveToFilesystem = (item) => { const _item = item.draft ? item.draft : item; const itemToSave = { uid: _item.uid, type: _item.type, name: _item.name, seq: _item.seq, settings: _item.settings, tags: _item.tags, examples: _item.examples || [], request: { method: _item.request.method, url: _item.request.url, params: [], headers: [], auth: _item.request.auth, body: _item.request.body, script: _item.request.script, vars: _item.request.vars, assertions: _item.request.assertions, tests: _item.request.tests, docs: _item.request.docs } }; if (_item.type === 'grpc-request') { itemToSave.request.methodType = _item.request.methodType; itemToSave.request.protoPath = _item.request.protoPath; delete itemToSave.request.params; } // Only process params for non-gRPC requests if (_item.type !== 'grpc-request') { each(_item.request.params, (param) => { itemToSave.request.params.push({ uid: param.uid, name: param.name, value: param.value, description: param.description, annotations: param.annotations, type: param.type, enabled: param.enabled }); }); } each(_item.request.headers, (header) => { itemToSave.request.headers.push({ uid: header.uid, name: header.name, value: header.value, description: header.description, annotations: header.annotations, enabled: header.enabled }); }); if (itemToSave.request.body.mode === 'json') { itemToSave.request.body = { ...itemToSave.request.body, json: replaceTabsWithSpaces(itemToSave.request.body.json) }; } if (itemToSave.request.body.mode === 'grpc') { itemToSave.request.body = { ...itemToSave.request.body, grpc: itemToSave.request.body.grpc.map(({ name, content }, index) => ({ name: name ? name : `message ${index + 1}`, content: replaceTabsWithSpaces(content) })) }; } return itemToSave; }; const sortCollection = (collection) => { const items = collection.items || []; let folderItems = filter(items, (item) => item.type === 'folder'); let requestItems = filter(items, (item) => item.type !== 'folder'); folderItems = sortByNameThenSequence(folderItems); requestItems = requestItems.sort((a, b) => a.seq - b.seq); collection.items = folderItems.concat(requestItems); each(folderItems, (item) => { sortCollection(item); }); }; const sortFolder = (folder = {}) => { const items = folder.items || []; let folderItems = filter(items, (item) => item.type === 'folder'); let requestItems = filter(items, (item) => item.type !== 'folder'); folderItems = sortByNameThenSequence(folderItems); requestItems = requestItems.sort((a, b) => a.seq - b.seq); folder.items = folderItems.concat(requestItems); each(folderItems, (item) => { sortFolder(item); }); return folder; }; const getAllRequestsInFolderRecursively = (folder = {}) => { let requests = []; if (folder.items && folder.items.length) { folder.items.forEach((item) => { // Skip transient requests if (item.isTransient) { return; } if (item.type !== 'folder') { requests.push(item); } else { requests = requests.concat(getAllRequestsInFolderRecursively(item)); } }); } return requests; }; const getEnvVars = (environment = {}) => { const variables = environment.variables; if (!variables || !variables.length) { return { __name__: environment.name }; } const envVars = {}; each(variables, (variable) => { if (variable.enabled) { envVars[variable.name] = variable.value; } }); return { ...envVars, __name__: environment.name }; }; const getFormattedCollectionOauth2Credentials = ({ oauth2Credentials = [] }) => { let credentialsVariables = {}; oauth2Credentials.forEach(({ credentialsId, credentials }) => { if (credentials) { Object.entries(credentials).forEach(([key, value]) => { credentialsVariables[`$oauth2.${credentialsId}.${key}`] = value; }); } }); return credentialsVariables; }; const mergeAuth = (collection, request, requestTreePath) => { // Start with collection level auth (always consider collection auth as base) const collectionRoot = collection?.draft?.root || collection?.root || {}; let collectionAuth = get(collectionRoot, 'request.auth', { mode: 'none' }); let effectiveAuth = collectionAuth; let lastFolderWithAuth = null; // Traverse through the path to find the closest auth configuration for (let i of requestTreePath) { if (i.type === 'folder') { const folderRoot = i?.draft || i?.root; const folderAuth = get(folderRoot, 'request.auth'); // Only consider folders that have a valid auth mode if (folderAuth && folderAuth.mode && folderAuth.mode !== 'none' && folderAuth.mode !== 'inherit') { effectiveAuth = folderAuth; lastFolderWithAuth = i; } } } // If request is set to inherit, use the effective auth from collection/folders if (request.auth.mode === 'inherit') { request.auth = effectiveAuth; // For OAuth2, we need to handle credentials properly if (effectiveAuth.mode === 'oauth2') { if (lastFolderWithAuth) { // If auth is from folder, add folderUid and clear itemUid request.oauth2Credentials = { ...request.oauth2Credentials, folderUid: lastFolderWithAuth.uid, itemUid: null, mode: request.auth.mode }; } else { // If auth is from collection, ensure no folderUid and no itemUid request.oauth2Credentials = { ...request.oauth2Credentials, folderUid: null, itemUid: null, mode: request.auth.mode }; } } } }; const resolveInheritedSettings = (settings) => { const resolvedSettings = {}; // Resolve each setting individually Object.keys(settings).forEach((settingKey) => { const currentValue = settings[settingKey]; // If setting is inherited, fallback to preferences only for timeout setting if (currentValue === 'inherit' || currentValue === undefined || currentValue === null) { if (settingKey === 'timeout') { resolvedSettings[settingKey] = preferencesUtil.getRequestTimeout(); } } else { // Use the current value as-is resolvedSettings[settingKey] = currentValue; } }); // Handle missing timeout setting - if timeout is not in settings, treat it as inherited if (!settings.hasOwnProperty('timeout')) { resolvedSettings.timeout = preferencesUtil.getRequestTimeout(); } return resolvedSettings; }; const sortByNameThenSequence = (items) => { const isSeqValid = (seq) => Number.isFinite(seq) && Number.isInteger(seq) && seq > 0; // Sort folders alphabetically by name const alphabeticallySorted = [...items].sort((a, b) => a.name && b.name && a.name.localeCompare(b.name)); // Extract folders without 'seq' const withoutSeq = alphabeticallySorted.filter((f) => !isSeqValid(f['seq'])); // Extract folders with 'seq' and sort them by 'seq' const withSeq = alphabeticallySorted.filter((f) => isSeqValid(f['seq'])).sort((a, b) => a.seq - b.seq); const sortedItems = withoutSeq; // Insert folders with 'seq' at their specified positions withSeq.forEach((item) => { const position = item.seq - 1; const existingItem = withoutSeq[position]; // Check if there's already an item with the same sequence number const hasItemWithSameSeq = Array.isArray(existingItem) ? existingItem?.[0]?.seq === item.seq : existingItem?.seq === item.seq; if (hasItemWithSameSeq) { // If there's a conflict, group items with same sequence together const newGroup = Array.isArray(existingItem) ? [...existingItem, item] : [existingItem, item]; withoutSeq.splice(position, 1, newGroup); } else { // Insert item at the specified position withoutSeq.splice(position, 0, item); } }); // return flattened sortedItems return sortedItems.flat(); }; module.exports = { mergeHeaders, mergeVars, mergeScripts, mergeAuth, wrapAndJoinScripts, getTreePathFromCollectionToItem, flattenItems, findItem, findItemInCollection, findItemByPathname, findItemInCollectionByPathname, findParentItemInCollection, findParentItemInCollectionByPathname, parseBruFileMeta, parseFileMeta, hydrateRequestWithUuid, transformRequestToSaveToFilesystem, sortCollection, sortFolder, getAllRequestsInFolderRecursively, getEnvVars, getFormattedCollectionOauth2Credentials, sortByNameThenSequence, resolveInheritedSettings };