fix(cli): resolve process hang and improve output consistency

- Add process.stdin.destroy() after rl.close() in all interactive commands
  to properly release stdin and allow process to exit cleanly
- Replace raw console.log with logger methods throughout CLI handlers
- Convert manual box drawing to logger.logBox() in daemon.ts
- Standardize menu formatting with logger.info() and logger.dim()
- Improve migration output to only show when migrations actually run

Fixes issue where process would not exit after "Setup complete!" message
due to stdin keeping the event loop alive.
This commit is contained in:
2025-10-20 00:32:06 +00:00
parent bb87316dd3
commit 3ff0dd7ac8
6 changed files with 93 additions and 76 deletions

View File

@@ -192,6 +192,7 @@ export class GroupHandler {
logger.log('\nGroup setup complete!');
} finally {
rl.close();
process.stdin.destroy();
}
} catch (error) {
logger.error(`Add group error: ${error instanceof Error ? error.message : String(error)}`);
@@ -309,6 +310,7 @@ export class GroupHandler {
logger.log('\nGroup edit complete!');
} finally {
rl.close();
process.stdin.destroy();
}
} catch (error) {
logger.error(`Edit group error: ${error instanceof Error ? error.message : String(error)}`);
@@ -366,6 +368,7 @@ export class GroupHandler {
});
rl.close();
process.stdin.destroy();
if (confirm !== 'y' && confirm !== 'yes') {
logger.log('Deletion cancelled.');

View File

@@ -213,9 +213,11 @@ export class ServiceHandler {
});
};
console.log('\nNUPST Uninstaller');
console.log('===============');
console.log('This will completely remove NUPST from your system.\n');
logger.log('');
logger.highlight('NUPST Uninstaller');
logger.dim('===============');
logger.log('This will completely remove NUPST from your system.');
logger.log('');
// Ask about removing configuration
const removeConfig = await prompt(
@@ -251,17 +253,20 @@ export class ServiceHandler {
}
if (!uninstallScriptPath) {
console.error('Could not locate uninstall.sh script. Aborting uninstall.');
logger.error('Could not locate uninstall.sh script. Aborting uninstall.');
rl.close();
process.stdin.destroy();
process.exit(1);
}
}
// Close readline before executing script
rl.close();
process.stdin.destroy();
// Execute uninstall.sh with the appropriate option
console.log(`\nRunning uninstaller from ${uninstallScriptPath}...`);
logger.log('');
logger.log(`Running uninstaller from ${uninstallScriptPath}...`);
// Pass the configuration removal option as an environment variable
const env = {
@@ -277,7 +282,7 @@ export class ServiceHandler {
stdio: 'inherit', // Show output in the terminal
});
} catch (error) {
console.error(`Uninstall failed: ${error instanceof Error ? error.message : String(error)}`);
logger.error(`Uninstall failed: ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
}
}

View File

@@ -47,6 +47,7 @@ export class UpsHandler {
await this.runAddProcess(prompt);
} finally {
rl.close();
process.stdin.destroy();
}
} catch (error) {
logger.error(`Add UPS error: ${error instanceof Error ? error.message : String(error)}`);
@@ -178,6 +179,7 @@ export class UpsHandler {
await this.runEditProcess(upsId, prompt);
} finally {
rl.close();
process.stdin.destroy();
}
} catch (error) {
logger.error(`Edit UPS error: ${error instanceof Error ? error.message : String(error)}`);
@@ -344,6 +346,7 @@ export class UpsHandler {
});
rl.close();
process.stdin.destroy();
if (confirm !== 'y' && confirm !== 'yes') {
logger.log('Deletion cancelled.');
@@ -667,10 +670,11 @@ export class UpsHandler {
// SNMP Version
const defaultVersion = snmpConfig.version || 1;
console.log('\nSNMP Version:');
console.log(' 1) SNMPv1');
console.log(' 2) SNMPv2c');
console.log(' 3) SNMPv3 (with security features)');
logger.log('');
logger.info('SNMP Version:');
logger.dim(' 1) SNMPv1');
logger.dim(' 2) SNMPv2c');
logger.dim(' 3) SNMPv3 (with security features)');
const versionInput = await prompt(`Select SNMP version [${defaultVersion}]: `);
const version = parseInt(versionInput, 10);
snmpConfig.version = versionInput.trim() && (version === 1 || version === 2 || version === 3)
@@ -697,13 +701,15 @@ export class UpsHandler {
snmpConfig: any,
prompt: (question: string) => Promise<string>,
): Promise<void> {
console.log('\nSNMPv3 Security Settings:');
logger.log('');
logger.info('SNMPv3 Security Settings:');
// Security Level
console.log('\nSecurity Level:');
console.log(' 1) noAuthNoPriv (No Authentication, No Privacy)');
console.log(' 2) authNoPriv (Authentication, No Privacy)');
console.log(' 3) authPriv (Authentication and Privacy)');
logger.log('');
logger.info('Security Level:');
logger.dim(' 1) noAuthNoPriv (No Authentication, No Privacy)');
logger.dim(' 2) authNoPriv (Authentication, No Privacy)');
logger.dim(' 3) authPriv (Authentication and Privacy)');
const defaultSecLevel = snmpConfig.securityLevel
? snmpConfig.securityLevel === 'noAuthNoPriv'
? 1
@@ -752,8 +758,9 @@ export class UpsHandler {
// Allow customizing the timeout value
const defaultTimeout = snmpConfig.timeout / 1000; // Convert from ms to seconds for display
console.log(
'\nSNMPv3 operations with authentication and privacy may require longer timeouts.',
logger.log('');
logger.info(
'SNMPv3 operations with authentication and privacy may require longer timeouts.',
);
const timeoutInput = await prompt(`SNMP Timeout in seconds [${defaultTimeout}]: `);
const timeout = parseInt(timeoutInput, 10);
@@ -773,9 +780,10 @@ export class UpsHandler {
prompt: (question: string) => Promise<string>,
): Promise<void> {
// Authentication protocol
console.log('\nAuthentication Protocol:');
console.log(' 1) MD5');
console.log(' 2) SHA');
logger.log('');
logger.info('Authentication Protocol:');
logger.dim(' 1) MD5');
logger.dim(' 2) SHA');
const defaultAuthProtocol = snmpConfig.authProtocol === 'SHA' ? 2 : 1;
const authProtocolInput = await prompt(
`Select Authentication Protocol [${defaultAuthProtocol}]: `,
@@ -799,9 +807,10 @@ export class UpsHandler {
prompt: (question: string) => Promise<string>,
): Promise<void> {
// Privacy protocol
console.log('\nPrivacy Protocol:');
console.log(' 1) DES');
console.log(' 2) AES');
logger.log('');
logger.info('Privacy Protocol:');
logger.dim(' 1) DES');
logger.dim(' 2) AES');
const defaultPrivProtocol = snmpConfig.privProtocol === 'AES' ? 2 : 1;
const privProtocolInput = await prompt(`Select Privacy Protocol [${defaultPrivProtocol}]: `);
const privProtocol = parseInt(privProtocolInput, 10) || defaultPrivProtocol;
@@ -822,7 +831,8 @@ export class UpsHandler {
thresholds: any,
prompt: (question: string) => Promise<string>,
): Promise<void> {
console.log('\nShutdown Thresholds:');
logger.log('');
logger.info('Shutdown Thresholds:');
// Battery threshold
const defaultBatteryThreshold = thresholds.battery || 60;
@@ -854,13 +864,14 @@ export class UpsHandler {
snmpConfig: any,
prompt: (question: string) => Promise<string>,
): Promise<void> {
console.log('\nUPS Model Selection:');
console.log(' 1) CyberPower');
console.log(' 2) APC');
console.log(' 3) Eaton');
console.log(' 4) TrippLite');
console.log(' 5) Liebert/Vertiv');
console.log(' 6) Custom (Advanced)');
logger.log('');
logger.info('UPS Model Selection:');
logger.dim(' 1) CyberPower');
logger.dim(' 2) APC');
logger.dim(' 3) Eaton');
logger.dim(' 4) TrippLite');
logger.dim(' 5) Liebert/Vertiv');
logger.dim(' 6) Custom (Advanced)');
const defaultModelValue = snmpConfig.upsModel === 'cyberpower'
? 1
@@ -891,8 +902,9 @@ export class UpsHandler {
snmpConfig.upsModel = 'liebert';
} else if (modelValue === 6) {
snmpConfig.upsModel = 'custom';
console.log('\nEnter custom OIDs for your UPS:');
console.log('(Leave blank to use standard RFC 1628 OIDs as fallback)');
logger.log('');
logger.info('Enter custom OIDs for your UPS:');
logger.dim('(Leave blank to use standard RFC 1628 OIDs as fallback)');
// Custom OIDs
const powerStatusOID = await prompt('Power Status OID: ');