UNPKG

niledatabase

Version:

Command line interface for Nile databases

352 lines (350 loc) 16.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createLocalCommand = createLocalCommand; const commander_1 = require("commander"); const colors_1 = require("../lib/colors"); const globalOptions_1 = require("../lib/globalOptions"); const child_process_1 = require("child_process"); const readline_1 = __importDefault(require("readline")); const ora_1 = __importDefault(require("ora")); const util_1 = require("util"); const os_1 = __importDefault(require("os")); const execAsync = (0, util_1.promisify)(child_process_1.exec); async function waitForPostgres(getOptions, retries = 30, interval = 1000) { const command = 'docker exec nile-local pg_isready -U 00000000-0000-0000-0000-000000000000 -p 5432 -d test -h localhost'; for (let i = 0; i < retries; i++) { try { const { stdout } = await execAsync(command); if (stdout.includes('accepting connections')) { return true; } } catch (error) { const options = getOptions(); if (options.debug) { console.log(colors_1.theme.dim(`Waiting for PostgreSQL (attempt ${i + 1}/${retries})...`)); if (error.stdout) console.log(colors_1.theme.dim('stdout:'), error.stdout); if (error.stderr) console.log(colors_1.theme.dim('stderr:'), error.stderr); } // Wait before next attempt await new Promise(resolve => setTimeout(resolve, interval)); continue; } } return false; } async function isDockerRunning() { try { await execAsync('docker info'); return true; } catch (error) { return false; } } function getDockerStartCommand() { const platform = os_1.default.platform(); switch (platform) { case 'darwin': return 'open -a Docker'; case 'win32': return 'start /B "" "C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe"'; default: return 'systemctl start docker'; } } function getDockerInstallInstructions() { const platform = os_1.default.platform(); switch (platform) { case 'darwin': return 'https://docs.docker.com/desktop/install/mac-install/'; case 'win32': return 'https://docs.docker.com/desktop/install/windows-install/'; default: return 'https://docs.docker.com/engine/install/'; } } function createLocalCommand(getOptions) { const local = new commander_1.Command('local') .description('Manage local development environment') .addHelpText('after', ` Examples: ${(0, colors_1.formatCommand)('nile local start')} Start local development environment ${(0, colors_1.formatCommand)('nile local start', '--no-prompt')} Start without prompting for psql connection ${(0, colors_1.formatCommand)('nile local stop')} Stop local development environment ${(0, colors_1.formatCommand)('nile local info')} Show connection information ${(0, globalOptions_1.getGlobalOptionsHelp)()}`); local .command('info') .description('Show connection information for local environment') .action(async () => { try { // Check if container is running const { stdout } = await execAsync('docker ps --filter name=nile-local --format {{.Names}}'); if (!stdout.includes('nile-local')) { console.error(colors_1.theme.error('\nNo Nile local environment is currently running.')); console.log(colors_1.theme.dim('Start it with: nile local start')); process.exit(1); } // Display connection information console.log('\nConnection Information:'); console.log(colors_1.theme.info('Host: ') + 'localhost'); console.log(colors_1.theme.info('Port: ') + '5432'); console.log(colors_1.theme.info('Database: ') + 'test'); console.log(colors_1.theme.info('Username: ') + '00000000-0000-0000-0000-000000000000'); console.log(colors_1.theme.info('Password: ') + 'password'); // Show direct connection command in debug mode const options = getOptions(); if (options.debug) { console.log(colors_1.theme.dim('\nDirect connection command:')); console.log(colors_1.theme.dim('PGPASSWORD=password psql -h localhost -p 5432 -U 00000000-0000-0000-0000-000000000000 -d test')); } } catch (error) { const options = getOptions(); if (options.debug) { console.error(colors_1.theme.error('\nFailed to check environment status:'), error); } else { console.error(colors_1.theme.error('\nFailed to check environment status:'), error.message || 'Unknown error'); } process.exit(1); } }); local .command('stop') .description('Stop local development environment') .action(async () => { try { // Check if container is running const { stdout } = await execAsync('docker ps --filter name=nile-local --format {{.Names}}'); if (!stdout.includes('nile-local')) { console.error(colors_1.theme.error('\nNo Nile local environment is currently running.')); process.exit(1); } // Start spinner for stopping container const stopSpinner = (0, ora_1.default)({ text: 'Stopping local development environment...', color: 'cyan' }).start(); try { await execAsync('docker stop nile-local && docker rm nile-local'); stopSpinner.succeed('Local environment stopped successfully'); } catch (error) { stopSpinner.fail('Failed to stop local environment'); if (getOptions().debug) { console.error(colors_1.theme.error('Error details:'), error.message); } process.exit(1); } } catch (error) { const options = getOptions(); if (options.debug) { console.error(colors_1.theme.error('Failed to check container status:'), error); } else { console.error(colors_1.theme.error('Failed to check container status:'), error.message || 'Unknown error'); } process.exit(1); } }); local .command('start') .description('Start local development environment') .option('--no-prompt', 'Start without prompting for psql connection') .action(async (cmdOptions) => { try { // Check if Docker daemon is running const dockerRunning = await isDockerRunning(); if (!dockerRunning) { console.error(colors_1.theme.error('\nDocker daemon is not running.')); console.log(colors_1.theme.info('\nTo start Docker:')); console.log(colors_1.theme.dim(`Run: ${getDockerStartCommand()}`)); console.log(colors_1.theme.info('\nIf Docker is not installed:')); console.log(colors_1.theme.dim(`Visit: ${getDockerInstallInstructions()}`)); process.exit(1); } // Check if container is already running try { const { stdout } = await execAsync('docker ps --filter name=nile-local --format {{.Names}}'); if (stdout.includes('nile-local')) { console.error(colors_1.theme.error('\nA Nile local environment is already running.')); console.log(colors_1.theme.dim('To stop it, use: docker stop nile-local')); process.exit(1); } } catch (error) { // Ignore error, means docker ps failed which is fine } // Start spinner for Docker pull const pullSpinner = (0, ora_1.default)({ text: 'Pulling latest Nile testing container...', color: 'cyan' }).start(); try { await execAsync('docker pull ghcr.io/niledatabase/testingcontainer:latest'); pullSpinner.succeed('Latest Nile testing container pulled successfully'); } catch (error) { pullSpinner.fail('Failed to pull latest container'); if (getOptions().debug) { console.error(colors_1.theme.error('Error details:'), error.message); } process.exit(1); } // Start spinner for container launch const startSpinner = (0, ora_1.default)({ text: 'Starting local development environment...', color: 'cyan' }).start(); // Start Docker container in background const docker = (0, child_process_1.spawn)('docker', [ 'run', '--name', 'nile-local', '-d', // Run in background '-p', '5432:5432', 'ghcr.io/niledatabase/testingcontainer:latest' ]); // Collect any error output let errorOutput = ''; docker.stderr?.on('data', (data) => { errorOutput += data.toString(); }); // Wait for container to start await new Promise((resolve, reject) => { docker.on('close', (code) => { if (code !== 0) { reject(new Error(`Docker failed to start: ${errorOutput}`)); } else { resolve(undefined); } }); }); // Wait for PostgreSQL to be ready const readySpinner = (0, ora_1.default)({ text: 'Waiting for database to be ready...', color: 'cyan' }).start(); const isReady = await waitForPostgres(getOptions); if (!isReady) { readySpinner.fail('Database failed to start within timeout period'); console.log(colors_1.theme.dim('\nStopping container...')); try { await execAsync('docker stop nile-local && docker rm nile-local'); } catch (error) { // Ignore cleanup errors } process.exit(1); } readySpinner.succeed('Database is ready'); startSpinner.succeed('Local development environment started successfully'); // Display connection information console.log('\nConnection Information:'); console.log(colors_1.theme.info('Host: ') + 'localhost'); console.log(colors_1.theme.info('Port: ') + '5432'); console.log(colors_1.theme.info('Database: ') + 'test'); console.log(colors_1.theme.info('Username: ') + '00000000-0000-0000-0000-000000000000'); console.log(colors_1.theme.info('Password: ') + 'password'); if (cmdOptions.prompt) { // Create readline interface const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout }); // Ask if user wants to connect with psql rl.question('\nWould you like to connect using psql? (y/N) ', async (answer) => { rl.close(); if (answer.toLowerCase() === 'y') { const connectSpinner = (0, ora_1.default)({ text: 'Connecting...', color: 'cyan' }).start(); // Add a longer delay to ensure the database is fully ready for connections await new Promise(resolve => setTimeout(resolve, 3000)); // Connect using psql with individual parameters const psql = (0, child_process_1.spawn)('psql', [ '-h', 'localhost', '-p', '5432', '-U', '00000000-0000-0000-0000-000000000000', '-d', 'test', '-w' // Never prompt for password ], { stdio: 'inherit', env: { ...process.env, PGPASSWORD: 'password' // Set password via environment variable } }); // Stop the spinner immediately as psql will take over the terminal connectSpinner.stop(); // Handle psql exit psql.on('exit', (code) => { if (code !== 0) { console.error(colors_1.theme.error('\nFailed to connect using psql. Please check if psql is installed.')); if (getOptions().debug) { console.error(colors_1.theme.dim('Try connecting directly with:')); console.error(colors_1.theme.dim('PGPASSWORD=password psql -h localhost -p 5432 -U 00000000-0000-0000-0000-000000000000 -d test')); } } // Note: Don't exit here as the Docker container should keep running }); // Handle psql error psql.on('error', (error) => { connectSpinner.fail('Failed to launch psql'); console.error(colors_1.theme.error('\nError launching psql:'), error.message); if (error.message.includes('ENOENT')) { console.error(colors_1.theme.error('Please make sure psql is installed and available in your PATH')); } }); } else { console.log(colors_1.theme.dim('\nYou can connect to the database using your preferred client with the connection information above.')); console.log(colors_1.theme.dim('To stop the environment, use: nile local stop')); } }); } else { console.log(colors_1.theme.dim('\nTo stop the environment, use: nile local stop')); } // Handle process termination process.on('SIGINT', async () => { console.log(colors_1.theme.dim('\nStopping local development environment...')); try { await execAsync('docker stop nile-local && docker rm nile-local'); console.log(colors_1.theme.success('Local environment stopped successfully')); } catch (error) { console.error(colors_1.theme.error('Failed to stop local environment cleanly')); } process.exit(0); }); } catch (error) { const options = getOptions(); if (options.debug) { console.error(colors_1.theme.error('Failed to start local environment:'), error); } else { console.error(colors_1.theme.error('Failed to start local environment:'), error.message || 'Unknown error'); } // Cleanup on error try { await execAsync('docker stop nile-local && docker rm nile-local'); } catch (cleanupError) { // Ignore cleanup errors } process.exit(1); } }); return local; }