add: export & import of workspace as zip#6432
Conversation
|
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (1)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including You can disable this status message by setting the WalkthroughAdds workspace import/export end-to-end: new ImportWorkspace UI, export action in workspace menu, Redux actions to call IPC, and two Electron IPC handlers (renderer:export-workspace, renderer:import-workspace). Introduces archiver and extract-zip dependencies. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as AppTitleBar / WorkspaceHome
participant Redux as Redux Store
participant IPC as Electron IPC
participant FS as File System
participant Toast as Toast Notifier
User->>UI: Click "Export Workspace"
UI->>Redux: dispatch exportWorkspaceAction(workspaceUid)
Redux->>IPC: ipcRenderer.invoke('renderer:export-workspace', workspacePath, name)
IPC->>FS: showSaveDialog
FS-->>IPC: filePath / canceled
alt canceled
IPC-->>Redux: { canceled: true }
Redux-->>UI: resolves
else saved
IPC->>FS: create ZIP (exclude node_modules, .git) via archiver
FS-->>IPC: ZIP created at filePath
IPC-->>Redux: { success: true, filePath }
Redux-->>UI: resolves
UI->>Toast: Show success message
end
sequenceDiagram
participant User
participant UI as ImportWorkspace Modal
participant Redux as Redux Store
participant IPC as Electron IPC
participant FS as File System
participant WS as Workspace Manager
participant Toast as Toast Notifier
User->>UI: Select or drop ZIP file
UI->>UI: validate ZIP
User->>UI: Choose extract location (Browse -> dispatch browseDirectory)
User->>UI: Submit import
UI->>Redux: dispatch importWorkspaceAction(zipPath, location)
Redux->>IPC: ipcRenderer.invoke('renderer:import-workspace', zipPath, location)
IPC->>FS: extract-zip -> extract to temp dir
IPC->>FS: detect workspace directory, read workspace.yml
IPC->>FS: move/rename to final unique path
alt success
IPC->>WS: register watchers / open workspace
IPC-->>Redux: { success: true, config, name }
Redux->>Redux: dispatch createWorkspace(config) & switch active workspace
UI->>Toast: Show success
UI->>UI: Close modal
else failure
IPC-->>Redux: { success: false, error }
UI->>Toast: Show error
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
packages/bruno-electron/src/ipc/workspace.js (1)
344-348: Add safeguard against infinite loop.While unlikely in practice, add a maximum iteration limit for defensive coding:
let finalWorkspacePath = path.join(extractLocation, sanitizedName); let counter = 1; - while (fs.existsSync(finalWorkspacePath)) { + const maxAttempts = 1000; + while (fs.existsSync(finalWorkspacePath) && counter <= maxAttempts) { finalWorkspacePath = path.join(extractLocation, `${sanitizedName} (${counter})`); counter++; } + if (counter > maxAttempts) { + throw new Error('Unable to find available workspace name'); + }packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js (2)
105-116: Inconsistent promise handling style.This function uses
.then()/.catch()whileonSubmitat line 30 usesasync/await. Consider using async/await here for consistency.- const browse = () => { - dispatch(browseDirectory()) - .then((dirPath) => { - if (typeof dirPath === 'string' && dirPath.length > 0) { - formik.setFieldValue('workspaceLocation', dirPath); - } - }) - .catch((error) => { - formik.setFieldValue('workspaceLocation', ''); - console.error(error); - }); + const browse = async () => { + try { + const dirPath = await dispatch(browseDirectory()); + if (typeof dirPath === 'string' && dirPath.length > 0) { + formik.setFieldValue('workspaceLocation', dirPath); + } + } catch (error) { + formik.setFieldValue('workspaceLocation', ''); + console.error(error); + } };
125-129: Unnecessary ref in dependency array.
locationInputRefis a ref object that doesn't change between renders. Use an empty dependency array since this effect only needs to run on mount.useEffect(() => { if (locationInputRef && locationInputRef.current) { locationInputRef.current.focus(); } - }, [locationInputRef]); + }, []);
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (6)
packages/bruno-app/src/components/AppTitleBar/index.js(6 hunks)packages/bruno-app/src/components/WorkspaceHome/index.js(3 hunks)packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js(1 hunks)packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js(1 hunks)packages/bruno-electron/package.json(2 hunks)packages/bruno-electron/src/ipc/workspace.js(2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CODING_STANDARDS.md)
**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions.() => {}is good
No space between function name and parentheses.func()notfunc ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly
Files:
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.jspackages/bruno-app/src/components/AppTitleBar/index.jspackages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.jspackages/bruno-electron/src/ipc/workspace.jspackages/bruno-app/src/components/WorkspaceHome/index.js
🧬 Code graph analysis (4)
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js (3)
packages/bruno-electron/src/app/apiSpecs.js (1)
workspaceUid(78-78)packages/bruno-app/src/providers/ReduxStore/slices/workspaces/index.js (6)
workspaceUid(32-32)workspace(20-20)workspace(42-42)workspace(50-50)workspace(63-63)workspace(75-75)packages/bruno-app/src/utils/common/path.js (2)
result(83-83)result(162-162)
packages/bruno-app/src/components/AppTitleBar/index.js (1)
packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js (1)
ImportWorkspace(14-238)
packages/bruno-electron/src/ipc/workspace.js (2)
packages/bruno-electron/src/utils/workspace-config.js (11)
require(4-4)require(5-5)require(359-359)fs(1-1)path(2-2)workspaceConfig(94-94)yaml(3-3)validateWorkspacePath(41-56)readWorkspaceConfig(86-101)validateWorkspaceConfig(166-182)getWorkspaceUid(358-365)packages/bruno-electron/src/utils/filesystem.js (3)
fs(2-2)sanitizeName(180-187)path(1-1)
packages/bruno-app/src/components/WorkspaceHome/index.js (1)
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js (2)
exportWorkspaceAction(698-723)exportWorkspaceAction(698-723)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: SSL Tests - Linux
- GitHub Check: SSL Tests - macOS
- GitHub Check: SSL Tests - Windows
- GitHub Check: Unit Tests
- GitHub Check: Playwright E2E Tests
- GitHub Check: CLI Tests
🔇 Additional comments (18)
packages/bruno-app/src/components/WorkspaceHome/index.js (3)
3-4: LGTM!Imports are correctly added for the new export functionality.
77-88: LGTM!Handler follows the established pattern: hides dropdown, dispatches action, handles success/cancel/error with appropriate toast messages. Good use of optional chaining for error handling.
230-233: LGTM!Export menu item correctly wired with consistent icon sizing and handler binding.
packages/bruno-app/src/components/AppTitleBar/index.js (5)
2-2: LGTM!Import additions are appropriate for the new functionality.
Also applies to: 17-17
60-60: LGTM!State management follows the existing
createWorkspaceModalOpenpattern.
93-95: LGTM!Simple and consistent with
handleCreateWorkspace.
163-169: LGTM!Menu item properly added with correct icon and handler.
180-182: LGTM!Modal rendering follows the established pattern for
CreateWorkspace.packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js (2)
698-722: LGTM!Solid validation and error handling. Correctly checks workspace existence and pathname before invoking IPC.
725-744: ThecreateWorkspacereducer already handles idempotent additions. It checks for an existing workspace by uid and merges the data usingObject.assigninstead of creating a duplicate. WhilecreateWorkspaceis indeed dispatched twice—once fromworkspaceOpenedEventand once fromimportWorkspaceAction—the reducer prevents duplicate entries from being added to Redux state, so no action is required here.packages/bruno-electron/src/ipc/workspace.js (5)
4-5: LGTM!Required dependencies properly imported.
248-306: LGTM with minor note on ignored directories.Export handler is well-structured with proper cleanup. The
ignoredDirectoriescheck only matches top-level entries by name; nestednode_moduleswithin subdirectories will still be excluded since the recursive function checks each directory entry. Good compression level choice.
362-372: LGTM!Workspace registration and watcher setup follow the established pattern from other handlers.
380-383: Good error cleanup.Temp directory cleanup on error with
.catch(() => {})prevents cleanup failures from masking the original error.
321-322: Verify extract-zip handles zip slip attacks internally.No published CVEs exist for extract-zip regarding zip slip vulnerabilities. Confirm the library validates entry paths before extraction, or add explicit path validation in the calling code if relying on untrusted archives.
packages/bruno-electron/package.json (1)
33-33: Dependencies archiver ^7.0.1 and extract-zip ^2.0.1 are current and secure. Both packages are at their latest versions with no known direct vulnerabilities. While maintenance activity is low for both, they remain stable, widely-used libraries appropriate for ZIP operations.packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js (2)
61-77: Solid file validation implementation.Good defensive approach checking both file extension and MIME types, with optional chaining on
window?.ipcRenderer?.getFilePath. Clear error messages for each failure case.
133-197: Well-structured modal with good UX.The drag-drop zone with visual feedback, file display with remove option, and clear user guidance create a solid import experience.
packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js (3)
28-28: Redundant validation check.The
.min(1, 'location is required')is redundant since.required('location is required')already validates non-empty strings.Apply this diff:
- workspaceLocation: Yup.string().min(1, 'location is required').required('location is required') + workspaceLocation: Yup.string().required('location is required')
105-116: Consider showing user feedback on browse failure.When
browseDirectoryfails, the error is only logged to the console. Users won't see any feedback if the browse dialog encounters an issue.Apply this diff to show a toast on error:
.catch((error) => { formik.setFieldValue('workspaceLocation', ''); + toast.error('Failed to browse directory'); console.error(error); });
125-129: Simplify useEffect dependency array.
locationInputRefis a ref object that doesn't change between renders. Use an empty dependency array to clearly indicate this effect should only run on mount.Apply this diff:
useEffect(() => { if (locationInputRef && locationInputRef.current) { locationInputRef.current.focus(); } - }, [locationInputRef]); + }, []);
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CODING_STANDARDS.md)
**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions.() => {}is good
No space between function name and parentheses.func()notfunc ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly
Files:
packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js
🧬 Code graph analysis (1)
packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js (4)
packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js (3)
importWorkspaceAction(725-745)importWorkspaceAction(725-745)window(18-18)packages/bruno-app/src/utils/common/error.js (1)
formatIpcError(38-44)packages/bruno-app/src/components/Modal/index.js (1)
Modal(60-163)packages/bruno-app/src/components/Help/index.js (1)
Help(11-38)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: SSL Tests - macOS
- GitHub Check: SSL Tests - Linux
- GitHub Check: SSL Tests - Windows
- GitHub Check: Playwright E2E Tests
- GitHub Check: Unit Tests
- GitHub Check: CLI Tests
🔇 Additional comments (3)
packages/bruno-app/src/components/WorkspaceSidebar/ImportWorkspace/index.js (3)
1-13: LGTM!Imports are clean and properly organized.
133-237: LGTM!The UI implementation is clean, properly structured, and follows accessibility best practices. The drag-and-drop zone with file picker provides a good UX, and the read-only location field with Browse button is intuitive.
61-77: No issues found. ThegetFilePathmethod is properly exposed in the preload script using Electron's standardwebUtils.getPathForFile()API and is correctly available to the renderer process.
Description
JIRA
Contribution Checklist:
Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.
Publishing to New Package Managers
Please see here for more information.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.