forked from cerc-io/snowballtools-base
Consolidate wrapper components
This commit is contained in:
Executable
+109
@@ -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
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
Executable
+53
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user