make yml default option (#6985)

* make yml default option
This commit is contained in:
naman-bruno
2026-02-02 19:45:45 +05:30
committed by GitHub
parent 700e25a1d5
commit 8c997c46af
16 changed files with 113 additions and 117 deletions

View File

@@ -14,6 +14,7 @@ import { closeTabs } from 'providers/ReduxStore/slices/tabs';
import { sanitizeName, validateName, validateNameError } from 'utils/common/regex';
import { resolveRequestFilename } from 'utils/common/platform';
import { transformRequestToSaveToFilesystem, findCollectionByUid, findItemInCollection } from 'utils/collections';
import { DEFAULT_COLLECTION_FORMAT } from 'utils/common/constants';
import { itemSchema } from '@usebruno/schema';
const SaveTransientRequest = ({ item: itemProp, collection: collectionProp, isOpen = false, onClose }) => {
@@ -115,7 +116,7 @@ const SaveTransientRequest = ({ item: itemProp, collection: collectionProp, isOp
const transformedItem = transformRequestToSaveToFilesystem(itemToSave);
await itemSchema.validate(transformedItem);
const format = collection.format || 'bru';
const format = collection.format || DEFAULT_COLLECTION_FORMAT;
const targetFilename = resolveRequestFilename(sanitizedFilename, format);
await ipcRenderer.invoke('renderer:save-transient-request', {

View File

@@ -28,21 +28,6 @@ const StyledWrapper = styled.div`
}
}
.beta-badge-corner {
position: absolute;
top: 0;
right: 0;
padding: 0.25rem 0.5rem;
font-size: 0.625rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.025em;
background-color: ${(props) => rgba(props.theme.colors.text.yellow, 0.15)};
color: ${(props) => props.theme.colors.text.yellow};
border-top-right-radius: ${(props) => props.theme.border.radius.base};
border-bottom-left-radius: ${(props) => props.theme.border.radius.base};
}
.share-button {
display: flex;
border-radius: ${(props) => props.theme.border.radius.base};

View File

@@ -87,14 +87,13 @@ const ShareCollection = ({ onClose, collectionUid }) => {
</div>
<div
className={`share-button relative ${
className={`share-button ${
isCollectionLoading
? 'opacity-50 cursor-not-allowed'
: 'cursor-pointer'
}`}
onClick={isCollectionLoading ? undefined : handleExportOpenCollection}
>
<span className="beta-badge-corner">Beta</span>
<div className="mr-3 p-1 rounded-full">
{isCollectionLoading ? (
<IconLoader2 size={28} className="animate-spin" />

View File

@@ -1,28 +1,10 @@
import styled from 'styled-components';
import { rgba } from 'polished';
const StyledWrapper = styled.div`
.beta-badge {
margin-left: 0.5rem;
padding: 0.125rem 0.5rem;
font-size: 0.625rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.025em;
background-color: ${(props) => rgba(props.theme.colors.text.yellow, 0.15)};
color: ${(props) => props.theme.colors.text.yellow};
border-radius: ${(props) => props.theme.border.radius.sm};
}
.discussion-link {
margin-left: 0.5rem;
font-size: ${(props) => props.theme.font.size.sm};
color: ${(props) => props.theme.textLink};
cursor: pointer;
font-weight: 400;
&:hover {
text-decoration: underline;
.advanced-options {
.caret {
color: ${(props) => props.theme.textLink};
fill: ${(props) => props.theme.textLink};
}
}

View File

@@ -1,4 +1,4 @@
import React, { useRef, useEffect } from 'react';
import React, { useRef, useEffect, useState, forwardRef } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useFormik } from 'formik';
import * as Yup from 'yup';
@@ -8,11 +8,12 @@ import Portal from 'components/Portal';
import Modal from 'components/Modal';
import { sanitizeName, validateName, validateNameError } from 'utils/common/regex';
import PathDisplay from 'components/PathDisplay/index';
import { useState } from 'react';
import { IconArrowBackUp, IconEdit, IconExternalLink } from '@tabler/icons';
import { IconArrowBackUp, IconEdit, IconCaretDown } from '@tabler/icons';
import Help from 'components/Help';
import Dropdown from 'components/Dropdown';
import { multiLineMsg } from 'utils/common';
import { formatIpcError } from 'utils/common/error';
import { DEFAULT_COLLECTION_FORMAT } from 'utils/common/constants';
import StyledWrapper from './StyledWrapper';
import get from 'lodash/get';
import Button from 'ui/Button';
@@ -23,7 +24,11 @@ const CreateCollection = ({ onClose, defaultLocation: propDefaultLocation }) =>
const workspaces = useSelector((state) => state.workspaces?.workspaces || []);
const workspaceUid = useSelector((state) => state.workspaces?.activeWorkspaceUid);
const [isEditing, toggleEditing] = useState(false);
const [showFileFormat, setShowFileFormat] = useState(false);
const preferences = useSelector((state) => state.app.preferences);
const dropdownTippyRef = useRef();
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
const activeWorkspace = workspaces.find((w) => w.uid === workspaceUid);
const isDefaultWorkspace = activeWorkspace?.type === 'default';
@@ -35,7 +40,7 @@ const CreateCollection = ({ onClose, defaultLocation: propDefaultLocation }) =>
collectionName: '',
collectionFolderName: '',
collectionLocation: defaultLocation || '',
format: 'bru'
format: DEFAULT_COLLECTION_FORMAT
},
validationSchema: Yup.object({
collectionName: Yup.string()
@@ -86,6 +91,20 @@ const CreateCollection = ({ onClose, defaultLocation: propDefaultLocation }) =>
}
}, [inputRef]);
const AdvancedOptions = forwardRef((props, ref) => {
return (
<div ref={ref} className="flex mr-2 text-link cursor-pointer items-center">
<button
className="btn-advanced"
type="button"
>
Options
</button>
<IconCaretDown className="caret ml-1" size={14} strokeWidth={2} />
</div>
);
});
return (
<Portal>
<StyledWrapper>
@@ -209,62 +228,61 @@ const CreateCollection = ({ onClose, defaultLocation: propDefaultLocation }) =>
</div>
)}
<div className="mt-4">
<label htmlFor="format" className="flex items-center font-medium">
File Format
<Help width="300">
<p>
Choose the file format for storing requests in this collection.
</p>
<p className="mt-2">
<strong>OpenCollection (YAML):</strong> Industry-standard YAML format (.yml files)
</p>
<p className="mt-1">
<strong>BRU:</strong> Bruno's native file format (.bru files)
</p>
</Help>
{formik.values.format === 'yml' && (
<>
<span className="beta-badge">Beta</span>
<a
href="#"
className="discussion-link"
onClick={(e) => {
e.preventDefault();
window.open('https://github.com/usebruno/bruno/discussions/6634', '_blank', 'noopener,noreferrer');
}}
>
Join the discussion
</a>
</>
)}
</label>
<select
id="format"
name="format"
className="block textbox mt-2 w-full"
value={formik.values.format}
onChange={formik.handleChange}
>
<option value="yml">OpenCollection (YAML)</option>
<option value="bru">BRU Format (.bru)</option>
</select>
{formik.touched.format && formik.errors.format ? (
<div className="text-red-500">{formik.errors.format}</div>
) : null}
</div>
{showFileFormat && (
<div className="mt-4">
<label htmlFor="format" className="flex items-center font-medium">
File Format
<Help width="300">
<p>
Choose the file format for storing requests in this collection.
</p>
<p className="mt-2">
<strong>OpenCollection (YAML):</strong> Industry-standard YAML format (.yml files)
</p>
<p className="mt-1">
<strong>BRU:</strong> Bruno's native file format (.bru files)
</p>
</Help>
</label>
<select
id="format"
name="format"
className="block textbox mt-2 w-full"
value={formik.values.format}
onChange={formik.handleChange}
>
<option value="yml">OpenCollection (YAML)</option>
<option value="bru">BRU Format (.bru)</option>
</select>
{formik.touched.format && formik.errors.format ? (
<div className="text-red-500">{formik.errors.format}</div>
) : null}
</div>
)}
</div>
<div className="flex justify-end items-center mt-8 bruno-modal-footer">
<span className="mr-2">
<Button type="button" color="secondary" variant="ghost" onClick={onClose}>
<div className="flex justify-between items-center mt-8 bruno-modal-footer">
<div className="flex advanced-options">
<Dropdown onCreate={onDropdownCreate} icon={<AdvancedOptions />} placement="bottom-start">
<div
className="dropdown-item"
key="show-file-format"
onClick={(e) => {
dropdownTippyRef.current.hide();
setShowFileFormat(!showFileFormat);
}}
>
{showFileFormat ? 'Hide File Format' : 'Show File Format'}
</div>
</Dropdown>
</div>
<div className="flex justify-end">
<Button type="button" color="secondary" variant="ghost" onClick={onClose} className="mr-2">
Cancel
</Button>
</span>
<span>
<Button type="submit">
Create
</Button>
</span>
</div>
</div>
</form>
</Modal>

View File

@@ -1,6 +1,6 @@
import { collectionSchema, environmentSchema, itemSchema } from '@usebruno/schema';
import { parseQueryParams, extractPromptVariables } from '@usebruno/common/utils';
import { REQUEST_TYPES } from 'utils/common/constants';
import { REQUEST_TYPES, DEFAULT_COLLECTION_FORMAT } from 'utils/common/constants';
import cloneDeep from 'lodash/cloneDeep';
import filter from 'lodash/filter';
import find from 'lodash/find';
@@ -2642,7 +2642,7 @@ export const importCollection = (collection, collectionLocation, options = {}) =
const state = getState();
const activeWorkspace = state.workspaces.workspaces.find((w) => w.uid === state.workspaces.activeWorkspaceUid);
const collectionPath = await ipcRenderer.invoke('renderer:import-collection', collection, collectionLocation, options.format || 'bru');
const collectionPath = await ipcRenderer.invoke('renderer:import-collection', collection, collectionLocation, options.format || DEFAULT_COLLECTION_FORMAT);
if (activeWorkspace && activeWorkspace.pathname && activeWorkspace.type !== 'default') {
const workspaceCollection = {

View File

@@ -1 +1,3 @@
export const REQUEST_TYPES = ['http-request', 'graphql-request', 'grpc-request', 'ws-request'];
export const DEFAULT_COLLECTION_FORMAT = 'yml';

View File

@@ -375,7 +375,7 @@ const handler = async function (argv) {
result.__name__ = nameOverride || path.basename(filePath, fileExt);
} else {
const content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
const envJson = parseEnvironment(content);
const envJson = parseEnvironment(content, { format: 'bru' });
result = getEnvVars(envJson);
result.__name__ = nameOverride || path.basename(filePath, '.bru');
}

View File

@@ -14,7 +14,8 @@ const {
parseFolder,
stringifyFolder,
stringifyEnvironment,
parseEnvironment
parseEnvironment,
DEFAULT_COLLECTION_FORMAT
} = require('@usebruno/filestore');
const { dotenvToJson } = require('@usebruno/lang');
const brunoConverters = require('@usebruno/converters');
@@ -133,7 +134,7 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
'renderer:create-collection',
async (event, collectionName, collectionFolderName, collectionLocation, options = {}) => {
try {
const format = options.format || 'bru';
const format = options.format || DEFAULT_COLLECTION_FORMAT;
collectionFolderName = sanitizeName(collectionFolderName);
const dirPath = path.join(collectionLocation, collectionFolderName);
if (fs.existsSync(dirPath)) {
@@ -1049,7 +1050,7 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
}
});
ipcMain.handle('renderer:import-collection', async (_, collection, collectionLocation, format = 'bru') => {
ipcMain.handle('renderer:import-collection', async (_, collection, collectionLocation, format = DEFAULT_COLLECTION_FORMAT) => {
try {
let collectionName = sanitizeName(collection.name);
let collectionPath = path.join(collectionLocation, collectionName);

View File

@@ -3,7 +3,7 @@ const path = require('node:path');
const { ipcMain } = require('electron');
const { sanitizeName, createDirectory, writeFile, safeWriteFileSync, getCollectionStats } = require('./filesystem');
const { generateUidBasedOnHash, stringifyJson } = require('./common');
const { stringifyRequestViaWorker, stringifyCollection, stringifyEnvironment, stringifyFolder } = require('@usebruno/filestore');
const { stringifyRequestViaWorker, stringifyCollection, stringifyEnvironment, stringifyFolder, DEFAULT_COLLECTION_FORMAT } = require('@usebruno/filestore');
/**
* Recursively find a unique folder name by appending incremental numbers
@@ -22,7 +22,7 @@ async function findUniqueFolderName(baseName, collectionLocation, counter = 0) {
/**
* Import a collection - shared logic used by both IPC handler and onboarding service
*/
async function importCollection(collection, collectionLocation, mainWindow, uniqueFolderName = null, format = 'bru') {
async function importCollection(collection, collectionLocation, mainWindow, uniqueFolderName = null, format = DEFAULT_COLLECTION_FORMAT) {
// Use provided unique folder name or use collection name
let folderName = uniqueFolderName ? sanitizeName(uniqueFolderName) : sanitizeName(collection.name);
let collectionPath = path.join(collectionLocation, folderName);

View File

@@ -5,6 +5,7 @@ const { uuid } = require('./common');
const os = require('os');
const { preferencesUtil } = require('../store/preferences');
const path = require('path');
const { DEFAULT_COLLECTION_FORMAT } = require('@usebruno/filestore');
const mergeHeaders = (collection, request, requestTreePath) => {
let headers = new Map();
@@ -393,7 +394,7 @@ const parseYmlFileMeta = (data) => {
};
// Format-aware meta parsing function
const parseFileMeta = (data, format = 'bru') => {
const parseFileMeta = (data, format = DEFAULT_COLLECTION_FORMAT) => {
if (format === 'yml') {
return parseYmlFileMeta(data);
} else {

View File

@@ -1,4 +1,4 @@
const { parseRequestAndRedactBody, parseRequestViaWorker } = require('@usebruno/filestore');
const { parseRequestAndRedactBody, parseRequestViaWorker, DEFAULT_COLLECTION_FORMAT } = require('@usebruno/filestore');
/**
* Parses a large BRU request string by redacting body blocks, parsing the remainder,
@@ -7,7 +7,7 @@ const { parseRequestAndRedactBody, parseRequestViaWorker } = require('@usebruno/
* @param {string} format - Collection format, defaults to 'bru'
* @returns {Promise<any>} parsed request JSON
*/
async function parseLargeRequestWithRedaction(bruContent, format = 'bru') {
async function parseLargeRequestWithRedaction(bruContent, format = DEFAULT_COLLECTION_FORMAT) {
const { bruFileStringWithRedactedBody, extractedBodyContent } = parseRequestAndRedactBody(bruContent, { format });
const parsedData = await parseRequestViaWorker(bruFileStringWithRedactedBody, { format });

View File

@@ -0,0 +1,3 @@
import type { CollectionFormat } from './types';
export const DEFAULT_COLLECTION_FORMAT: CollectionFormat = 'yml';

View File

@@ -25,10 +25,11 @@ import {
StringifyOptions,
CollectionFormat
} from './types';
import { DEFAULT_COLLECTION_FORMAT } from './constants';
import { bruRequestParseAndRedactBodyData } from './formats/bru/utils/request-parse-and-redact-body-data';
// request
export const parseRequest = (content: string, options: ParseOptions = { format: 'bru' }): any => {
export const parseRequest = (content: string, options: ParseOptions = { format: DEFAULT_COLLECTION_FORMAT }): any => {
if (options.format === 'bru') {
return parseBruRequest(content);
} else if (options.format === 'yml') {
@@ -44,7 +45,7 @@ export const parseRequestAndRedactBody = (content: string, options: ParseOptions
throw new Error(`Unsupported format: ${options.format}`);
};
export const stringifyRequest = (requestObj: BrunoItem, options: StringifyOptions = { format: 'bru' }): string => {
export const stringifyRequest = (requestObj: BrunoItem, options: StringifyOptions = { format: DEFAULT_COLLECTION_FORMAT }): string => {
if (options.format === 'bru') {
return stringifyBruRequest(requestObj);
} else if (options.format === 'yml') {
@@ -74,7 +75,7 @@ export const stringifyRequestViaWorker = async (requestObj: any, options: { form
};
// collection
export const parseCollection = (content: string, options: ParseOptions = { format: 'bru' }): any => {
export const parseCollection = (content: string, options: ParseOptions = { format: DEFAULT_COLLECTION_FORMAT }): any => {
if (options.format === 'bru') {
return parseBruCollection(content);
} else if (options.format === 'yml') {
@@ -83,7 +84,7 @@ export const parseCollection = (content: string, options: ParseOptions = { forma
throw new Error(`Unsupported format: ${options.format}`);
};
export const stringifyCollection = (collectionObj: BrunoCollection, brunoConfig: any, options: StringifyOptions = { format: 'bru' }): string => {
export const stringifyCollection = (collectionObj: BrunoCollection, brunoConfig: any, options: StringifyOptions = { format: DEFAULT_COLLECTION_FORMAT }): string => {
if (options.format === 'bru') {
return stringifyBruCollection(collectionObj, false);
} else if (options.format === 'yml') {
@@ -93,7 +94,7 @@ export const stringifyCollection = (collectionObj: BrunoCollection, brunoConfig:
};
// folder
export const parseFolder = (content: string, options: ParseOptions = { format: 'bru' }): any => {
export const parseFolder = (content: string, options: ParseOptions = { format: DEFAULT_COLLECTION_FORMAT }): any => {
if (options.format === 'bru') {
return parseBruCollection(content);
} else if (options.format === 'yml') {
@@ -102,7 +103,7 @@ export const parseFolder = (content: string, options: ParseOptions = { format: '
throw new Error(`Unsupported format: ${options.format}`);
};
export const stringifyFolder = (folderObj: any, options: StringifyOptions = { format: 'bru' }): string => {
export const stringifyFolder = (folderObj: any, options: StringifyOptions = { format: DEFAULT_COLLECTION_FORMAT }): string => {
if (options.format === 'bru') {
return stringifyBruCollection(folderObj, true);
} else if (options.format === 'yml') {
@@ -112,7 +113,7 @@ export const stringifyFolder = (folderObj: any, options: StringifyOptions = { fo
};
// environment
export const parseEnvironment = (content: string, options: ParseOptions = { format: 'bru' }): any => {
export const parseEnvironment = (content: string, options: ParseOptions = { format: DEFAULT_COLLECTION_FORMAT }): any => {
if (options.format === 'bru') {
return parseBruEnvironment(content);
} else if (options.format === 'yml') {
@@ -121,7 +122,7 @@ export const parseEnvironment = (content: string, options: ParseOptions = { form
throw new Error(`Unsupported format: ${options.format}`);
};
export const stringifyEnvironment = (envObj: BrunoEnvironment, options: StringifyOptions = { format: 'bru' }): string => {
export const stringifyEnvironment = (envObj: BrunoEnvironment, options: StringifyOptions = { format: DEFAULT_COLLECTION_FORMAT }): string => {
if (options.format === 'bru') {
return stringifyBruEnvironment(envObj);
} else if (options.format === 'yml') {
@@ -136,3 +137,4 @@ export const parseDotEnv = (content: string): Record<string, string> => {
export { BruParserWorker };
export * from './types';
export * from './constants';

View File

@@ -1,5 +1,6 @@
import WorkerQueue from './WorkerQueue';
import { Lane, CollectionFormat } from '../types';
import { DEFAULT_COLLECTION_FORMAT } from '../constants';
import path from 'node:path';
const sizeInMB = (size: number): number => {
@@ -54,7 +55,7 @@ class BruParserWorker {
return queueForSize?.workerQueue ?? this.workerQueues[this.workerQueues.length - 1].workerQueue;
}
private async enqueueTask({ data, taskType, format = 'bru' }: { data: any; taskType: 'parse' | 'stringify'; format?: CollectionFormat }): Promise<any> {
private async enqueueTask({ data, taskType, format = DEFAULT_COLLECTION_FORMAT }: { data: any; taskType: 'parse' | 'stringify'; format?: CollectionFormat }): Promise<any> {
const size = getSize(data);
const workerQueue = this.getWorkerQueue(size);
const workerScriptPath = path.join(__dirname, './workers/worker-script.js');
@@ -67,11 +68,11 @@ class BruParserWorker {
});
}
async parseRequest(data: any, format: CollectionFormat = 'bru'): Promise<any> {
async parseRequest(data: any, format: CollectionFormat = DEFAULT_COLLECTION_FORMAT): Promise<any> {
return this.enqueueTask({ data, taskType: 'parse', format });
}
async stringifyRequest(data: any, format: CollectionFormat = 'bru'): Promise<any> {
async stringifyRequest(data: any, format: CollectionFormat = DEFAULT_COLLECTION_FORMAT): Promise<any> {
return this.enqueueTask({ data, taskType: 'stringify', format });
}

View File

@@ -2,6 +2,7 @@ import { parentPort } from 'node:worker_threads';
import { parseBruRequest, stringifyBruRequest } from '../formats/bru';
import { parseYmlItem, stringifyYmlItem } from '../formats/yml';
import { CollectionFormat } from '../types';
import { DEFAULT_COLLECTION_FORMAT } from '../constants';
interface WorkerMessage {
taskType: 'parse' | 'stringify';
@@ -14,7 +15,7 @@ interface WorkerMessage {
parentPort?.on('message', async (message: WorkerMessage) => {
try {
const { taskType, data: messageData } = message;
const { data, format = 'bru' } = messageData;
const { data, format = DEFAULT_COLLECTION_FORMAT } = messageData;
let result: any;
if (taskType === 'parse') {