#!/usr/bin/env node /** # Bruno Development Script # # This script sets up and runs the Bruno development environment with hot-reloading. # It manages concurrent processes for various packages and provides cleanup on exit. # # Usage: # From the root of the project, run: # node ./scripts/dev-hot-reload.js [options] # or # npm run dev:watch -- [options] */ const { execSync } = require('child_process'); const { readFileSync } = require('fs'); // Get major version from .nvmrc (e.g. v22.1.0 -> v22) const NODE_VERSION = readFileSync('.nvmrc', 'utf8').trim().split('.')[0]; // Configuration const CONFIG = { NODE_VERSION, ELECTRON_WATCH_PATHS: [ 'packages/**/dist/', 'packages/bruno-electron/src/', 'packages/bruno-lang/src/', 'packages/bruno-lang/v2/src/', 'packages/bruno-js/src/', 'packages/bruno-schema/src/' ], ELECTRON_START_DELAY: 10, // seconds NODEMON_WATCH_DELAY: 1000 // milliseconds }; const COLORS = { red: '\x1b[0;31m', green: '\x1b[0;32m', yellow: '\x1b[1;33m', blue: '\x1b[0;34m', nc: '\x1b[0m' // No Color }; const LOG_LEVELS = { INFO: 'INFO', WARN: 'WARN', ERROR: 'ERROR', DEBUG: 'DEBUG', SUCCESS: 'SUCCESS' }; function log(level, msg) { let color = COLORS.nc; switch (level) { case LOG_LEVELS.INFO: case LOG_LEVELS.SUCCESS: color = COLORS.green; break; case LOG_LEVELS.WARN: color = COLORS.yellow; break; case LOG_LEVELS.ERROR: color = COLORS.red; break; case LOG_LEVELS.DEBUG: color = COLORS.blue; break; } const output = `${color}[${level}]${COLORS.nc} ${msg}`; if (level === LOG_LEVELS.ERROR) { console.error(output); } else { console.log(output); } } // Show help documentation function showHelp() { console.log(` Development Environment Setup for Bruno Usage: From the root of the project, run: npm run dev:watch -- [options] or node scripts/dev-hot-reload.js [options] Options: -s, --setup Clean all node_modules folders and re-install dependencies before starting -h, --help Show this help message Examples: # Start development environment npm run dev:watch # Start after cleaning node_modules npm run dev:watch -- --setup # Show this help npm run dev:watch -- --help `); } function commandExists(command) { try { execSync(`command -v ${command}`, { stdio: 'ignore' }); return true; } catch { return false; } } // Install global NPM package if not present function ensureGlobalPackage(packageName) { if (!commandExists(packageName)) { log(LOG_LEVELS.INFO, `Installing ${packageName} globally...`); execSync(`npm install -g ${packageName}`, { stdio: 'inherit' }); } } // Ensure correct node version function ensureNodeVersion(requiredVersion) { const currentVersion = process.version; if (!currentVersion.includes(requiredVersion)) { log(LOG_LEVELS.ERROR, `Node ${requiredVersion} is required but currently installed version is ${currentVersion}`); log(LOG_LEVELS.ERROR, `Please install node ${requiredVersion} and try again.`); log(LOG_LEVELS.ERROR, `You can run 'nvm install ${requiredVersion}' to install it, or 'nvm use ${requiredVersion}' if it's already installed.`); process.exit(1); } } function cleanNodeModules() { log(LOG_LEVELS.INFO, 'Removing all node_modules directories...'); execSync('find . -name "node_modules" -type d -prune -exec rm -rf {} +', { stdio: 'inherit' }); log(LOG_LEVELS.SUCCESS, 'Node modules cleanup completed'); } function reinstallDependencies() { log(LOG_LEVELS.INFO, 'Re-installing dependencies...'); execSync('npm install --legacy-peer-deps', { stdio: 'inherit' }); log(LOG_LEVELS.SUCCESS, 'Dependencies re-installation completed'); } // Setup development environment function startDevelopment() { log(LOG_LEVELS.INFO, 'Starting development servers...'); const concurrently = require('concurrently'); const watchPaths = CONFIG.ELECTRON_WATCH_PATHS.map(path => `--watch "${path}"`).join(' '); // concurrently command objects: { command, name, prefixColor, env, cwd, ipc } const commandObjects = [ { command: 'npm run watch --workspace=packages/bruno-common', name: 'common', prefixColor: 'magenta' }, { command: 'npm run watch --workspace=packages/bruno-converters', name: 'converters', prefixColor: 'green' }, { command: 'npm run watch --workspace=packages/bruno-query', name: 'query', prefixColor: 'blue' }, { command: 'npm run watch --workspace=packages/bruno-graphql-docs', name: 'graphql', prefixColor: 'white' }, { command: 'npm run watch --workspace=packages/bruno-requests', name: 'requests', prefixColor: 'gray' }, { command: 'npm run dev:web', name: 'react', prefixColor: 'cyan' }, { command: `sleep ${CONFIG.ELECTRON_START_DELAY} && nodemon ${watchPaths} --ext js,jsx,ts,tsx --delay ${CONFIG.NODEMON_WATCH_DELAY}ms --exec "npm run dev --workspace=packages/bruno-electron"`, name: 'electron', prefixColor: 'yellow', delay: CONFIG.ELECTRON_START_DELAY } ]; const { result } = concurrently(commandObjects, { prefix: '[{name}: {pid}]', killOthers: ['failure', 'success'], restartTries: 3, restartDelay: 1000 }); result .then(() => log(LOG_LEVELS.SUCCESS, 'All processes completed successfully')) .catch(err => { log(LOG_LEVELS.ERROR, 'Development environment failed to start'); console.error(err); process.exit(1); }); } // Main function (async function main() { const args = process.argv.slice(2); let runSetup = false; // Parse command line arguments for (const arg of args) { if (arg === '-s' || arg === '--setup') { runSetup = true; } else if (arg === '-h' || arg === '--help') { showHelp(); process.exit(0); } else { log(LOG_LEVELS.ERROR, `Unknown parameter: ${arg}`); showHelp(); process.exit(1); } } log(LOG_LEVELS.INFO, 'Initializing Bruno development environment...'); // Ensure required global packages and node version ensureNodeVersion(CONFIG.NODE_VERSION); ensureGlobalPackage('nodemon'); ensureGlobalPackage('concurrently'); // Run setup if requested if (runSetup) { cleanNodeModules(); reinstallDependencies(); } // Start development environment startDevelopment(); })().catch(err => { log(LOG_LEVELS.ERROR, 'An error occurred:'); console.error(err); process.exit(1); });