- Architecture Overview
- Development Environment Setup
- Project Structure
- Core Components
- Build and Deployment
- Testing
- Debugging
- Extension Development
- Contributing Guidelines
Multi Grep Replacer is built using Electron, providing a cross-platform desktop application with modern web technologies.
- Framework: Electron 25.0.0
- Runtime: Node.js 18.15.0
- UI: HTML5 + CSS3 + Vanilla JavaScript
- Build System: Electron Builder
- Testing: Jest + Spectron
- Linting: ESLint + Prettier
- Logger: Vibe Logger for structured logging
┌─────────────────────┐ ┌─────────────────────┐
│ Main Process │◄──►│ Renderer Process │
│ (Node.js) │IPC │ (Chromium) │
│ │ │ │
│ - File Operations │ │ - UI Controller │
│ - Replacement Engine│ │ - Event Handlers │
│ - Config Management │ │ - Progress Display │
│ - IPC Handlers │ │ - Theme Management │
└─────────────────────┘ └─────────────────────┘
│ │
│ │
┌─────▼─────┐ ┌────▼────┐
│ Preload │ │ HTML │
│ Script │ │ CSS │
│ (Bridge) │ │ JS │
└───────────┘ └─────────┘
- Context Isolation: Enabled for security
- Node Integration: Disabled in renderer
- Preload Script: Secure API bridge
- Content Security Policy: Strict CSP headers
- Node.js 18.x or higher
- npm 9.x or higher
- Git
- Code editor (VS Code recommended)
-
Clone the repository
git clone https://github.com/sarap422/multi-grep-replacer.git cd multi-grep-replacer -
Install dependencies
npm install
-
Install development tools (optional)
npm install -g electron npm install -g @electron-forge/cli
-
Verify installation
npm run lint npm test npm start
Recommended extensions:
- ESLint
- Prettier - Code formatter
- JavaScript (ES6) code snippets
- Electron debugger
Workspace settings (.vscode/settings.json):
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"eslint.validate": ["javascript"],
"files.associations": {
"*.md": "markdown"
}
}Create a .env file in the project root:
NODE_ENV=development
DEBUG=multi-grep-replacer:*
ELECTRON_ENABLE_LOGGING=truemulti-grep-replacer/
├── src/
│ ├── main/ # Main process code
│ │ ├── main.js # Application entry point
│ │ ├── config-manager.js # Configuration management
│ │ ├── file-operations.js # File system operations
│ │ ├── replacement-engine.js # Text replacement logic
│ │ ├── performance-optimizer.js # Performance optimizations
│ │ ├── memory-manager.js # Memory management
│ │ └── debug-logger.js # Debug logging system
│ ├── renderer/ # Renderer process code
│ │ ├── index.html # Main UI layout
│ │ ├── css/ # Stylesheets
│ │ │ ├── main.css # Main styles
│ │ │ ├── components.css # Component styles
│ │ │ └── themes.css # Theme definitions
│ │ └── js/ # JavaScript modules
│ │ ├── app.js # Application initialization
│ │ ├── ui-controller.js # UI event handling
│ │ ├── rule-manager.js # Replacement rules UI
│ │ ├── file-explorer.js # File selection UI
│ │ └── execution-controller.js # Execution flow
│ └── preload/ # Preload scripts
│ └── preload.js # Secure API bridge
├── tests/ # Test suites
│ ├── unit/ # Unit tests
│ ├── integration/ # Integration tests
│ └── e2e/ # End-to-end tests
├── config/ # Configuration files
│ ├── default.json # Default configuration
│ └── samples/ # Sample configurations
├── build/ # Build configuration
│ ├── electron-builder.config.js
│ └── entitlements.mac.plist
├── docs/ # Documentation
├── logs/ # Development logs
└── dist/ # Build output (ignored)
Handles configuration loading, saving, and validation.
class ConfigManager {
static async loadConfig(filePath) {
// Load and validate configuration
}
static async saveConfig(config, filePath) {
// Save configuration with validation
}
static validateConfig(config) {
// Validate configuration structure
}
}Manages file system operations with permissions and error handling.
class FileOperations {
static async findFiles(directory, options) {
// Recursive file search with filters
}
static async readFile(filePath) {
// Read file with encoding detection
}
static async writeFile(filePath, content) {
// Write file with error handling
}
}Core text replacement logic with performance optimization.
class ReplacementEngine {
constructor(rules, options) {
this.rules = rules;
this.options = options;
this.performanceOptimizer = new PerformanceOptimizer();
}
async processFiles(filePaths, progressCallback) {
// Process multiple files with optimization
}
}Main UI coordination and event handling.
class UIController {
constructor() {
this.ruleManager = new RuleManager();
this.fileExplorer = new FileExplorer();
this.executionController = new ExecutionController();
}
initialize() {
// Initialize UI components and event listeners
}
}Manages replacement rules in the UI.
class RuleManager {
addRule(rule) {
// Add rule to UI with validation
}
removeRule(ruleId) {
// Remove rule from UI
}
getRules() {
// Get all current rules
}
}All communication uses the secure contextBridge pattern:
// preload.js - Secure API exposure
contextBridge.exposeInMainWorld('electronAPI', {
selectFolder: () => ipcRenderer.invoke('select-folder'),
executeReplacement: (config) => ipcRenderer.invoke('execute-replacement', config),
onProgress: (callback) => ipcRenderer.on('replacement-progress', callback)
});
// main.js - IPC handlers
ipcMain.handle('select-folder', async () => {
const result = await dialog.showOpenDialog(mainWindow, {
properties: ['openDirectory']
});
return result;
});# Start development server
npm start
# Run with debugging
npm run debug
# Run with verbose logging
DEBUG=* npm start# Build for current platform
npm run build
# Build for specific platforms
npm run build:mac
npm run build:win
npm run build:linux
# Build for all platforms
npm run build:allThe build is configured in build/electron-builder.config.js:
module.exports = {
appId: 'com.multigrepreplacer.app',
productName: 'Multi Grep Replacer',
directories: {
output: 'dist'
},
files: [
'src/**/*',
'config/**/*',
'node_modules/**/*',
'!**/*.{test,spec}.js',
'!tests/**/*'
],
mac: {
category: 'public.app-category.developer-tools',
target: [
{ target: 'dmg', arch: ['universal'] }
]
},
win: {
target: [
{ target: 'nsis', arch: ['x64'] }
]
},
linux: {
target: [
{ target: 'AppImage', arch: ['x64'] },
{ target: 'deb', arch: ['x64'] }
]
}
};For production releases, configure code signing:
// macOS
mac: {
identity: "Developer ID Application: Your Name",
entitlements: "build/entitlements.mac.plist",
hardenedRuntime: true,
notarize: true
}
// Windows
win: {
certificateFile: "path/to/certificate.p12",
certificatePassword: process.env.CSC_KEY_PASSWORD
}tests/
├── unit/ # Unit tests
│ ├── main/ # Main process tests
│ ├── renderer/ # Renderer process tests
│ └── shared/ # Shared utilities tests
├── integration/ # Integration tests
│ ├── ipc-communication.test.js
│ └── file-operations.test.js
└── e2e/ # End-to-end tests
└── user-workflows.test.js
# All tests
npm test
# Unit tests only
npm run test:unit
# Integration tests
npm run test:integration
# End-to-end tests
npm run test:e2e
# Test with coverage
npm run test:coverage// tests/unit/main/config-manager.test.js
const ConfigManager = require('../../../src/main/config-manager');
describe('ConfigManager', () => {
test('should load valid configuration', async () => {
const config = await ConfigManager.loadConfig('config/default.json');
expect(config).toBeDefined();
expect(config.app_info).toBeDefined();
});
test('should validate configuration structure', () => {
const validConfig = { /* valid config */ };
const result = ConfigManager.validateConfig(validConfig);
expect(result.isValid).toBe(true);
});
});// tests/integration/ipc-communication.test.js
const { ipcRenderer } = require('electron');
describe('IPC Communication', () => {
test('should handle folder selection', async () => {
const result = await ipcRenderer.invoke('select-folder');
expect(result).toHaveProperty('path');
expect(result).toHaveProperty('canceled');
});
});// tests/performance/replacement-engine.test.js
const PerformanceTestSuite = require('../../../src/main/performance-test');
describe('Performance Tests', () => {
test('should process 1000 files within 30 seconds', async () => {
const testSuite = new PerformanceTestSuite();
const result = await testSuite.runFileProcessingTest();
expect(result.duration).toBeLessThan(30000);
expect(result.filesProcessed).toBe(1000);
expect(result.throughput).toBeGreaterThan(33); // files per second
});
});{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/src/main/main.js",
"args": ["--remote-debugging-port=9222"],
"env": {
"NODE_ENV": "development"
}
},
{
"name": "Debug Renderer Process",
"type": "chrome",
"request": "attach",
"port": 9222,
"webRoot": "${workspaceFolder}/src/renderer"
}
]
}// src/main/debug-logger.js
const { createFileLogger } = require('vibelogger');
class DebugLogger {
constructor() {
this.vibeLogger = createFileLogger('multi-grep-replacer');
}
logOperation(operation, data, options = {}) {
this.vibeLogger.info(operation, `Operation: ${operation}`, {
context: data,
...options
});
}
}// Monitor UI responsiveness
const performanceMonitor = new PerformanceMonitor();
performanceMonitor.trackUIResponse('button-click', () => {
// Button click handler
});// Use memory manager to track leaks
const memoryManager = new MemoryManager();
memoryManager.startMonitoring();
// Check for memory leaks periodically
setInterval(() => {
const usage = memoryManager.getCurrentMemoryUsage();
if (usage.heapUsed > MEMORY_WARNING_THRESHOLD) {
console.warn('Potential memory leak detected');
}
}, 10000);// Add detailed IPC logging
ipcMain.handle('test-handler', async (event, ...args) => {
vibeLogger.info('ipc_request', 'IPC request received', {
context: { handler: 'test-handler', args }
});
try {
const result = await processRequest(...args);
vibeLogger.info('ipc_response', 'IPC response sent', {
context: { handler: 'test-handler', result }
});
return result;
} catch (error) {
vibeLogger.error('ipc_error', 'IPC error occurred', {
context: { handler: 'test-handler', error: error.message }
});
throw error;
}
});Access Chrome DevTools for the renderer process:
// In main.js
if (isDevelopment) {
mainWindow.webContents.openDevTools();
}Note: Extension API will be available in version 2.0
// Future plugin interface
class Plugin {
constructor(context) {
this.context = context;
}
activate() {
// Plugin activation logic
this.context.registerCommand('my-command', this.handleCommand);
this.context.registerReplacementProvider(this.myProvider);
}
deactivate() {
// Cleanup logic
}
}class CustomReplacementProvider {
canHandle(rule) {
return rule.type === 'custom';
}
async process(content, rule) {
// Custom processing logic
return processedContent;
}
}We use ESLint and Prettier for consistent code formatting:
// .eslintrc.js
module.exports = {
extends: ['eslint:recommended'],
env: {
node: true,
es2022: true
},
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
},
rules: {
'no-console': 'warn',
'no-debugger': 'error',
'prefer-const': 'error'
}
};- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make changes with descriptive commits
- Run tests:
npm test - Run linting:
npm run lint - Push changes:
git push origin feature/my-feature - Create a pull request
type(scope): description
[optional body]
[optional footer]
Types: feat, fix, docs, style, refactor, test, chore
Example:
feat(ui): add dark mode support
Added automatic theme detection based on system preferences.
Includes manual theme toggle in settings.
Closes #123
- Description: Clear description of changes
- Testing: All tests must pass
- Documentation: Update relevant documentation
- Performance: Consider performance implications
- Breaking Changes: Clearly mark breaking changes
- Update version in
package.json - Update
CHANGELOG.md - Create git tag:
git tag v1.0.0 - Push tag:
git push origin v1.0.0 - GitHub Actions will build and create release
Last updated: 2025-08-18 | Version 1.0.0