feat(cli): add beautiful colored output and fix daemon exit bug
Some checks failed
CI / Type Check & Lint (push) Failing after 6s
CI / Build Test (Current Platform) (push) Successful in 6s
Release / build-and-release (push) Successful in 44s
CI / Build All Platforms (push) Successful in 50s
CI / Type Check & Lint (pull_request) Failing after 5s
CI / Build Test (Current Platform) (pull_request) Successful in 5s
CI / Build All Platforms (pull_request) Successful in 49s
Some checks failed
CI / Type Check & Lint (push) Failing after 6s
CI / Build Test (Current Platform) (push) Successful in 6s
Release / build-and-release (push) Successful in 44s
CI / Build All Platforms (push) Successful in 50s
CI / Type Check & Lint (pull_request) Failing after 5s
CI / Build Test (Current Platform) (pull_request) Successful in 5s
CI / Build All Platforms (pull_request) Successful in 49s
Major improvements: - Created color theme system (ts/colors.ts) with ANSI colors - Enhanced logger with colors, table formatting, and styled boxes - Fixed daemon exit bug - now stays running when no UPS configured - Added config hot-reload with file watcher for live updates - Beautified CLI help output with color-coded commands - Added showcase test demonstrating all output features - Fixed ANSI code handling for perfect table/box alignment Features: - Color-coded messages (success=green, error=red, warning=yellow, info=cyan) - Status symbols (●○◐◯ for running/stopped/starting/unknown) - Battery level colors (green>60%, yellow 30-60%, red<30%) - Table formatting with auto-sizing and column alignment - Styled boxes (success, error, warning, info styles) - Hot-reload: daemon watches config file and reloads automatically - Idle mode: daemon stays alive when no devices, checks periodically Daemon improvements: - No longer exits when no UPS devices configured - Enters idle monitoring loop waiting for config - File watcher detects config changes in real-time - Auto-reloads and starts monitoring when devices added - Logs warnings instead of errors for missing devices Technical fixes: - Strip ANSI codes when calculating text width for alignment - Use visible length for padding calculations in tables and boxes - Properly handle colored text in table cells and box lines Breaking changes: None (backward compatible)
This commit is contained in:
132
ts/daemon.ts
132
ts/daemon.ts
@@ -353,8 +353,9 @@ export class NupstDaemon {
|
||||
logger.log('Starting UPS monitoring...');
|
||||
|
||||
if (!this.config.upsDevices || this.config.upsDevices.length === 0) {
|
||||
logger.error('No UPS devices found in configuration. Monitoring stopped.');
|
||||
this.isRunning = false;
|
||||
logger.warn('No UPS devices found in configuration. Daemon will remain idle...');
|
||||
// Don't exit - enter idle monitoring mode instead
|
||||
await this.idleMonitoring();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -890,6 +891,133 @@ export class NupstDaemon {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Idle monitoring loop when no UPS devices are configured
|
||||
* Watches for config changes and reloads when detected
|
||||
*/
|
||||
private async idleMonitoring(): Promise<void> {
|
||||
const IDLE_CHECK_INTERVAL = 60000; // Check every 60 seconds
|
||||
let lastConfigCheck = Date.now();
|
||||
const CONFIG_CHECK_INTERVAL = 60000; // Check config every minute
|
||||
|
||||
logger.log('Entering idle monitoring mode...');
|
||||
logger.log('Daemon will check for config changes every 60 seconds');
|
||||
|
||||
// Start file watcher for hot-reload
|
||||
this.watchConfigFile();
|
||||
|
||||
while (this.isRunning) {
|
||||
try {
|
||||
const currentTime = Date.now();
|
||||
|
||||
// Periodically check if config has been updated
|
||||
if (currentTime - lastConfigCheck >= CONFIG_CHECK_INTERVAL) {
|
||||
try {
|
||||
// Try to load config
|
||||
const newConfig = await this.loadConfig();
|
||||
|
||||
// Check if we now have UPS devices configured
|
||||
if (newConfig.upsDevices && newConfig.upsDevices.length > 0) {
|
||||
logger.success('Configuration updated! UPS devices found. Starting monitoring...');
|
||||
this.initializeUpsStatus();
|
||||
// Exit idle mode and start monitoring
|
||||
await this.monitor();
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
// Config still doesn't exist or invalid, continue waiting
|
||||
}
|
||||
|
||||
lastConfigCheck = currentTime;
|
||||
}
|
||||
|
||||
await this.sleep(IDLE_CHECK_INTERVAL);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error during idle monitoring: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
await this.sleep(IDLE_CHECK_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
logger.log('Idle monitoring stopped');
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch config file for changes and reload automatically
|
||||
*/
|
||||
private watchConfigFile(): void {
|
||||
try {
|
||||
// Use Deno's file watcher to monitor config file
|
||||
const configDir = path.dirname(this.CONFIG_PATH);
|
||||
|
||||
// Spawn a background watcher (non-blocking)
|
||||
(async () => {
|
||||
try {
|
||||
const watcher = Deno.watchFs(configDir);
|
||||
|
||||
logger.log('Config file watcher started');
|
||||
|
||||
for await (const event of watcher) {
|
||||
// Only respond to modify events on the config file
|
||||
if (
|
||||
event.kind === 'modify' &&
|
||||
event.paths.some((p) => p.includes('config.json'))
|
||||
) {
|
||||
logger.info('Config file changed, reloading...');
|
||||
await this.reloadConfig();
|
||||
}
|
||||
|
||||
// Stop watching if daemon stopped
|
||||
if (!this.isRunning) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Watcher error - not critical, just log it
|
||||
logger.dim(
|
||||
`Config watcher stopped: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
})();
|
||||
} catch (error) {
|
||||
// If we can't start the watcher, just log and continue
|
||||
// The periodic check will still work
|
||||
logger.dim('Could not start config file watcher, using periodic checks only');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload configuration and restart monitoring if needed
|
||||
*/
|
||||
private async reloadConfig(): Promise<void> {
|
||||
try {
|
||||
const oldDeviceCount = this.config.upsDevices?.length || 0;
|
||||
|
||||
// Load the new configuration
|
||||
await this.loadConfig();
|
||||
const newDeviceCount = this.config.upsDevices?.length || 0;
|
||||
|
||||
if (newDeviceCount > 0 && oldDeviceCount === 0) {
|
||||
logger.success(`Configuration reloaded! Found ${newDeviceCount} UPS device(s)`);
|
||||
logger.info('Monitoring will start automatically...');
|
||||
} else if (newDeviceCount !== oldDeviceCount) {
|
||||
logger.success(
|
||||
`Configuration reloaded! UPS devices: ${oldDeviceCount} → ${newDeviceCount}`,
|
||||
);
|
||||
|
||||
// Reinitialize UPS status tracking
|
||||
this.initializeUpsStatus();
|
||||
} else {
|
||||
logger.success('Configuration reloaded successfully');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`Failed to reload config: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep for the specified milliseconds
|
||||
*/
|
||||
|
Reference in New Issue
Block a user