Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/bruno-cli/src/utils/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ const createCollectionFromBrunoObject = async (collection, dirPath, options = {}

for (const env of collection.environments) {
const content = stringifyEnvironment(env, { format });
const filename = format === 'bru' ? sanitizeName(`${env.name}.bru`) : sanitizeName(`${env.name}.yml`);
const filename = format === 'bru' ? `${sanitizeName(env.name)}.bru` : `${sanitizeName(env.name)}.yml`;
fs.writeFileSync(path.join(envDirPath, filename), content);
}
}
Expand Down Expand Up @@ -622,12 +622,12 @@ const processCollectionItems = async (items = [], currentPath, options = {}) =>
// Create request file
let sanitizedFilename;
if (format == 'yml') {
sanitizedFilename = sanitizeName(item?.filename || `${item.name}.yml`);
sanitizedFilename = item?.filename ? sanitizeName(item.filename) : `${sanitizeName(item.name)}.yml`;
if (!sanitizedFilename.endsWith('.yml')) {
sanitizedFilename += '.yml';
}
} else {
sanitizedFilename = sanitizeName(item?.filename || `${item.name}.bru`);
sanitizedFilename = item?.filename ? sanitizeName(item.filename) : `${sanitizeName(item.name)}.bru`;
if (!sanitizedFilename.endsWith('.bru')) {
sanitizedFilename += '.bru';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,78 @@ describe('createCollectionFromBrunoObject', () => {
)
).rejects.toThrow('Unsupported item type: unsupported-type');
});

it('strips a trailing period from request and environment names before appending the .bru extension', async () => {
createOutputDir();

await createCollectionFromBrunoObject(
{
name: 'trailing-period-collection',
items: [
{
type: 'http-request',
name: 'Add a new pet to the store.',
seq: 1,
request: {
method: 'POST',
url: 'https://api.example.com/pet'
}
}
],
environments: [
{
name: 'Production.',
variables: []
}
]
},
outputDir,
{ format: 'bru' }
);

const files = fs.readdirSync(outputDir);
expect(files).toContain('Add a new pet to the store.bru');
expect(files).not.toContain('Add a new pet to the store..bru');

const envFiles = fs.readdirSync(path.join(outputDir, 'environments'));
expect(envFiles).toContain('Production.bru');
expect(envFiles).not.toContain('Production..bru');
});

it('strips a trailing period from request and environment names before appending the .yml extension', async () => {
createOutputDir();

await createCollectionFromBrunoObject(
{
name: 'trailing-period-collection',
items: [
{
type: 'http-request',
name: 'Add a new pet to the store.',
seq: 1,
request: {
method: 'POST',
url: 'https://api.example.com/pet'
}
}
],
environments: [
{
name: 'Production.',
variables: []
}
]
},
outputDir,
{ format: 'yml' }
);

const files = fs.readdirSync(outputDir);
expect(files).toContain('Add a new pet to the store.yml');
expect(files).not.toContain('Add a new pet to the store..yml');

const envFiles = fs.readdirSync(path.join(outputDir, 'environments'));
expect(envFiles).toContain('Production.yml');
expect(envFiles).not.toContain('Production..yml');
});
});
12 changes: 6 additions & 6 deletions packages/bruno-electron/src/ipc/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -1337,18 +1337,18 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
if (item?.filename) {
const ext = path.extname(item.filename);
if (ext === '.bru' || ext === '.yml') {
return item.filename.replace(ext, `.${format}`);
return sanitizeName(item.filename.replace(ext, `.${format}`));
}
return item.filename;
return sanitizeName(item.filename);
}
return `${item.name}.${format}`;
return `${sanitizeName(item.name)}.${format}`;
};

// Recursive function to parse the collection items and create files/folders
const parseCollectionItems = async (items = [], currentPath) => {
await Promise.all(items.map(async (item) => {
if (['http-request', 'graphql-request', 'grpc-request', 'ws-request'].includes(item.type)) {
let sanitizedFilename = sanitizeName(getFilenameWithFormat(item, format));
let sanitizedFilename = getFilenameWithFormat(item, format);
const content = await stringifyRequestViaWorker(item, { format });
const filePath = path.join(currentPath, sanitizedFilename);
safeWriteFileSync(filePath, content);
Expand All @@ -1371,7 +1371,7 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {
}
// Handle items of type 'js'
if (item.type === 'js') {
let sanitizedFilename = sanitizeName(item?.filename || `${item.name}.js`);
let sanitizedFilename = item?.filename ? sanitizeName(item.filename) : `${sanitizeName(item.name)}.js`;
const filePath = path.join(currentPath, sanitizedFilename);
safeWriteFileSync(filePath, item.fileContent);
}
Expand All @@ -1386,7 +1386,7 @@ const registerRendererEventHandlers = (mainWindow, watcher) => {

await Promise.all(environments.map(async (env) => {
const content = await stringifyEnvironment(env, { format });
let sanitizedEnvFilename = sanitizeName(`${env.name}.${format}`);
let sanitizedEnvFilename = `${sanitizeName(env.name)}.${format}`;
const filePath = path.join(envDirPath, sanitizedEnvFilename);
safeWriteFileSync(filePath, content);
}));
Expand Down
6 changes: 3 additions & 3 deletions packages/bruno-electron/src/utils/collection-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ async function importCollection(collection, collectionLocation, mainWindow, uniq
const parseCollectionItems = async (items = [], currentPath) => {
for (const item of items) {
if (['http-request', 'graphql-request', 'grpc-request'].includes(item.type)) {
let sanitizedFilename = sanitizeName(item.filename || `${item.name}.${format}`);
let sanitizedFilename = item.filename ? sanitizeName(item.filename) : `${sanitizeName(item.name)}.${format}`;
const content = await stringifyRequestViaWorker(item, { format });
const filePath = path.join(currentPath, sanitizedFilename);
safeWriteFileSync(filePath, content);
Comment on lines +41 to 44

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Use one collision-resolution strategy across both Electron import flows.

Sanitization intentionally causes distinct names to converge, but neither flow reserves or uniquifies the resulting paths. This can overwrite imported data or fail imports; packages/bruno-electron/src/ipc/collection.js is additionally nondeterministic because request and JavaScript writes use Promise.all.

  • packages/bruno-electron/src/utils/collection-import.js Lines 41-44: uniquify sanitized request filenames.
  • packages/bruno-electron/src/utils/collection-import.js Lines 64-67: uniquify sanitized JavaScript filenames.
  • packages/bruno-electron/src/utils/collection-import.js Lines 79-82: uniquify sanitized environment filenames.
  • packages/bruno-electron/src/ipc/collection.js Lines 1344-1354: reserve unique request paths before writing.
  • packages/bruno-electron/src/ipc/collection.js Lines 1374-1376: reserve unique JavaScript paths before writing.
  • packages/bruno-electron/src/ipc/collection.js Lines 1389-1391: reserve unique environment paths before writing.
📍 Affects 2 files
  • packages/bruno-electron/src/utils/collection-import.js#L41-L44 (this comment)
  • packages/bruno-electron/src/utils/collection-import.js#L64-L67
  • packages/bruno-electron/src/utils/collection-import.js#L79-L82
  • packages/bruno-electron/src/ipc/collection.js#L1344-L1354
  • packages/bruno-electron/src/ipc/collection.js#L1374-L1376
  • packages/bruno-electron/src/ipc/collection.js#L1389-L1391
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-electron/src/utils/collection-import.js` around lines 41 - 44,
Use one shared collision-resolution strategy across both Electron import flows.
In collection-import.js, uniquify sanitized request, JavaScript, and environment
filenames before constructing their paths; in collection.js, reserve unique
request, JavaScript, and environment paths before asynchronous writes so
Promise.all cannot select duplicate destinations. Preserve the intended
extensions and ensure each generated path is unique within the import operation.

Expand All @@ -61,7 +61,7 @@ async function importCollection(collection, collectionLocation, mainWindow, uniq
}
// Handle items of type 'js'
if (item.type === 'js') {
let sanitizedFilename = sanitizeName(item.filename || `${item.name}.js`);
let sanitizedFilename = item.filename ? sanitizeName(item.filename) : `${sanitizeName(item.name)}.js`;
const filePath = path.join(currentPath, sanitizedFilename);
safeWriteFileSync(filePath, item.fileContent);
}
Expand All @@ -76,7 +76,7 @@ async function importCollection(collection, collectionLocation, mainWindow, uniq

for (const env of environments) {
const content = await stringifyEnvironment(env, { format });
let sanitizedEnvFilename = sanitizeName(`${env.name}.${format}`);
let sanitizedEnvFilename = `${sanitizeName(env.name)}.${format}`;
const filePath = path.join(envDirPath, sanitizedEnvFilename);
safeWriteFileSync(filePath, content);
}
Expand Down
Loading