refactor: replace 'any' types with proper TypeScript interfaces

Major type safety improvements throughout the codebase:

- Updated DEFAULT_CONFIG version to 4.2
- Replaced 'any' with proper types in systemd.ts:
  * displaySingleUpsStatus now uses IUpsConfig and NupstSnmp types
  * Fixed legacy config handling to use proper IUpsConfig format
  * Removed inline 'any' type annotations

- Replaced 'any' with proper types in daemon.ts:
  * emergencyUps now properly typed as { ups: IUpsConfig, status: ISnmpUpsStatus }
  * Exported IUpsStatus interface for reuse
  * Added ISnmpUpsStatus import to disambiguate from daemon's IUpsStatus

- Replaced 'any' with Record<string, unknown> in migration system:
  * Updated BaseMigration abstract class signatures
  * Updated MigrationRunner.run() signature
  * Updated migration-v4.0-to-v4.1.ts to use proper types
  * Migrations use Record<string, unknown> because they deal with
    unknown config schemas that are being upgraded

Benefits:
- TypeScript now catches type errors at compile time
- Would have caught the ups.thresholds bug earlier
- Better IDE autocomplete and type checking
- More maintainable and self-documenting code
This commit is contained in:
2025-10-20 12:27:02 +00:00
parent 316c66c344
commit b6b7b43161
5 changed files with 45 additions and 28 deletions

View File

@@ -28,18 +28,18 @@ export abstract class BaseMigration {
/**
* Check if this migration should run on the given config
*
* @param config - Raw configuration object to check
* @param config - Raw configuration object to check (unknown schema for migrations)
* @returns True if migration should run, false otherwise
*/
abstract shouldRun(config: any): Promise<boolean>;
abstract shouldRun(config: Record<string, unknown>): Promise<boolean>;
/**
* Perform the migration on the given config
*
* @param config - Raw configuration object to migrate
* @param config - Raw configuration object to migrate (unknown schema for migrations)
* @returns Migrated configuration object
*/
abstract migrate(config: any): Promise<any>;
abstract migrate(config: Record<string, unknown>): Promise<Record<string, unknown>>;
/**
* Get human-readable name for this migration

View File

@@ -32,7 +32,9 @@ export class MigrationRunner {
* @param config - Raw configuration object to migrate
* @returns Migrated configuration and whether migrations ran
*/
async run(config: any): Promise<{ config: any; migrated: boolean }> {
async run(
config: Record<string, unknown>,
): Promise<{ config: Record<string, unknown>; migrated: boolean }> {
let currentConfig = config;
let anyMigrationsRan = false;

View File

@@ -49,15 +49,15 @@ export class MigrationV4_0ToV4_1 extends BaseMigration {
readonly fromVersion = '4.0';
readonly toVersion = '4.1';
async shouldRun(config: any): Promise<boolean> {
async shouldRun(config: Record<string, unknown>): Promise<boolean> {
// Run if config is version 4.0
if (config.version === '4.0') {
return true;
}
// Also run if config has upsDevices with thresholds at UPS level (v4.0 format)
if (config.upsDevices && config.upsDevices.length > 0) {
const firstDevice = config.upsDevices[0];
if (Array.isArray(config.upsDevices) && config.upsDevices.length > 0) {
const firstDevice = config.upsDevices[0] as Record<string, unknown>;
// v4.0 has thresholds at UPS level, v4.1 has them in actions
return firstDevice.thresholds !== undefined;
}
@@ -65,14 +65,15 @@ export class MigrationV4_0ToV4_1 extends BaseMigration {
return false;
}
async migrate(config: any): Promise<any> {
async migrate(config: Record<string, unknown>): Promise<Record<string, unknown>> {
logger.info(`${this.getName()}: Migrating v4.0 config to v4.1 format...`);
logger.dim(` - Moving thresholds from UPS level to action level`);
logger.dim(` - Creating default shutdown actions from existing thresholds`);
// Migrate UPS devices
const migratedDevices = (config.upsDevices || []).map((device: any) => {
const migrated: any = {
const devices = (config.upsDevices as Array<Record<string, unknown>>) || [];
const migratedDevices = devices.map((device) => {
const migrated: Record<string, unknown> = {
id: device.id,
name: device.name,
snmp: device.snmp,
@@ -80,20 +81,21 @@ export class MigrationV4_0ToV4_1 extends BaseMigration {
};
// If device has thresholds at UPS level, convert to shutdown action
if (device.thresholds) {
const deviceThresholds = device.thresholds as { battery: number; runtime: number } | undefined;
if (deviceThresholds) {
migrated.actions = [
{
type: 'shutdown',
thresholds: {
battery: device.thresholds.battery,
runtime: device.thresholds.runtime,
battery: deviceThresholds.battery,
runtime: deviceThresholds.runtime,
},
triggerMode: 'onlyThresholds', // Preserve old behavior (only on threshold violation)
shutdownDelay: 5, // Default delay
},
];
logger.dim(
`${device.name}: Created shutdown action (battery: ${device.thresholds.battery}%, runtime: ${device.thresholds.runtime}min)`,
`${device.name}: Created shutdown action (battery: ${deviceThresholds.battery}%, runtime: ${deviceThresholds.runtime}min)`,
);
} else {
// No thresholds, just add empty actions array
@@ -104,7 +106,8 @@ export class MigrationV4_0ToV4_1 extends BaseMigration {
});
// Add actions to groups
const migratedGroups = (config.groups || []).map((group: any) => ({
const groups = (config.groups as Array<Record<string, unknown>>) || [];
const migratedGroups = groups.map((group) => ({
...group,
actions: group.actions || [],
}));