From c2d39cc19a7caaa39b827f55679ce32835d6d940 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Sat, 18 Oct 2025 21:07:57 +0000 Subject: [PATCH] fix: resolve all TypeScript type errors across codebase for Deno strict mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Comprehensive type safety improvements across all CLI handlers and daemon: **Error handling type fixes:** - Add 'error instanceof Error' checks before accessing error.message throughout - Fix all error/retryError/stdError/upsError type assertions - Replace direct error.message with proper type guards **Switch case improvements:** - Wrap case block declarations in braces to satisfy deno-lint - Fix no-case-declarations warnings in CLI command handlers **Null/undefined safety:** - Add checks for config.snmp and config.thresholds before access - Fix IUpsStatus lastStatusChange to handle undefined with default value - Add proper null checks in legacy configuration paths **Type annotations:** - Add explicit type annotations to lambda parameters (groupId, updateAvailable, etc.) - Add TUpsModel type cast for 'cyberpower' default - Import and use INupstConfig type where needed **Parameter type fixes:** - Fix implicit 'any' type errors in array callbacks - Add type annotations to filter/find/map parameters Files modified: - ts/cli.ts: config.snmp/thresholds null checks, unused error variable fixes - ts/cli/group-handler.ts: 4 error.message fixes + 2 parameter type annotations - ts/cli/service-handler.ts: 3 error.message fixes - ts/cli/ups-handler.ts: 5 error.message fixes + config checks + TUpsModel import - ts/daemon.ts: 8 error.message fixes + IUpsStatus lastStatusChange fix + updateAvailable type - ts/nupst.ts: 1 error.message fix - ts/systemd.ts: 5 error.message fixes + parameter type annotation All tests passing (3/3 SNMP tests + 10/10 logger tests) Type check: ✓ No errors --- ts/cli.ts | 96 ++++++++++++++++++++++----------------- ts/cli/group-handler.ts | 12 ++--- ts/cli/service-handler.ts | 6 +-- ts/cli/ups-handler.ts | 32 +++++++++---- ts/daemon.ts | 36 ++++++++------- ts/nupst.ts | 2 +- ts/systemd.ts | 14 +++--- 7 files changed, 112 insertions(+), 86 deletions(-) diff --git a/ts/cli.ts b/ts/cli.ts index 7ef1296..0640b84 100644 --- a/ts/cli.ts +++ b/ts/cli.ts @@ -115,13 +115,14 @@ export class NupstCli { case 'add': await upsHandler.add(); break; - case 'edit': + case 'edit': { const upsId = subcommandArgs[0]; await upsHandler.edit(upsId); break; + } case 'remove': case 'rm': // Alias - case 'delete': // Backward compatibility + case 'delete': { // Backward compatibility const upsIdToRemove = subcommandArgs[0]; if (!upsIdToRemove) { logger.error('UPS ID is required for remove command'); @@ -130,6 +131,7 @@ export class NupstCli { } await upsHandler.remove(upsIdToRemove); break; + } case 'list': case 'ls': // Alias await upsHandler.list(); @@ -153,7 +155,7 @@ export class NupstCli { case 'add': await groupHandler.add(); break; - case 'edit': + case 'edit': { const groupId = subcommandArgs[0]; if (!groupId) { logger.error('Group ID is required for edit command'); @@ -162,9 +164,10 @@ export class NupstCli { } await groupHandler.edit(groupId); break; + } case 'remove': case 'rm': // Alias - case 'delete': // Backward compatibility + case 'delete': { // Backward compatibility const groupIdToRemove = subcommandArgs[0]; if (!groupIdToRemove) { logger.error('Group ID is required for remove command'); @@ -173,6 +176,7 @@ export class NupstCli { } await groupHandler.remove(groupIdToRemove); break; + } case 'list': case 'ls': // Alias await groupHandler.list(); @@ -291,7 +295,7 @@ export class NupstCli { // Try to load configuration try { await this.nupst.getDaemon().loadConfig(); - } catch (error) { + } catch (_error) { const errorBoxWidth = 45; logger.logBoxTitle('Configuration Error', errorBoxWidth); logger.logBoxLine('No configuration found.'); @@ -351,49 +355,57 @@ export class NupstCli { } } else { // Legacy single UPS configuration - // SNMP Settings - logger.logBoxLine('SNMP Settings:'); - logger.logBoxLine(` Host: ${config.snmp.host}`); - logger.logBoxLine(` Port: ${config.snmp.port}`); - logger.logBoxLine(` Version: ${config.snmp.version}`); - logger.logBoxLine(` UPS Model: ${config.snmp.upsModel || 'cyberpower'}`); + if (!config.snmp) { + logger.logBoxLine('Error: Legacy configuration missing SNMP settings'); + } else { + // SNMP Settings + logger.logBoxLine('SNMP Settings:'); + logger.logBoxLine(` Host: ${config.snmp.host}`); + logger.logBoxLine(` Port: ${config.snmp.port}`); + logger.logBoxLine(` Version: ${config.snmp.version}`); + logger.logBoxLine(` UPS Model: ${config.snmp.upsModel || 'cyberpower'}`); - if (config.snmp.version === 1 || config.snmp.version === 2) { - logger.logBoxLine(` Community: ${config.snmp.community}`); - } else if (config.snmp.version === 3) { - logger.logBoxLine(` Security Level: ${config.snmp.securityLevel}`); - logger.logBoxLine(` Username: ${config.snmp.username}`); + if (config.snmp.version === 1 || config.snmp.version === 2) { + logger.logBoxLine(` Community: ${config.snmp.community}`); + } else if (config.snmp.version === 3) { + logger.logBoxLine(` Security Level: ${config.snmp.securityLevel}`); + logger.logBoxLine(` Username: ${config.snmp.username}`); - // Show auth and privacy details based on security level - if ( - config.snmp.securityLevel === 'authNoPriv' || - config.snmp.securityLevel === 'authPriv' - ) { - logger.logBoxLine(` Auth Protocol: ${config.snmp.authProtocol || 'None'}`); + // Show auth and privacy details based on security level + if ( + config.snmp.securityLevel === 'authNoPriv' || + config.snmp.securityLevel === 'authPriv' + ) { + logger.logBoxLine(` Auth Protocol: ${config.snmp.authProtocol || 'None'}`); + } + + if (config.snmp.securityLevel === 'authPriv') { + logger.logBoxLine(` Privacy Protocol: ${config.snmp.privProtocol || 'None'}`); + } + + // Show timeout value + logger.logBoxLine(` Timeout: ${config.snmp.timeout / 1000} seconds`); } - if (config.snmp.securityLevel === 'authPriv') { - logger.logBoxLine(` Privacy Protocol: ${config.snmp.privProtocol || 'None'}`); + // Show OIDs if custom model is selected + if (config.snmp.upsModel === 'custom' && config.snmp.customOIDs) { + logger.logBoxLine('Custom OIDs:'); + logger.logBoxLine(` Power Status: ${config.snmp.customOIDs.POWER_STATUS || 'Not set'}`); + logger.logBoxLine( + ` Battery Capacity: ${config.snmp.customOIDs.BATTERY_CAPACITY || 'Not set'}` + ); + logger.logBoxLine(` Battery Runtime: ${config.snmp.customOIDs.BATTERY_RUNTIME || 'Not set'}`); } - - // Show timeout value - logger.logBoxLine(` Timeout: ${config.snmp.timeout / 1000} seconds`); - } - - // Show OIDs if custom model is selected - if (config.snmp.upsModel === 'custom' && config.snmp.customOIDs) { - logger.logBoxLine('Custom OIDs:'); - logger.logBoxLine(` Power Status: ${config.snmp.customOIDs.POWER_STATUS || 'Not set'}`); - logger.logBoxLine( - ` Battery Capacity: ${config.snmp.customOIDs.BATTERY_CAPACITY || 'Not set'}` - ); - logger.logBoxLine(` Battery Runtime: ${config.snmp.customOIDs.BATTERY_RUNTIME || 'Not set'}`); } // Thresholds - logger.logBoxLine('Thresholds:'); - logger.logBoxLine(` Battery: ${config.thresholds.battery}%`); - logger.logBoxLine(` Runtime: ${config.thresholds.runtime} minutes`); + if (!config.thresholds) { + logger.logBoxLine('Error: Legacy configuration missing threshold settings'); + } else { + logger.logBoxLine('Thresholds:'); + logger.logBoxLine(` Battery: ${config.thresholds.battery}%`); + logger.logBoxLine(` Runtime: ${config.thresholds.runtime} minutes`); + } logger.logBoxLine(`Check Interval: ${config.checkInterval / 1000} seconds`); // Configuration file location @@ -419,11 +431,11 @@ export class NupstCli { logger.logBoxLine(`Service Active: ${isActive ? 'Yes' : 'No'}`); logger.logBoxLine(`Service Enabled: ${isEnabled ? 'Yes' : 'No'}`); logger.logBoxEnd(); - } catch (error) { + } catch (_error) { // Ignore errors checking service status } } catch (error) { - logger.error(`Failed to display configuration: ${error.message}`); + logger.error(`Failed to display configuration: ${error instanceof Error ? error.message : String(error)}`); } } diff --git a/ts/cli/group-handler.ts b/ts/cli/group-handler.ts index 5dcb5b4..96f1f46 100644 --- a/ts/cli/group-handler.ts +++ b/ts/cli/group-handler.ts @@ -78,7 +78,7 @@ export class GroupHandler { logger.logBoxEnd(); } catch (error) { - logger.error(`Failed to list UPS groups: ${error.message}`); + logger.error(`Failed to list UPS groups: ${error instanceof Error ? error.message : String(error)}`); } } @@ -187,7 +187,7 @@ export class GroupHandler { rl.close(); } } catch (error) { - logger.error(`Add group error: ${error.message}`); + logger.error(`Add group error: ${error instanceof Error ? error.message : String(error)}`); } } @@ -298,7 +298,7 @@ export class GroupHandler { rl.close(); } } catch (error) { - logger.error(`Edit group error: ${error.message}`); + logger.error(`Edit group error: ${error instanceof Error ? error.message : String(error)}`); } } @@ -375,7 +375,7 @@ export class GroupHandler { // Check if service is running and restart it if needed this.nupst.getUpsHandler().restartServiceIfRunning(); } catch (error) { - logger.error(`Failed to delete group: ${error.message}`); + logger.error(`Failed to delete group: ${error instanceof Error ? error.message : String(error)}`); } } @@ -485,7 +485,7 @@ export class GroupHandler { return; } - const group = config.groups.find(g => g.id === groupId); + const group = config.groups.find((g: { id: string }) => g.id === groupId); if (!group) { logger.error(`Group with ID "${groupId}" not found.`); return; @@ -493,7 +493,7 @@ export class GroupHandler { // Show current assignments logger.log(`\nUPS devices in group "${group.name}" (${group.id}):`); - const upsInGroup = config.upsDevices.filter(ups => ups.groups && ups.groups.includes(groupId)); + const upsInGroup = config.upsDevices.filter((ups: { groups?: string[] }) => ups.groups && ups.groups.includes(groupId)); if (upsInGroup.length === 0) { logger.log('- None'); } else { diff --git a/ts/cli/service-handler.ts b/ts/cli/service-handler.ts index 398521b..2b04cff 100644 --- a/ts/cli/service-handler.ts +++ b/ts/cli/service-handler.ts @@ -201,12 +201,12 @@ export class ServiceHandler { logger.logBoxEnd(); } catch (error) { logger.logBoxLine('Error during update process:'); - logger.logBoxLine(`${error.message}`); + logger.logBoxLine(`${error instanceof Error ? error.message : String(error)}`); logger.logBoxEnd(); process.exit(1); } } catch (error) { - logger.error(`Update failed: ${error.message}`); + logger.error(`Update failed: ${error instanceof Error ? error.message : String(error)}`); process.exit(1); } } @@ -300,7 +300,7 @@ export class ServiceHandler { stdio: 'inherit', // Show output in the terminal }); } catch (error) { - console.error(`Uninstall failed: ${error.message}`); + console.error(`Uninstall failed: ${error instanceof Error ? error.message : String(error)}`); process.exit(1); } } diff --git a/ts/cli/ups-handler.ts b/ts/cli/ups-handler.ts index 371c4ca..c3b530d 100644 --- a/ts/cli/ups-handler.ts +++ b/ts/cli/ups-handler.ts @@ -2,6 +2,8 @@ import { execSync } from "node:child_process"; import { Nupst } from '../nupst.ts'; import { logger } from '../logger.ts'; import * as helpers from '../helpers/index.ts'; +import type { TUpsModel } from '../snmp/types.ts'; +import type { INupstConfig } from '../daemon.ts'; /** * Class for handling UPS-related CLI commands @@ -46,7 +48,7 @@ export class UpsHandler { rl.close(); } } catch (error) { - logger.error(`Add UPS error: ${error.message}`); + logger.error(`Add UPS error: ${error instanceof Error ? error.message : String(error)}`); } } @@ -105,7 +107,7 @@ export class UpsHandler { community: 'public', version: 1, timeout: 5000, - upsModel: 'cyberpower' + upsModel: 'cyberpower' as TUpsModel }, thresholds: { battery: 60, @@ -135,7 +137,7 @@ export class UpsHandler { config.upsDevices.push(newUps); // Save the configuration - await this.nupst.getDaemon().saveConfig(config); + await this.nupst.getDaemon().saveConfig(config as INupstConfig); this.displayUpsConfigSummary(newUps); @@ -177,7 +179,7 @@ export class UpsHandler { rl.close(); } } catch (error) { - logger.error(`Edit UPS error: ${error.message}`); + logger.error(`Edit UPS error: ${error instanceof Error ? error.message : String(error)}`); } } @@ -212,6 +214,10 @@ export class UpsHandler { // Convert old format to new format if needed if (!config.upsDevices) { // Initialize with the current config as the first UPS + if (!config.snmp || !config.thresholds) { + logger.error('Legacy configuration is missing required SNMP or threshold settings'); + return; + } config.upsDevices = [{ id: 'default', name: 'Default UPS', @@ -348,7 +354,7 @@ export class UpsHandler { // Check if service is running and restart it if needed await this.restartServiceIfRunning(); } catch (error) { - logger.error(`Failed to delete UPS: ${error.message}`); + logger.error(`Failed to delete UPS: ${error instanceof Error ? error.message : String(error)}`); } } @@ -378,6 +384,12 @@ export class UpsHandler { const boxWidth = 45; logger.logBoxTitle('UPS Devices', boxWidth); logger.logBoxLine('Legacy single-UPS configuration detected.'); + if (!config.snmp || !config.thresholds) { + logger.logBoxLine(''); + logger.logBoxLine('Error: Configuration missing SNMP or threshold settings'); + logger.logBoxEnd(); + return; + } logger.logBoxLine(''); logger.logBoxLine('Default UPS:'); logger.logBoxLine(` Host: ${config.snmp.host}:${config.snmp.port}`); @@ -416,7 +428,7 @@ export class UpsHandler { logger.logBoxEnd(); } catch (error) { - logger.error(`Failed to list UPS devices: ${error.message}`); + logger.error(`Failed to list UPS devices: ${error instanceof Error ? error.message : String(error)}`); } } @@ -465,7 +477,7 @@ export class UpsHandler { await this.testConnection(config); } } catch (error) { - logger.error(`Test failed: ${error.message}`); + logger.error(`Test failed: ${error instanceof Error ? error.message : String(error)}`); } } @@ -568,7 +580,7 @@ export class UpsHandler { } catch (error) { const errorBoxWidth = 45; logger.logBoxTitle(`Connection Failed: ${upsName}`, errorBoxWidth); - logger.logBoxLine(`Error: ${error.message}`); + logger.logBoxLine(`Error: ${error instanceof Error ? error.message : String(error)}`); logger.logBoxEnd(); logger.log("\nPlease check your settings and run 'nupst edit' to reconfigure this UPS."); } @@ -937,7 +949,7 @@ export class UpsHandler { const errorBoxWidth = 45; logger.log(''); logger.logBoxTitle('Connection Failed!', errorBoxWidth); - logger.logBoxLine(`Error: ${error.message}`); + logger.logBoxLine(`Error: ${error instanceof Error ? error.message : String(error)}`); logger.logBoxEnd(); logger.log('\nPlease check your settings and try again.'); } @@ -972,7 +984,7 @@ export class UpsHandler { logger.logBoxLine(' sudo systemctl restart nupst.service'); } } catch (error) { - logger.logBoxLine(`Error restarting service: ${error.message}`); + logger.logBoxLine(`Error restarting service: ${error instanceof Error ? error.message : String(error)}`); logger.logBoxLine('You may need to restart the service manually:'); logger.logBoxLine(' sudo systemctl restart nupst.service'); } diff --git a/ts/daemon.ts b/ts/daemon.ts index 2413d17..8a57933 100644 --- a/ts/daemon.ts +++ b/ts/daemon.ts @@ -179,11 +179,11 @@ export class NupstDaemon { return this.config; } catch (error) { - if (error.message && error.message.includes('No configuration found')) { + if (error instanceof Error && error.message && error.message.includes('No configuration found')) { throw error; // Re-throw the no configuration error } - this.logConfigError(`Error loading configuration: ${error.message}`); + this.logConfigError(`Error loading configuration: ${error instanceof Error ? error.message : String(error)}`); throw new Error('Failed to load configuration'); } } @@ -252,7 +252,7 @@ export class NupstDaemon { this.snmp.getNupst().logVersionInfo(false); // Don't check for updates immediately on startup // Check for updates in the background - this.snmp.getNupst().checkForUpdates().then(updateAvailable => { + this.snmp.getNupst().checkForUpdates().then((updateAvailable: boolean) => { if (updateAvailable) { const updateStatus = this.snmp.getNupst().getUpdateStatus(); const boxWidth = 45; @@ -272,7 +272,7 @@ export class NupstDaemon { await this.monitor(); } catch (error) { this.isRunning = false; - logger.error(`Daemon failed to start: ${error.message}`); + logger.error(`Daemon failed to start: ${error instanceof Error ? error.message : String(error)}`); process.exit(1); // Exit with error } } @@ -373,7 +373,7 @@ export class NupstDaemon { // Wait before next check await this.sleep(this.config.checkInterval); } catch (error) { - logger.error(`Error during UPS monitoring: ${error.message}`); + logger.error(`Error during UPS monitoring: ${error instanceof Error ? error.message : String(error)}`); await this.sleep(this.config.checkInterval); } } @@ -407,29 +407,31 @@ export class NupstDaemon { // Get the current status from the map const currentStatus = this.upsStatus.get(ups.id); - + // Update status with new values - const updatedStatus = { - ...currentStatus, + const updatedStatus: IUpsStatus = { + id: ups.id, + name: ups.name, powerStatus: status.powerStatus, batteryCapacity: status.batteryCapacity, batteryRuntime: status.batteryRuntime, - lastCheckTime: currentTime + lastCheckTime: currentTime, + lastStatusChange: currentStatus?.lastStatusChange || currentTime }; - + // Check if power status changed - if (currentStatus.powerStatus !== status.powerStatus) { + if (currentStatus && currentStatus.powerStatus !== status.powerStatus) { logger.logBoxTitle(`Power Status Change: ${ups.name}`, 50); logger.logBoxLine(`Status changed: ${currentStatus.powerStatus} → ${status.powerStatus}`); logger.logBoxEnd(); - + updatedStatus.lastStatusChange = currentTime; } - + // Update the status in the map this.upsStatus.set(ups.id, updatedStatus); } catch (error) { - logger.error(`Error checking UPS ${ups.name} (${ups.id}): ${error.message}`); + logger.error(`Error checking UPS ${ups.name} (${ups.id}): ${error instanceof Error ? error.message : String(error)}`); } } } @@ -631,7 +633,7 @@ export class NupstDaemon { }); logger.log(`Shutdown initiated: ${stdout}`); } catch (e) { - throw new Error(`Shutdown command not found: ${e.message}`); + throw new Error(`Shutdown command not found: ${e instanceof Error ? e.message : String(e)}`); } } @@ -725,14 +727,14 @@ export class NupstDaemon { return; } } catch (upsError) { - logger.error(`Error checking UPS ${ups.name} during shutdown: ${upsError.message}`); + logger.error(`Error checking UPS ${ups.name} during shutdown: ${upsError instanceof Error ? upsError.message : String(upsError)}`); } } // Wait before checking again await this.sleep(CHECK_INTERVAL); } catch (error) { - logger.error(`Error monitoring UPS during shutdown: ${error.message}`); + logger.error(`Error monitoring UPS during shutdown: ${error instanceof Error ? error.message : String(error)}`); await this.sleep(CHECK_INTERVAL); } } diff --git a/ts/nupst.ts b/ts/nupst.ts index 741eba2..59e0343 100644 --- a/ts/nupst.ts +++ b/ts/nupst.ts @@ -103,7 +103,7 @@ export class Nupst { return this.updateAvailable; } catch (error) { - logger.error(`Error checking for updates: ${error.message}`); + logger.error(`Error checking for updates: ${error instanceof Error ? error.message : String(error)}`); return false; } } diff --git a/ts/systemd.ts b/ts/systemd.ts index 574dd00..8cb0744 100644 --- a/ts/systemd.ts +++ b/ts/systemd.ts @@ -81,7 +81,7 @@ WantedBy=multi-user.target logger.logBoxLine('Service enabled to start on boot'); logger.logBoxEnd(); } catch (error) { - if (error.message === 'Configuration not found') { + if (error instanceof Error && error.message === 'Configuration not found') { // Just rethrow the error as the message has already been displayed throw error; } @@ -105,7 +105,7 @@ WantedBy=multi-user.target logger.logBoxLine('NUPST service started successfully'); logger.logBoxEnd(); } catch (error) { - if (error.message === 'Configuration not found') { + if (error instanceof Error && error.message === 'Configuration not found') { // Exit with error code since configuration is required process.exit(1); } @@ -157,7 +157,7 @@ WantedBy=multi-user.target await this.displayServiceStatus(); await this.displayAllUpsStatus(); } catch (error) { - logger.error(`Failed to get status: ${error.message}`); + logger.error(`Failed to get status: ${error instanceof Error ? error.message : String(error)}`); } } @@ -219,7 +219,7 @@ WantedBy=multi-user.target } catch (error) { const boxWidth = 45; logger.logBoxTitle('UPS Status', boxWidth); - logger.logBoxLine(`Failed to retrieve UPS status: ${error.message}`); + logger.logBoxLine(`Failed to retrieve UPS status: ${error instanceof Error ? error.message : String(error)}`); logger.logBoxEnd(); } } @@ -239,8 +239,8 @@ WantedBy=multi-user.target if (ups.groups && ups.groups.length > 0) { // Get group names if available const config = this.daemon.getConfig(); - const groupNames = ups.groups.map(groupId => { - const group = config.groups?.find(g => g.id === groupId); + const groupNames = ups.groups.map((groupId: string) => { + const group = config.groups?.find((g: { id: string }) => g.id === groupId); return group ? group.name : groupId; }); logger.logBoxLine(`Groups: ${groupNames.join(', ')}`); @@ -275,7 +275,7 @@ WantedBy=multi-user.target logger.logBoxEnd(); } catch (error) { logger.logBoxTitle(`UPS Status: ${ups.name}`, boxWidth); - logger.logBoxLine(`Failed to retrieve UPS status: ${error.message}`); + logger.logBoxLine(`Failed to retrieve UPS status: ${error instanceof Error ? error.message : String(error)}`); logger.logBoxEnd(); } }