Feat: add openapi to bruno import in cli

This commit is contained in:
pooja-bruno
2025-05-07 15:36:21 +05:30
parent 2ee7ce5829
commit f2eaa79318
6 changed files with 451 additions and 2 deletions

View File

@@ -1,5 +1,9 @@
const { get, each, find, compact } = require('lodash');
const os = require('os');
const fs = require('fs');
const path = require('path');
const { jsonToBruV2, envJsonToBruV2, jsonToCollectionBru } = require('@usebruno/lang');
const { sanitizeName } = require('./filesystem');
const mergeHeaders = (collection, request, requestTreePath) => {
let headers = new Map();
@@ -200,10 +204,141 @@ const getTreePathFromCollectionToItem = (collection, _item) => {
return path;
};
/**
* Safe write file implementation to handle errors
* @param {string} filePath - Path to write file
* @param {string} content - Content to write
*/
const safeWriteFileSync = (filePath, content) => {
try {
fs.writeFileSync(filePath, content, { encoding: 'utf8' });
} catch (error) {
console.error(`Error writing file ${filePath}:`, error);
}
};
/**
* Creates a Bruno collection directory structure from a Bruno collection object
*
* @param {Object} collection - The Bruno collection object
* @param {string} dirPath - The output directory path
*/
const createCollectionFromBrunoObject = async (collection, dirPath) => {
// Create bruno.json
const brunoConfig = {
version: '1',
name: collection.name,
type: 'collection',
ignore: ['node_modules', '.git']
};
fs.writeFileSync(
path.join(dirPath, 'bruno.json'),
JSON.stringify(brunoConfig, null, 2)
);
// Create collection.bru if root exists
if (collection.root) {
const collectionContent = await jsonToCollectionBru(collection.root);
fs.writeFileSync(path.join(dirPath, 'collection.bru'), collectionContent);
}
// Process environments
if (collection.environments && collection.environments.length) {
const envDirPath = path.join(dirPath, 'environments');
fs.mkdirSync(envDirPath, { recursive: true });
for (const env of collection.environments) {
const content = await envJsonToBruV2(env);
const filename = sanitizeName(`${env.name}.bru`);
fs.writeFileSync(path.join(envDirPath, filename), content);
}
}
// Process collection items
await processCollectionItems(collection.items, dirPath);
return dirPath;
};
/**
* Recursively processes collection items to create files and folders
*
* @param {Array} items - Collection items
* @param {string} currentPath - Current directory path
*/
const processCollectionItems = async (items = [], currentPath) => {
for (const item of items) {
if (item.type === 'folder') {
// Create folder
let sanitizedFolderName = sanitizeName(item?.filename || item?.name);
const folderPath = path.join(currentPath, sanitizedFolderName);
fs.mkdirSync(folderPath, { recursive: true });
// Create folder.bru file if root exists
if (item?.root?.meta?.name) {
const folderBruFilePath = path.join(folderPath, 'folder.bru');
if (item.seq) {
item.root.meta.seq = item.seq;
}
const folderContent = await jsonToCollectionBru(
item.root,
true
);
safeWriteFileSync(folderBruFilePath, folderContent);
}
// Process folder items recursively
if (item.items && item.items.length) {
await processCollectionItems(item.items, folderPath);
}
} else if (['http-request', 'graphql-request'].includes(item.type)) {
// Create request file
let sanitizedFilename = sanitizeName(item?.filename || `${item.name}.bru`);
if (!sanitizedFilename.endsWith('.bru')) {
sanitizedFilename += '.bru';
}
// Convert JSON to BRU format based on the item type
let type = item.type === 'http-request' ? 'http' : 'graphql';
const bruJson = {
meta: {
name: item.name,
type: type,
seq: typeof item.seq === 'number' ? item.seq : 1
},
http: {
method: (item.request?.method || 'GET').toLowerCase(),
url: item.request?.url || '',
auth: item.request?.auth?.mode || 'none',
body: item.request?.body?.mode || 'none'
},
params: item.request?.params || [],
headers: item.request?.headers || [],
auth: item.request?.auth || {},
body: item.request?.body || {},
script: item.request?.script || {},
vars: {
req: item.request?.vars?.req || [],
res: item.request?.vars?.res || []
},
assertions: item.request?.assertions || [],
tests: item.request?.tests || '',
docs: item.request?.docs || ''
};
// Convert to BRU format and write to file
const content = await jsonToBruV2(bruJson);
safeWriteFileSync(path.join(currentPath, sanitizedFilename), content);
}
}
};
module.exports = {
mergeHeaders,
mergeVars,
mergeScripts,
findItemInCollection,
getTreePathFromCollectionToItem
getTreePathFromCollectionToItem,
createCollectionFromBrunoObject
}

View File

@@ -118,6 +118,46 @@ const getSubDirectories = (dir) => {
}
};
/**
* Sanitizes a filename to make it safe for filesystem operations
*
* @param {string} name - The name to sanitize
* @returns {string} - The sanitized name
*/
const sanitizeName = (name) => {
if (!name) return '';
const invalidCharacters = /[<>:"/\\|?*\x00-\x1F]/g;
return name
.replace(invalidCharacters, '-') // replace invalid characters with hyphens
.replace(/^[.\s-]+/, '') // remove leading dots, hyphens and spaces
.replace(/[.\s]+$/, ''); // remove trailing dots and spaces (keep trailing hyphens)
};
/**
* Validates if a name is valid for the filesystem
*
* @param {string} name - The name to validate
* @returns {boolean} - True if the name is valid, false otherwise
*/
const validateName = (name) => {
if (!name) return false;
const reservedDeviceNames = /^(CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9])$/i;
const firstCharacter = /^[^.\s\-\<>:"/\\|?*\x00-\x1F]/; // no dot, space, or hyphen at start
const middleCharacters = /^[^<>:"/\\|?*\x00-\x1F]*$/; // no invalid characters
const lastCharacter = /[^.\s]$/; // no dot or space at end, hyphen allowed
if (name.length > 255) return false; // max name length
if (reservedDeviceNames.test(name)) return false; // windows reserved names
return (
firstCharacter.test(name) &&
middleCharacters.test(name) &&
lastCharacter.test(name)
);
};
module.exports = {
exists,
isSymbolicLink,
@@ -131,5 +171,7 @@ module.exports = {
searchForFiles,
searchForBruFiles,
stripExtension,
getSubDirectories
getSubDirectories,
sanitizeName,
validateName
};