Consolidate wrapper components

This commit is contained in:
icld
2025-02-20 09:43:07 -08:00
parent 8353c1ecbe
commit 9d7ab3abac
43 changed files with 3932 additions and 2966 deletions
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env node
const { exec } = require('child_process');
const { promisify } = require('util');
const chalk = require('chalk');
const execAsync = promisify(exec);
const ports = [3000, 3001, 8000];
async function killProcessOnPort(port) {
try {
console.log(chalk.blue(`🔍 Checking if port ${port} is in use...`));
if (process.platform === 'darwin' || process.platform === 'linux') {
// Try with lsof first
try {
const { stdout } = await execAsync(`lsof -i :${port} -t`);
if (stdout.trim()) {
console.log(chalk.yellow(`⚠️ Port ${port} is in use. Attempting to terminate processes...`));
const pids = stdout.trim().split('\n').filter(Boolean);
for (const pid of pids) {
try {
await execAsync(`kill -9 ${pid}`);
console.log(chalk.green(`✅ Terminated process ${pid} using port ${port}`));
} catch (error) {
console.error(chalk.red(`Failed to kill process ${pid}: ${error.message}`));
}
}
} else {
console.log(chalk.green(`✅ Port ${port} is available`));
}
} catch (lsofError) {
// Fallback to netstat if lsof fails
try {
const { stdout } = await execAsync(`netstat -anp 2>/dev/null | grep ${port} | grep LISTEN`);
if (stdout.trim()) {
console.log(chalk.yellow(`⚠️ Port ${port} is in use. Using netstat fallback...`));
// Extract PIDs with regex - this is more complex with netstat
const pidRegex = /\s(\d+)\/\w+\s*$/;
const matches = stdout.match(pidRegex);
if (matches && matches[1]) {
const pid = matches[1];
try {
await execAsync(`kill -9 ${pid}`);
console.log(chalk.green(`✅ Terminated process ${pid} using port ${port}`));
} catch (error) {
console.error(chalk.red(`Failed to kill process ${pid}: ${error.message}`));
}
} else {
console.log(chalk.yellow(`⚠️ Could not extract PID from netstat output`));
}
} else {
console.log(chalk.green(`✅ Port ${port} is available`));
}
} catch (netstatError) {
console.log(chalk.yellow(`⚠️ Could not check port ${port}: Both lsof and netstat failed`));
}
}
} else if (process.platform === 'win32') {
// Windows approach
const { stdout } = await execAsync(`netstat -ano | findstr :${port}`);
if (stdout.trim()) {
console.log(chalk.yellow(`⚠️ Port ${port} is in use. Attempting to terminate processes...`));
const pids = stdout.split('\n')
.filter(line => line.includes(`:${port}`))
.map(line => line.trim().split(/\s+/).pop() || '')
.filter(Boolean);
for (const pid of pids) {
try {
await execAsync(`taskkill /F /PID ${pid}`);
console.log(chalk.green(`✅ Terminated process ${pid} using port ${port}`));
} catch (error) {
console.error(chalk.red(`Failed to kill process ${pid}: ${error.message}`));
}
}
} else {
console.log(chalk.green(`✅ Port ${port} is available`));
}
}
} catch (error) {
// General error handling
console.log(chalk.yellow(`⚠️ Could not check port ${port}: ${error.message}`));
console.log(chalk.blue('Continuing anyway...'));
}
}
async function main() {
console.log(chalk.cyan.bold('🧹 Port Cleanup Utility'));
console.log(chalk.cyan('---------------------'));
for (const port of ports) {
await killProcessOnPort(port);
}
console.log(chalk.green('✅ Port cleanup complete'));
}
main().catch(error => {
console.error(chalk.red(`❌ Unhandled error: ${error.message}`));
console.log(chalk.yellow('Continuing with startup anyway...'));
process.exit(0); // Exit successfully even if port killing fails
});
+303
View File
@@ -0,0 +1,303 @@
#!/usr/bin/env node
const { spawn, exec } = require('child_process');
const chalk = require('chalk');
const path = require('path');
const fs = require('fs');
const { promisify } = require('util');
const os = require('os');
const execAsync = promisify(exec);
// Configuration
const config = {
rootDir: process.cwd(),
backendDir: path.join(process.cwd(), 'packages/backend'),
frontendDir: path.join(process.cwd(), 'packages/frontend'),
ports: {
backend: 8000,
frontend: 3000,
alternativeFrontend: 3001
}
};
// Helper to check if a port is in use and kill the process if needed
async function ensurePortAvailable(port) {
try {
console.log(chalk.blue(`🔍 Checking if port ${port} is in use...`));
// Different commands for different operating systems
let command;
if (process.platform === 'win32') {
command = `netstat -ano | findstr :${port}`;
} else {
command = `lsof -i :${port} -t`;
}
const { stdout } = await execAsync(command);
if (stdout.trim()) {
console.log(chalk.yellow(`⚠️ Port ${port} is in use. Attempting to terminate processes...`));
let pids;
if (process.platform === 'win32') {
// Extract PIDs from Windows netstat output
pids = stdout.split('\n')
.filter(line => line.includes(`:${port}`))
.map(line => line.trim().split(/\s+/).pop() || '')
.filter(Boolean);
} else {
// Unix lsof already returns PIDs directly
pids = stdout.trim().split('\n').filter(Boolean);
}
// Kill each process
for (const pid of pids) {
try {
const killCommand = process.platform === 'win32'
? `taskkill /F /PID ${pid}`
: `kill -9 ${pid}`;
await execAsync(killCommand);
console.log(chalk.green(`✅ Terminated process ${pid} using port ${port}`));
} catch (error) {
console.error(chalk.red(`Failed to kill process ${pid}: ${error}`));
}
}
} else {
console.log(chalk.green(`✅ Port ${port} is available`));
}
} catch (error) {
// If the command fails (e.g., lsof not found), assume port is available
console.log(chalk.yellow(`⚠️ Could not check port ${port}: ${error}`));
}
}
// Helper to run commands with proper output handling
function runCommand(command, args, cwd, label) {
return new Promise((resolve, reject) => {
console.log(chalk.blue(`🚀 Starting: ${label}...`));
const childProcess = spawn(command, args, {
cwd,
stdio: 'pipe',
shell: true
});
let stdout = '';
let stderr = '';
childProcess.stdout && childProcess.stdout.on('data', (data) => {
const output = data.toString();
stdout += output;
process.stdout.write(chalk.gray(`[${label}] `) + output);
});
childProcess.stderr && childProcess.stderr.on('data', (data) => {
const output = data.toString();
stderr += output;
process.stderr.write(chalk.yellow(`[${label}] `) + output);
});
childProcess.on('error', (error) => {
console.error(chalk.red(`❌ Error in ${label}: ${error.message}`));
reject(error);
});
childProcess.on('close', (code) => {
if (code === 0) {
console.log(chalk.green(`✅ Completed: ${label}`));
resolve();
} else {
console.error(chalk.red(`❌ Failed: ${label} (exit code: ${code})`));
reject(new Error(`Command failed with exit code ${code}: ${stderr}`));
}
});
});
}
// Check if iTerm2 is available (macOS only)
async function isITerm2Available() {
if (process.platform !== 'darwin') return false;
try {
const { stdout } = await execAsync('osascript -e "exists application \\"iTerm2\\""');
return stdout.trim() === 'true';
} catch (error) {
return false;
}
}
// Open split panes in iTerm2
async function openITerm2SplitPanes(backendCommand, frontendCommand) {
const script = `
tell application "iTerm2"
create window with default profile
tell current window
tell current session
set backendSession to (split horizontally with default profile)
set frontendSession to (split vertically with default profile)
select
write text "cd ${config.backendDir} && clear && echo '🚀 BACKEND SERVER' && ${backendCommand}"
set name to "Backend Server"
tell backendSession
select
write text "cd ${config.frontendDir} && clear && echo '🚀 FRONTEND DEV SERVER' && ${frontendCommand}"
set name to "Frontend Dev Server"
end tell
tell frontendSession
select
write text "cd ${config.rootDir} && clear && echo '📊 MONOREPO ROOT'"
set name to "Monorepo Root"
end tell
end tell
end tell
end tell
`;
try {
await execAsync(`osascript -e '${script}'`);
return true;
} catch (error) {
console.error(chalk.yellow(`⚠️ Failed to open iTerm2 with split panes: ${error.message}`));
return false;
}
}
// Open a new terminal based on platform
async function openTerminals(backendCommand, frontendCommand) {
// First check if we can use iTerm2 on macOS
if (process.platform === 'darwin') {
const iTerm2Available = await isITerm2Available();
if (iTerm2Available) {
console.log(chalk.blue('🖥️ Opening iTerm2 with split panes...'));
const success = await openITerm2SplitPanes(backendCommand, frontendCommand);
if (success) return true;
}
}
// Fallback to separate terminal windows
const escapedBackendCommand = backendCommand.replace(/"/g, '\\"');
const escapedFrontendCommand = frontendCommand.replace(/"/g, '\\"');
const escapedBackendDir = config.backendDir.replace(/"/g, '\\"');
const escapedFrontendDir = config.frontendDir.replace(/"/g, '\\"');
// Platform-specific terminal opening commands
if (process.platform === 'darwin') {
// macOS - Terminal.app
const backendScript = `
tell application "Terminal"
do script "cd \\"${escapedBackendDir}\\" && clear && echo '🚀 BACKEND SERVER' && ${escapedBackendCommand}"
set custom title of front window to "Backend Server"
end tell
`;
const frontendScript = `
tell application "Terminal"
do script "cd \\"${escapedFrontendDir}\\" && clear && echo '🚀 FRONTEND DEV SERVER' && ${escapedFrontendCommand}"
set custom title of front window to "Frontend Dev Server"
end tell
`;
await execAsync(`osascript -e '${backendScript}'`);
await execAsync(`osascript -e '${frontendScript}'`);
} else if (process.platform === 'win32') {
// Windows - try Windows Terminal first, fall back to cmd
try {
// Windows Terminal (supports multiple tabs)
await execAsync(`wt -w 0 -d "${escapedBackendDir}" cmd /k "title Backend Server && ${escapedBackendCommand}" ; split-pane -d "${escapedFrontendDir}" cmd /k "title Frontend Dev Server && ${escapedFrontendCommand}"`);
} catch (error) {
// Fallback to regular cmd windows
spawn('cmd.exe', ['/c', 'start', 'cmd.exe', '/K',
`cd /d "${escapedBackendDir}" && title Backend Server && ${escapedBackendCommand}`]);
spawn('cmd.exe', ['/c', 'start', 'cmd.exe', '/K',
`cd /d "${escapedFrontendDir}" && title Frontend Dev Server && ${escapedFrontendCommand}`]);
}
} else {
// Linux terminals with split support
try {
// Try Tilix (supports split screen)
await execAsync(`tilix --window-style=disable-csd-hide-toolbar --maximize --session-file=<(echo '[{"command":"cd ${escapedBackendDir} && ${escapedBackendCommand}","title":"Backend Server"},{"command":"cd ${escapedFrontendDir} && ${escapedFrontendCommand}","title":"Frontend Dev Server"}]')`);
} catch (error) {
try {
// Try Terminator (supports split screen)
await execAsync(`terminator --maximize -e "bash -c 'cd ${escapedBackendDir} && ${escapedBackendCommand}'" -e "bash -c 'cd ${escapedFrontendDir} && ${escapedFrontendCommand}'"`);
} catch (error) {
// Fallback to separate gnome-terminal windows
try {
spawn('gnome-terminal', ['--', 'bash', '-c',
`cd "${escapedBackendDir}" && echo -e "\\033]0;Backend Server\\007" && ${escapedBackendCommand}; exec bash`]);
spawn('gnome-terminal', ['--', 'bash', '-c',
`cd "${escapedFrontendDir}" && echo -e "\\033]0;Frontend Dev Server\\007" && ${escapedFrontendCommand}; exec bash`]);
} catch (error) {
// Last resort: try xterm
spawn('xterm', ['-T', 'Backend Server', '-e',
`cd "${escapedBackendDir}" && ${escapedBackendCommand}; exec bash`]);
spawn('xterm', ['-T', 'Frontend Dev Server', '-e',
`cd "${escapedFrontendDir}" && ${escapedFrontendCommand}; exec bash`]);
}
}
}
}
return true;
}
// Check if directory exists
function checkDirectoryExists(dir, name) {
if (!fs.existsSync(dir)) {
console.error(chalk.red(`❌ Error: ${name} directory not found at ${dir}`));
throw new Error(`Directory not found: ${dir}`);
}
}
// Main execution function
async function runMonorepoWorkflow() {
console.log(chalk.cyan.bold('📦 Monorepo Workflow Script'));
console.log(chalk.cyan('---------------------------'));
try {
// Validate directories
checkDirectoryExists(config.rootDir, 'Root');
checkDirectoryExists(config.backendDir, 'Backend');
checkDirectoryExists(config.frontendDir, 'Frontend');
// Ensure all required ports are available
await ensurePortAvailable(config.ports.backend);
await ensurePortAvailable(config.ports.frontend);
await ensurePortAvailable(config.ports.alternativeFrontend);
// Step 1: Install dependencies at root
await runCommand('yarn', [], config.rootDir, 'Root dependency installation');
// Step 2: Build packages (ignoring frontend)
await runCommand('yarn', ['build', '--ignore', 'frontend'], config.rootDir, 'Building packages');
// Step 3: Start services in split terminal
console.log(chalk.blue('🚀 Opening terminal with services...'));
await openTerminals('yarn start', 'yarn dev');
console.log(chalk.green.bold('✅ Development environment started!'));
console.log(chalk.cyan('Backend running at:') + chalk.yellow(` http://localhost:${config.ports.backend}`));
console.log(chalk.cyan('Frontend running at:') + chalk.yellow(` http://localhost:${config.ports.frontend}`));
console.log(chalk.gray('Check the opened terminal windows for detailed logs.'));
// Exit successfully
process.exit(0);
} catch (error) {
console.error(chalk.red.bold('❌ Workflow failed:'));
console.error(chalk.red(error.message));
process.exit(1);
}
}
// Execute the workflow
runMonorepoWorkflow().catch(error => {
console.error(chalk.red.bold('❌ Unhandled error:'));
console.error(error);
process.exit(1);
});
+53
View File
@@ -0,0 +1,53 @@
#!/usr/bin/env node
const { execSync } = require('child_process');
const chalk = require('chalk');
const path = require('path');
const fs = require('fs');
const rootDir = process.cwd();
const backendDir = path.join(rootDir, 'packages/backend');
const frontendDir = path.join(rootDir, 'packages/frontend');
// Create shell scripts for each service
const backendScript = path.join(rootDir, 'scripts', 'start-backend.sh');
const frontendScript = path.join(rootDir, 'scripts', 'start-frontend.sh');
// Write shell scripts
fs.writeFileSync(backendScript, `#!/bin/bash
cd "${backendDir}"
echo "🚀 Starting Backend Server..."
yarn start
`, { mode: 0o755 });
fs.writeFileSync(frontendScript, `#!/bin/bash
cd "${frontendDir}"
echo "🚀 Starting Frontend Dev Server..."
yarn dev
`, { mode: 0o755 });
console.log(chalk.cyan.bold('📦 Starting development environment'));
try {
// Clean up ports first
console.log(chalk.blue('🧹 Cleaning up ports...'));
execSync('yarn kill:ports', { stdio: 'inherit' });
// Build packages
console.log(chalk.blue('🔨 Building packages...'));
execSync('yarn build --ignore frontend', { stdio: 'inherit' });
console.log(chalk.green('✅ Build complete'));
console.log(chalk.blue('🚀 Starting services in separate terminals...'));
// Open new terminals with the shell scripts
execSync(`open -a Terminal ${backendScript}`);
execSync(`open -a Terminal ${frontendScript}`);
console.log(chalk.green('✅ Services started in separate terminal windows'));
console.log(chalk.cyan('Backend running at:') + chalk.yellow(' http://localhost:8000'));
console.log(chalk.cyan('Frontend running at:') + chalk.yellow(' http://localhost:3000'));
} catch (error) {
console.error(chalk.red(`❌ Error: ${error.message}`));
process.exit(1);
}