From afd80449b35a10de40cc5393ccb6daa515f26e9b Mon Sep 17 00:00:00 2001 From: Koen Kanters Date: Sun, 8 Sep 2024 14:26:18 +0200 Subject: [PATCH] fix(ignore): Migrate to eslint 9 (#23800) * Eslint 9 * Automatic changes * Manual changes * Process feedback * u --- .eslintignore | 2 - .eslintrc.js | 63 -- .github/workflows/update_deps.yml | 3 +- .prettierrc | 20 +- eslint.config.mjs | 32 + lib/controller.ts | 34 +- lib/eventBus.ts | 1 - lib/extension/availability.ts | 2 + lib/extension/bind.ts | 2 + lib/extension/bridge.ts | 47 +- lib/extension/configure.ts | 11 +- lib/extension/externalExtension.ts | 7 +- lib/extension/frontend.ts | 16 +- lib/extension/groups.ts | 2 + lib/extension/homeassistant.ts | 31 +- lib/extension/legacy/bridgeLegacy.ts | 11 +- lib/extension/legacy/deviceGroupMembership.ts | 2 + lib/extension/legacy/report.ts | 4 +- lib/extension/legacy/softReset.ts | 1 + lib/extension/networkMap.ts | 4 +- lib/extension/otaUpdate.ts | 21 +- lib/extension/publish.ts | 14 +- lib/extension/receive.ts | 2 + lib/model/device.ts | 4 +- lib/mqtt.ts | 7 +- lib/state.ts | 3 +- lib/types/types.d.ts | 11 +- lib/types/zigbee2mqtt-frontend.d.ts | 4 +- lib/util/logger.ts | 7 +- lib/util/settings.ts | 90 +-- lib/util/utils.ts | 46 +- lib/util/yaml.ts | 3 +- lib/zigbee.ts | 6 +- package-lock.json | 660 ++++++++---------- package.json | 15 +- test/availability.test.js | 21 +- 36 files changed, 582 insertions(+), 627 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.js create mode 100644 eslint.config.mjs diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 7406675a..00000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/* -test/ \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 5934a6eb..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,63 +0,0 @@ -module.exports = { - env: { - 'jest/globals': true, - es6: true, - node: true, - }, - extends: ['eslint:recommended', 'plugin:jest/recommended', 'plugin:jest/style', 'prettier'], - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module', - }, - rules: { - 'require-jsdoc': 'off', - 'no-prototype-builtins': 'off', - '@typescript-eslint/no-floating-promises': 'error', - }, - plugins: ['jest', 'perfectionist'], - overrides: [ - { - files: ['*.ts'], - parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint'], - extends: ['plugin:@typescript-eslint/recommended'], - parserOptions: { - project: './tsconfig.json', - }, - rules: { - '@typescript-eslint/await-thenable': 'error', - '@typescript-eslint/ban-ts-comment': 'off', - '@typescript-eslint/explicit-function-return-type': 'error', - '@typescript-eslint/no-empty-function': 'off', - '@typescript-eslint/no-explicit-any': 'error', - '@typescript-eslint/no-floating-promises': 'error', - '@typescript-eslint/no-unused-vars': 'error', - 'no-return-await': 'error', - 'perfectionist/sort-imports': [ - 'error', - { - groups: [ - 'type', - ['builtin', 'external'], - 'internal-type', - 'internal', - ['parent-type', 'sibling-type', 'index-type'], - ['parent', 'sibling', 'index'], - 'object', - 'unknown', - ], - customGroups: { - value: {}, - type: {}, - }, - newlinesBetween: 'always', - internalPattern: ['~/**'], - type: 'natural', - order: 'asc', - ignoreCase: false, - }, - ], - }, - }, - ], -}; diff --git a/.github/workflows/update_deps.yml b/.github/workflows/update_deps.yml index 33eb759e..ef98dd4a 100644 --- a/.github/workflows/update_deps.yml +++ b/.github/workflows/update_deps.yml @@ -23,8 +23,7 @@ jobs: node-version: 20 cache: npm # connect-gzip-static@4.0.0 requires Node 20 >= - # eslint: https://github.com/typescript-eslint/typescript-eslint/issues/8211 - - run: npx npm-check-updates -u -x connect-gzip-static -x eslint + - run: npx npm-check-updates -u -x connect-gzip-static - run: rm -f package-lock.json - run: npm install - uses: peter-evans/create-pull-request@v7 diff --git a/.prettierrc b/.prettierrc index 0267e1af..0c99705e 100644 --- a/.prettierrc +++ b/.prettierrc @@ -5,5 +5,23 @@ "printWidth": 150, "bracketSpacing": false, "endOfLine": "lf", - "tabWidth": 4 + "tabWidth": 4, + "importOrder": [ + "", + "^(node:)", + "", + "", + "", + "^[.]", + "", + "", + "", + "", + "", + "^zigbee", + "", + "^[.]" + ], + "importOrderParserPlugins": ["typescript", "decorators"], + "plugins": ["@ianvs/prettier-plugin-sort-imports"] } diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..1e26a89f --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,32 @@ +// @ts-check + +import eslint from '@eslint/js'; +import eslintConfigPrettier from 'eslint-config-prettier'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + languageOptions: { + parserOptions: { + project: true, + }, + }, + rules: { + '@typescript-eslint/await-thenable': 'error', + '@typescript-eslint/ban-ts-comment': 'error', + '@typescript-eslint/explicit-function-return-type': 'error', + '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/no-unused-vars': 'error', + 'array-bracket-spacing': ['error', 'never'], + 'no-return-await': 'error', + 'object-curly-spacing': ['error', 'never'], + '@typescript-eslint/no-floating-promises': 'error', + }, + }, + { + ignores: ['dist/', '**/*.js', '**/*.mjs'], + }, + eslintConfigPrettier, +); diff --git a/lib/controller.ts b/lib/controller.ts index cfa293f7..c46482e3 100644 --- a/lib/controller.ts +++ b/lib/controller.ts @@ -1,6 +1,10 @@ +import type * as SdNotify from 'sd-notify'; + import assert from 'assert'; + import bind from 'bind-decorator'; import stringify from 'json-stable-stringify-without-jsonify'; + import {setLogger as zhSetLogger} from 'zigbee-herdsman'; import {setLogger as zhcSetLogger} from 'zigbee-herdsman-converters'; @@ -31,6 +35,8 @@ import * as settings from './util/settings'; import utils from './util/utils'; import Zigbee from './zigbee'; +type SdNotifyType = typeof SdNotify; + const AllExtensions = [ ExtensionPublish, ExtensionReceive, @@ -63,15 +69,6 @@ type ExtensionArgs = [ addExtension: (extension: Extension) => Promise, ]; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -let sdNotify: any = null; -try { - // eslint-disable-next-line @typescript-eslint/no-require-imports - sdNotify = process.env.NOTIFY_SOCKET ? require('sd-notify') : null; -} catch { - // sd-notify is optional -} - export class Controller { private eventBus: EventBus; private zigbee: Zigbee; @@ -81,6 +78,7 @@ export class Controller { private exitCallback: (code: number, restart: boolean) => Promise; private extensions: Extension[]; private extensionArgs: ExtensionArgs; + private sdNotify: SdNotifyType | undefined; constructor(restartCallback: () => Promise, exitCallback: (code: number, restart: boolean) => Promise) { logger.init(); @@ -149,6 +147,14 @@ export class Controller { const info = await utils.getZigbee2MQTTVersion(); logger.info(`Starting Zigbee2MQTT version ${info.version} (commit #${info.commitHash})`); + try { + this.sdNotify = process.env.NOTIFY_SOCKET ? await import('sd-notify') : undefined; + logger.debug('sd-notify loaded'); + } catch { + // istanbul ignore next + logger.debug('sd-notify is not installed'); + } + // Start zigbee let startResult; try { @@ -224,11 +230,11 @@ export class Controller { logger.info(`Zigbee2MQTT started!`); - const watchdogInterval = sdNotify?.watchdogInterval() || 0; + const watchdogInterval = this.sdNotify?.watchdogInterval() || 0; if (watchdogInterval > 0) { - sdNotify.startWatchdogMode(Math.floor(watchdogInterval / 2)); + this.sdNotify?.startWatchdogMode(Math.floor(watchdogInterval / 2)); } - sdNotify?.ready(); + this.sdNotify?.ready(); } @bind async enableDisableExtension(enable: boolean, name: string): Promise { @@ -253,7 +259,7 @@ export class Controller { } async stop(restart = false): Promise { - sdNotify?.stopping(); + this.sdNotify?.stopping(process.pid); // Call extensions await this.callExtensions('stop', this.extensions); @@ -272,7 +278,7 @@ export class Controller { code = 1; } - sdNotify?.stopWatchdogMode(); + this.sdNotify?.stopWatchdogMode(); return this.exit(code, restart); } diff --git a/lib/eventBus.ts b/lib/eventBus.ts index 27d7ede8..027106c2 100644 --- a/lib/eventBus.ts +++ b/lib/eventBus.ts @@ -2,7 +2,6 @@ import events from 'events'; import logger from './util/logger'; -// eslint-disable-next-line type ListenerKey = object; interface EventBusMap { diff --git a/lib/extension/availability.ts b/lib/extension/availability.ts index 04a1b268..f13552bd 100644 --- a/lib/extension/availability.ts +++ b/lib/extension/availability.ts @@ -1,6 +1,8 @@ import assert from 'assert'; + import bind from 'bind-decorator'; import debounce from 'debounce'; + import * as zhc from 'zigbee-herdsman-converters'; import logger from '../util/logger'; diff --git a/lib/extension/bind.ts b/lib/extension/bind.ts index 5ae3f4b1..1ddbaeef 100755 --- a/lib/extension/bind.ts +++ b/lib/extension/bind.ts @@ -1,7 +1,9 @@ import assert from 'assert'; + import bind from 'bind-decorator'; import debounce from 'debounce'; import stringify from 'json-stable-stringify-without-jsonify'; + import {Zcl} from 'zigbee-herdsman'; import {ClusterName} from 'zigbee-herdsman/dist/zspec/zcl/definition/tstype'; diff --git a/lib/extension/bridge.ts b/lib/extension/bridge.ts index 98cdd5fd..cad29657 100644 --- a/lib/extension/bridge.ts +++ b/lib/extension/bridge.ts @@ -1,14 +1,15 @@ -/* eslint-disable camelcase */ -import bind from 'bind-decorator'; import fs from 'fs'; + +import bind from 'bind-decorator'; import stringify from 'json-stable-stringify-without-jsonify'; import JSZip from 'jszip'; import objectAssignDeep from 'object-assign-deep'; import winston from 'winston'; import Transport from 'winston-transport'; -import {Clusters} from 'zigbee-herdsman/dist/zspec/zcl/definition/cluster'; -import {CustomClusters, ClusterDefinition, ClusterName} from 'zigbee-herdsman/dist/zspec/zcl/definition/tstype'; + import * as zhc from 'zigbee-herdsman-converters'; +import {Clusters} from 'zigbee-herdsman/dist/zspec/zcl/definition/cluster'; +import {ClusterDefinition, ClusterName, CustomClusters} from 'zigbee-herdsman/dist/zspec/zcl/definition/tstype'; import Device from '../model/device'; import Group from '../model/group'; @@ -275,12 +276,12 @@ export default class Bridge extends Extension { } @bind async groupAdd(message: string | KeyValue): Promise { - if (typeof message === 'object' && !message.hasOwnProperty('friendly_name')) { + if (typeof message === 'object' && message.friendly_name === undefined) { throw new Error(`Invalid payload`); } const friendlyName = typeof message === 'object' ? message.friendly_name : message; - const ID = typeof message === 'object' && message.hasOwnProperty('id') ? message.id : null; + const ID = typeof message === 'object' && message.id !== undefined ? message.id : null; const group = settings.addGroup(friendlyName, ID); this.zigbee.createGroup(group.ID); await this.publishGroups(); @@ -316,7 +317,7 @@ export default class Bridge extends Extension { } @bind async installCodeAdd(message: KeyValue | string): Promise { - if (typeof message === 'object' && !message.hasOwnProperty('value')) { + if (typeof message === 'object' && message.value === undefined) { throw new Error('Invalid payload'); } @@ -327,7 +328,7 @@ export default class Bridge extends Extension { } @bind async permitJoin(message: KeyValue | string): Promise { - if (typeof message === 'object' && !message.hasOwnProperty('value')) { + if (typeof message === 'object' && message.value === undefined) { throw new Error('Invalid payload'); } @@ -426,7 +427,7 @@ export default class Bridge extends Extension { } @bind async touchlinkIdentify(message: KeyValue | string): Promise { - if (typeof message !== 'object' || !message.hasOwnProperty('ieee_address') || !message.hasOwnProperty('channel')) { + if (typeof message !== 'object' || message.ieee_address === undefined || message.channel === undefined) { throw new Error('Invalid payload'); } @@ -438,7 +439,7 @@ export default class Bridge extends Extension { @bind async touchlinkFactoryReset(message: KeyValue | string): Promise { let result = false; const payload: {ieee_address?: string; channel?: number} = {}; - if (typeof message === 'object' && message.hasOwnProperty('ieee_address') && message.hasOwnProperty('channel')) { + if (typeof message === 'object' && message.ieee_address !== undefined && message.channel !== undefined) { logger.info(`Start Touchlink factory reset of '${message.ieee_address}' on channel ${message.channel}`); result = await this.zigbee.touchlinkFactoryReset(message.ieee_address, message.channel); payload.ieee_address = message.ieee_address; @@ -473,7 +474,7 @@ export default class Bridge extends Extension { getValue(message: KeyValue | string): string | boolean | number { if (typeof message === 'object') { - if (!message.hasOwnProperty('value')) { + if (message.value === undefined) { throw new Error('No value given'); } @@ -484,7 +485,7 @@ export default class Bridge extends Extension { } async changeEntityOptions(entityType: 'device' | 'group', message: KeyValue | string): Promise { - if (typeof message !== 'object' || !message.hasOwnProperty('id') || !message.hasOwnProperty('options')) { + if (typeof message !== 'object' || message.id === undefined || message.options === undefined) { throw new Error(`Invalid payload`); } @@ -514,12 +515,12 @@ export default class Bridge extends Extension { @bind async deviceConfigureReporting(message: string | KeyValue): Promise { if ( typeof message !== 'object' || - !message.hasOwnProperty('id') || - !message.hasOwnProperty('cluster') || - !message.hasOwnProperty('maximum_report_interval') || - !message.hasOwnProperty('minimum_report_interval') || - !message.hasOwnProperty('reportable_change') || - !message.hasOwnProperty('attribute') + message.id === undefined || + message.cluster === undefined || + message.maximum_report_interval === undefined || + message.minimum_report_interval === undefined || + message.reportable_change === undefined || + message.attribute === undefined ) { throw new Error(`Invalid payload`); } @@ -565,7 +566,7 @@ export default class Bridge extends Extension { } @bind async deviceInterview(message: string | KeyValue): Promise { - if (typeof message !== 'object' || !message.hasOwnProperty('id')) { + if (typeof message !== 'object' || message.id === undefined) { throw new Error(`Invalid payload`); } @@ -588,7 +589,7 @@ export default class Bridge extends Extension { } @bind async deviceGenerateExternalDefinition(message: string | KeyValue): Promise { - if (typeof message !== 'object' || !message.hasOwnProperty('id')) { + if (typeof message !== 'object' || message.id === undefined) { throw new Error(`Invalid payload`); } @@ -606,7 +607,7 @@ export default class Bridge extends Extension { async renameEntity(entityType: 'group' | 'device', message: string | KeyValue): Promise { const deviceAndHasLast = entityType === 'device' && typeof message === 'object' && message.last === true; - if (typeof message !== 'object' || (!message.hasOwnProperty('from') && !deviceAndHasLast) || !message.hasOwnProperty('to')) { + if (typeof message !== 'object' || (message.from === undefined && !deviceAndHasLast) || message.to === undefined) { throw new Error(`Invalid payload`); } @@ -616,7 +617,7 @@ export default class Bridge extends Extension { const from = deviceAndHasLast ? this.lastJoinedDeviceIeeeAddr : message.from; const to = message.to; - const homeAssisantRename = message.hasOwnProperty('homeassistant_rename') ? message.homeassistant_rename : false; + const homeAssisantRename = message.homeassistant_rename !== undefined ? message.homeassistant_rename : false; const entity = this.getEntity(entityType, from); const oldFriendlyName = entity.options.friendly_name; @@ -745,7 +746,7 @@ export default class Bridge extends Extension { ieee_address: this.zigbee.firstCoordinatorEndpoint().getDevice().ieeeAddr, ...this.coordinatorVersion, }, - network: utils.toSnakeCase(await this.zigbee.getNetworkParameters()), + network: utils.toSnakeCaseObject(await this.zigbee.getNetworkParameters()), log_level: logger.getLevel(), permit_join: this.zigbee.getPermitJoin(), permit_join_timeout: this.zigbee.getPermitJoinTimeout(), diff --git a/lib/extension/configure.ts b/lib/extension/configure.ts index 352badc9..30689063 100644 --- a/lib/extension/configure.ts +++ b/lib/extension/configure.ts @@ -1,5 +1,6 @@ import bind from 'bind-decorator'; import stringify from 'json-stable-stringify-without-jsonify'; + import * as zhc from 'zigbee-herdsman-converters'; import Device from '../model/device'; @@ -19,7 +20,7 @@ export default class Configure extends Extension { @bind private async onReconfigure(data: eventdata.Reconfigure): Promise { // Disabling reporting unbinds some cluster which could be bound by configure, re-setup. - if (data.device.zh.meta?.hasOwnProperty('configured')) { + if (data.device.zh.meta?.configured !== undefined) { delete data.device.zh.meta.configured; data.device.zh.save(); } @@ -43,7 +44,7 @@ export default class Configure extends Extension { await this.configure(device, 'mqtt_message', true); } else if (data.topic === this.topic) { const message = utils.parseJSON(data.message, data.message); - const ID = typeof message === 'object' && message.hasOwnProperty('id') ? message.id : message; + const ID = typeof message === 'object' && message.id !== undefined ? message.id : message; let error: string | undefined; const device = this.zigbee.resolveEntity(ID); @@ -76,7 +77,7 @@ export default class Configure extends Extension { }); this.eventBus.onDeviceJoined(this, async (data) => { - if (data.device.zh.meta.hasOwnProperty('configured')) { + if (data.device.zh.meta.configured !== undefined) { delete data.device.zh.meta.configured; data.device.zh.save(); } @@ -104,7 +105,7 @@ export default class Configure extends Extension { return; } - if (device.zh.meta?.hasOwnProperty('configured')) { + if (device.zh.meta?.configured !== undefined) { return; } @@ -120,7 +121,7 @@ export default class Configure extends Extension { this.configuring.add(device.ieeeAddr); - if (!this.attempts.hasOwnProperty(device.ieeeAddr)) { + if (this.attempts[device.ieeeAddr] === undefined) { this.attempts[device.ieeeAddr] = 0; } diff --git a/lib/extension/externalExtension.ts b/lib/extension/externalExtension.ts index 9d406bba..6df65685 100644 --- a/lib/extension/externalExtension.ts +++ b/lib/extension/externalExtension.ts @@ -1,8 +1,9 @@ -import bind from 'bind-decorator'; import fs from 'fs'; -import stringify from 'json-stable-stringify-without-jsonify'; import path from 'path'; +import bind from 'bind-decorator'; +import stringify from 'json-stable-stringify-without-jsonify'; + import * as settings from '../util/settings'; import utils from '../util/utils'; import data from './../util/data'; @@ -93,7 +94,7 @@ export default class ExternalExtension extends Extension { @bind private async loadExtension(ConstructorClass: typeof Extension): Promise { await this.enableDisableExtension(false, ConstructorClass.name); - // @ts-ignore + // @ts-expect-error `ConstructorClass` is the interface, not the actual passed class await this.addExtension(new ConstructorClass(this.zigbee, this.mqtt, this.state, this.publishEntityState, this.eventBus, settings, logger)); } diff --git a/lib/extension/frontend.ts b/lib/extension/frontend.ts index 985ec168..0f94a166 100644 --- a/lib/extension/frontend.ts +++ b/lib/extension/frontend.ts @@ -1,14 +1,16 @@ import assert from 'assert'; -import bind from 'bind-decorator'; -import gzipStatic, {RequestHandler} from 'connect-gzip-static'; -import finalhandler from 'finalhandler'; import fs from 'fs'; import http from 'http'; import https from 'https'; -import stringify from 'json-stable-stringify-without-jsonify'; import net from 'net'; import url from 'url'; + +import bind from 'bind-decorator'; +import gzipStatic, {RequestHandler} from 'connect-gzip-static'; +import finalhandler from 'finalhandler'; +import stringify from 'json-stable-stringify-without-jsonify'; import WebSocket from 'ws'; + import frontend from 'zigbee2mqtt-frontend'; import logger from '../util/logger'; @@ -78,8 +80,7 @@ export default class Frontend extends Extension { /* istanbul ignore next */ const options = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - setHeaders: (res: any, path: string): void => { + setHeaders: (res: {setHeader(key: string, value: string): void}, path: string): void => { if (path.endsWith('index.html')) { res.setHeader('Cache-Control', 'no-store'); } @@ -117,8 +118,7 @@ export default class Frontend extends Extension { } @bind private onRequest(request: http.IncomingMessage, response: http.ServerResponse): void { - // @ts-ignore - this.fileServer(request, response, finalhandler(request, response)); + this.fileServer?.(request, response, finalhandler(request, response)); } private authenticate(request: http.IncomingMessage, cb: (authenticate: boolean) => void): void { diff --git a/lib/extension/groups.ts b/lib/extension/groups.ts index 04de3775..4e834c2e 100644 --- a/lib/extension/groups.ts +++ b/lib/extension/groups.ts @@ -1,7 +1,9 @@ import assert from 'assert'; + import bind from 'bind-decorator'; import equals from 'fast-deep-equal/es6'; import stringify from 'json-stable-stringify-without-jsonify'; + import * as zhc from 'zigbee-herdsman-converters'; import Device from '../model/device'; diff --git a/lib/extension/homeassistant.ts b/lib/extension/homeassistant.ts index b9dc7ea7..962a8cae 100644 --- a/lib/extension/homeassistant.ts +++ b/lib/extension/homeassistant.ts @@ -1,11 +1,13 @@ import assert from 'assert'; + import bind from 'bind-decorator'; import stringify from 'json-stable-stringify-without-jsonify'; + import * as zhc from 'zigbee-herdsman-converters'; import logger from '../util/logger'; import * as settings from '../util/settings'; -import utils, {isNumericExpose, isBinaryExpose, isEnumExpose, assertBinaryExpose, assertNumericExpose, assertEnumExpose} from '../util/utils'; +import utils, {assertBinaryExpose, assertEnumExpose, assertNumericExpose, isBinaryExpose, isEnumExpose, isNumericExpose} from '../util/utils'; import Extension from './extension'; interface MockProperty { @@ -13,7 +15,6 @@ interface MockProperty { value: KeyValue | string | null; } -// eslint-disable-next-line camelcase interface DiscoveryEntry { mockProperties: MockProperty[]; type: string; @@ -379,7 +380,6 @@ class Bridge { homeassistant?: KeyValue; }; - /* eslint-disable brace-style */ get ID(): string { return this.coordinatorIeeeAddress; } @@ -417,7 +417,6 @@ class Bridge { isGroup(): this is Group { return false; } - /* eslint-enable brace-style */ } /** @@ -1528,7 +1527,7 @@ export default class HomeAssistant extends Extension { }); }); - if (isDevice && entity.options.hasOwnProperty('legacy') && !entity.options.legacy) { + if (isDevice && entity.options.legacy !== undefined && !entity.options.legacy) { configs = configs.filter((c) => c !== SENSOR_CLICK); } @@ -1541,7 +1540,7 @@ export default class HomeAssistant extends Extension { if (entity.options.homeassistant) { const s = entity.options.homeassistant; - configs = configs.filter((config) => !s.hasOwnProperty(config.object_id) || s[config.object_id] != null); + configs = configs.filter((config) => s[config.object_id] === undefined || s[config.object_id] != null); configs.forEach((config) => { const configOverride = s[config.object_id]; if (configOverride) { @@ -1563,7 +1562,7 @@ export default class HomeAssistant extends Extension { return; } else if ( isDevice && - (!entity.definition || entity.zh.interviewing || (entity.options.hasOwnProperty('homeassistant') && !entity.options.homeassistant)) + (!entity.definition || entity.zh.interviewing || (entity.options.homeassistant !== undefined && !entity.options.homeassistant)) ) { return; } @@ -1582,11 +1581,11 @@ export default class HomeAssistant extends Extension { delete payload.state_topic_postfix; } - if (!payload.hasOwnProperty('state_topic') || payload.state_topic) { + if (payload.state_topic === undefined || payload.state_topic) { payload.state_topic = stateTopic; } else { /* istanbul ignore else */ - if (payload.hasOwnProperty('state_topic')) { + if (payload.state_topic !== undefined) { delete payload.state_topic; } } @@ -1626,7 +1625,7 @@ export default class HomeAssistant extends Extension { payload.origin = this.discoveryOrigin; // Availability payload (can be disabled by setting `payload.availability = false`). - if (!payload.hasOwnProperty('availability') || payload.availability) { + if (payload.availability === undefined || payload.availability) { payload.availability = [{topic: `${settings.get().mqtt.base_topic}/bridge/state`}]; if (isDevice || isGroup) { @@ -1941,17 +1940,17 @@ export default class HomeAssistant extends Extension { override adjustMessageBeforePublish(entity: Device | Group | Bridge, message: KeyValue): void { this.getDiscovered(entity).mockProperties.forEach((mockProperty) => { - if (!message.hasOwnProperty(mockProperty.property)) { + if (message[mockProperty.property] === undefined) { message[mockProperty.property] = mockProperty.value; } }); // Copy hue -> h, saturation -> s to make homeassistant happy - if (message.hasOwnProperty('color')) { - if (message.color.hasOwnProperty('hue')) { + if (message.color !== undefined) { + if (message.color.hue !== undefined) { message.color.h = message.color.hue; } - if (message.color.hasOwnProperty('saturation')) { + if (message.color.saturation !== undefined) { message.color.s = message.color.saturation; } } @@ -1977,8 +1976,8 @@ export default class HomeAssistant extends Extension { private async publishDeviceTriggerDiscover(device: Device, key: string, value: string, force = false): Promise { const haConfig = device.options.homeassistant; if ( - device.options.hasOwnProperty('homeassistant') && - (haConfig == null || (haConfig.hasOwnProperty('device_automation') && typeof haConfig === 'object' && haConfig.device_automation == null)) + device.options.homeassistant !== undefined && + (haConfig == null || (haConfig.device_automation !== undefined && typeof haConfig === 'object' && haConfig.device_automation == null)) ) { return; } diff --git a/lib/extension/legacy/bridgeLegacy.ts b/lib/extension/legacy/bridgeLegacy.ts index 2eb4fd23..4bf05934 100644 --- a/lib/extension/legacy/bridgeLegacy.ts +++ b/lib/extension/legacy/bridgeLegacy.ts @@ -1,4 +1,5 @@ import assert from 'assert'; + import bind from 'bind-decorator'; import stringify from 'json-stable-stringify-without-jsonify'; @@ -67,7 +68,7 @@ export default class BridgeLegacy extends Extension { return; } - if (!json.hasOwnProperty('friendly_name') || !json.hasOwnProperty('options')) { + if (json.friendly_name === undefined || json.options === undefined) { logger.error('Invalid JSON message, should contain "friendly_name" and "options"'); return; } @@ -227,11 +228,11 @@ export default class BridgeLegacy extends Extension { try { // json payload with id and friendly_name const json = JSON.parse(message); - if (json.hasOwnProperty('id')) { + if (json.id !== undefined) { id = json.id; name = `group_${id}`; } - if (json.hasOwnProperty('friendly_name')) { + if (json.friendly_name !== undefined) { name = json.friendly_name; } } catch { @@ -321,7 +322,7 @@ export default class BridgeLegacy extends Extension { await cleanup(); } catch (error) { logger.error(`Failed to ${lookup[action][2]} ${entity.name} (${error})`); - // eslint-disable-next-line + logger.error(`See https://www.zigbee2mqtt.io/guide/usage/mqtt_topics_and_messages.html#zigbee2mqtt-bridge-request for more info`); await this.mqtt.publish('bridge/log', stringify({type: `device_${lookup[action][0]}_failed`, message})); @@ -342,7 +343,7 @@ export default class BridgeLegacy extends Extension { const option = match[1]; - if (!this.supportedOptions.hasOwnProperty(option)) { + if (this.supportedOptions[option] === undefined) { return; } diff --git a/lib/extension/legacy/deviceGroupMembership.ts b/lib/extension/legacy/deviceGroupMembership.ts index 8024b3e6..f3b618e7 100644 --- a/lib/extension/legacy/deviceGroupMembership.ts +++ b/lib/extension/legacy/deviceGroupMembership.ts @@ -1,5 +1,7 @@ /* istanbul ignore file */ + import assert from 'assert'; + import bind from 'bind-decorator'; import Device from '../../model/device'; diff --git a/lib/extension/legacy/report.ts b/lib/extension/legacy/report.ts index f7b0abc4..5e013280 100644 --- a/lib/extension/legacy/report.ts +++ b/lib/extension/legacy/report.ts @@ -172,11 +172,11 @@ export default class Report extends Extension { // Gledopto devices don't support reporting. if (devicesNotSupportingReporting.includes(device.definition) || device.definition.vendor === 'Gledopto') return false; - if (this.enabled && device.zh.meta.hasOwnProperty('reporting') && device.zh.meta.reporting === reportKey) { + if (this.enabled && device.zh.meta.reporting !== undefined && device.zh.meta.reporting === reportKey) { return false; } - if (!this.enabled && !device.zh.meta.hasOwnProperty('reporting')) { + if (!this.enabled && device.zh.meta.reporting === undefined) { return false; } diff --git a/lib/extension/legacy/softReset.ts b/lib/extension/legacy/softReset.ts index 7df9d0e2..c58ce3dd 100644 --- a/lib/extension/legacy/softReset.ts +++ b/lib/extension/legacy/softReset.ts @@ -1,4 +1,5 @@ /* istanbul ignore file */ + import logger from '../../util/logger'; // DEPRECATED import * as settings from '../../util/settings'; diff --git a/lib/extension/networkMap.ts b/lib/extension/networkMap.ts index 414b8425..c7012ff8 100644 --- a/lib/extension/networkMap.ts +++ b/lib/extension/networkMap.ts @@ -55,7 +55,7 @@ export default class NetworkMap extends Extension { @bind async onMQTTMessage(data: eventdata.MQTTMessage): Promise { /* istanbul ignore else */ if (this.legacyApi) { - if ((data.topic === this.legacyTopic || data.topic === this.legacyTopicRoutes) && this.supportedFormats.hasOwnProperty(data.message)) { + if ((data.topic === this.legacyTopic || data.topic === this.legacyTopicRoutes) && this.supportedFormats[data.message] !== undefined) { const includeRoutes = data.topic === this.legacyTopicRoutes; const topology = await this.networkScan(includeRoutes); let converted = this.supportedFormats[data.message](topology); @@ -68,7 +68,7 @@ export default class NetworkMap extends Extension { const message = utils.parseJSON(data.message, data.message); try { const type = typeof message === 'object' ? message.type : message; - if (!this.supportedFormats.hasOwnProperty(type)) { + if (this.supportedFormats[type] === undefined) { throw new Error(`Type '${type}' not supported, allowed are: ${Object.keys(this.supportedFormats)}`); } diff --git a/lib/extension/otaUpdate.ts b/lib/extension/otaUpdate.ts index a2d408d2..85587ab4 100644 --- a/lib/extension/otaUpdate.ts +++ b/lib/extension/otaUpdate.ts @@ -1,8 +1,10 @@ import assert from 'assert'; +import path from 'path'; + import bind from 'bind-decorator'; import stringify from 'json-stable-stringify-without-jsonify'; -import path from 'path'; import * as URI from 'uri-js'; + import {Zcl} from 'zigbee-herdsman'; import * as zhc from 'zigbee-herdsman-converters'; @@ -27,7 +29,7 @@ function isValidUrl(url: string): boolean { type UpdateState = 'updating' | 'idle' | 'available'; interface UpdatePayload { update_available?: boolean; - // eslint-disable-next-line camelcase + update: { progress?: number; remaining?: number; @@ -93,9 +95,10 @@ export default class OTAUpdate extends Extension { // with only 10 - 60 seconds inbetween. It doesn't make sense to check for a new update // each time, so this interval can be set by the user. The default is 1,440 minutes (one day). const updateCheckInterval = settings.get().ota.update_check_interval * 1000 * 60; - const check = this.lastChecked.hasOwnProperty(data.device.ieeeAddr) - ? Date.now() - this.lastChecked[data.device.ieeeAddr] > updateCheckInterval - : true; + const check = + this.lastChecked[data.device.ieeeAddr] !== undefined + ? Date.now() - this.lastChecked[data.device.ieeeAddr] > updateCheckInterval + : true; if (!check) return; this.lastChecked[data.device.ieeeAddr] = Date.now(); @@ -185,10 +188,10 @@ export default class OTAUpdate extends Extension { } const message = utils.parseJSON(data.message, data.message); - const ID = (typeof message === 'object' && message.hasOwnProperty('id') ? message.id : message) as string; + const ID = (typeof message === 'object' && message['id'] !== undefined ? message.id : message) as string; const device = this.zigbee.resolveEntity(ID); const type = data.topic.substring(data.topic.lastIndexOf('/') + 1); - const responseData: {id: string; updateAvailable?: boolean; from?: string; to?: string} = {id: ID}; + const responseData: {id: string; updateAvailable?: boolean; from?: KeyValue | null; to?: KeyValue | null} = {id: ID}; let error: string | undefined; let errorStack: string | undefined; @@ -288,8 +291,8 @@ export default class OTAUpdate extends Extension { const to = await this.readSoftwareBuildIDAndDateCode(device); const [fromS, toS] = [stringify(from_), stringify(to)]; logger.info(`Device '${device.name}' was updated from '${fromS}' to '${toS}'`); - responseData.from = from_ ? utils.toSnakeCase(from_) : null; - responseData.to = to ? utils.toSnakeCase(to) : null; + responseData.from = from_ ? utils.toSnakeCaseObject(from_) : null; + responseData.to = to ? utils.toSnakeCaseObject(to) : null; /** * Re-configure after reading software build ID and date code, some devices use a * custom attribute for this (e.g. Develco SMSZB-120) diff --git a/lib/extension/publish.ts b/lib/extension/publish.ts index 44c1a14f..2c25083d 100644 --- a/lib/extension/publish.ts +++ b/lib/extension/publish.ts @@ -1,6 +1,8 @@ import assert from 'assert'; + import bind from 'bind-decorator'; import stringify from 'json-stable-stringify-without-jsonify'; + import * as zhc from 'zigbee-herdsman-converters'; import * as philips from 'zigbee-herdsman-converters/lib/philips'; @@ -117,7 +119,7 @@ export default class Publish extends Extension { // ever issue a read here, as we assume the device will properly report changes. // Only do this when the retrieve_state option is enabled for this device. // retrieve_state == deprecated - if (re instanceof Device && result && result.hasOwnProperty('readAfterWriteTime') && re.options.retrieve_state) { + if (re instanceof Device && result && result.readAfterWriteTime !== undefined && re.options.retrieve_state) { const convertGet = converter.convertGet; assert(convertGet !== undefined, 'Converter has `readAfterWriteTime` but no `convertGet`'); setTimeout(() => convertGet(target, key, meta), result.readAfterWriteTime); @@ -131,9 +133,9 @@ export default class Publish extends Extension { * (state) is probably unnecessary. */ if (settings.get().homeassistant) { - const hasColorTemp = message.hasOwnProperty('color_temp'); - const hasColor = message.hasOwnProperty('color'); - const hasBrightness = message.hasOwnProperty('brightness'); + const hasColorTemp = message.color_temp !== undefined; + const hasColor = message.color !== undefined; + const hasBrightness = message.brightness !== undefined; const isOn = entityState.state === 'ON' ? true : false; if (isOn && (hasColorTemp || hasColor) && !hasBrightness) { delete message.state; @@ -254,7 +256,7 @@ export default class Publish extends Extension { endpointOrGroupID = localTarget.ID; } - if (!usedConverters.hasOwnProperty(endpointOrGroupID)) usedConverters[endpointOrGroupID] = []; + if (usedConverters[endpointOrGroupID] === undefined) usedConverters[endpointOrGroupID] = []; /* istanbul ignore next */ // Match any key if the toZigbee converter defines no key. const converter = converters.find((c) => (!c.key || c.key.includes(key)) && (!c.endpoint || c.endpoint == endpointName)); @@ -302,7 +304,7 @@ export default class Publish extends Extension { if (parsedTopic.type === 'set' && converter.convertSet) { logger.debug(`Publishing '${parsedTopic.type}' '${key}' to '${re.name}'`); const result = await converter.convertSet(localTarget, key, value, meta); - const optimistic = !entitySettings.hasOwnProperty('optimistic') || entitySettings.optimistic; + const optimistic = entitySettings.optimistic === undefined || entitySettings.optimistic; if (result && result.state && optimistic) { const msg = result.state; diff --git a/lib/extension/receive.ts b/lib/extension/receive.ts index 29812d11..b3f35df3 100755 --- a/lib/extension/receive.ts +++ b/lib/extension/receive.ts @@ -1,7 +1,9 @@ import assert from 'assert'; + import bind from 'bind-decorator'; import debounce from 'debounce'; import stringify from 'json-stable-stringify-without-jsonify'; + import * as zhc from 'zigbee-herdsman-converters'; import logger from '../util/logger'; diff --git a/lib/model/device.ts b/lib/model/device.ts index bacc809b..e995c9f3 100644 --- a/lib/model/device.ts +++ b/lib/model/device.ts @@ -1,7 +1,7 @@ -/* eslint-disable brace-style */ import assert from 'assert'; -import {CustomClusters} from 'zigbee-herdsman/dist/zspec/zcl/definition/tstype'; + import * as zhc from 'zigbee-herdsman-converters'; +import {CustomClusters} from 'zigbee-herdsman/dist/zspec/zcl/definition/tstype'; import * as settings from '../util/settings'; diff --git a/lib/mqtt.ts b/lib/mqtt.ts index 9263aa37..2d91395d 100644 --- a/lib/mqtt.ts +++ b/lib/mqtt.ts @@ -1,7 +1,8 @@ import type {QoS} from 'mqtt-packet'; -import bind from 'bind-decorator'; import fs from 'fs'; + +import bind from 'bind-decorator'; import * as mqtt from 'mqtt'; import logger from './util/logger'; @@ -74,14 +75,14 @@ export default class MQTT { options.clientId = mqttSettings.client_id; } - if (mqttSettings.hasOwnProperty('reject_unauthorized') && !mqttSettings.reject_unauthorized) { + if (mqttSettings.reject_unauthorized !== undefined && !mqttSettings.reject_unauthorized) { logger.debug(`MQTT reject_unauthorized set false, ignoring certificate warnings.`); options.rejectUnauthorized = false; } return new Promise((resolve, reject) => { this.client = mqtt.connect(mqttSettings.server, options); - // @ts-ignore https://github.com/Koenkk/zigbee2mqtt/issues/9822 + // https://github.com/Koenkk/zigbee2mqtt/issues/9822 this.client.stream.setMaxListeners(0); this.eventBus.onPublishAvailability(this, this.publishStateOnline); diff --git a/lib/state.ts b/lib/state.ts index 5532cf13..84f38ae7 100644 --- a/lib/state.ts +++ b/lib/state.ts @@ -1,4 +1,5 @@ import fs from 'fs'; + import objectAssignDeep from 'object-assign-deep'; import data from './util/data'; @@ -88,7 +89,7 @@ class State { } exists(entity: Device | Group): boolean { - return this.state.hasOwnProperty(entity.ID); + return this.state[entity.ID] !== undefined; } get(entity: Group | Device): KeyValue { diff --git a/lib/types/types.d.ts b/lib/types/types.d.ts index 19cac675..97bc1f52 100644 --- a/lib/types/types.d.ts +++ b/lib/types/types.d.ts @@ -1,4 +1,3 @@ -/* eslint-disable camelcase */ import type TypeEventBus from 'lib/eventBus'; import type TypeExtension from 'lib/extension/extension'; import type TypeDevice from 'lib/model/device'; @@ -7,17 +6,17 @@ import type TypeMQTT from 'lib/mqtt'; import type TypeState from 'lib/state'; import type TypeZigbee from 'lib/zigbee'; import type {QoS} from 'mqtt-packet'; +import type * as zhc from 'zigbee-herdsman-converters'; import type { - NetworkParameters as ZHNetworkParameters, CoordinatorVersion as ZHCoordinatorVersion, LQI as ZHLQI, + NetworkParameters as ZHNetworkParameters, RoutingTable as ZHRoutingTable, RoutingTableEntry as ZHRoutingTableEntry, } from 'zigbee-herdsman/dist/adapter/tstype'; import type * as ZHEvents from 'zigbee-herdsman/dist/controller/events'; -import type {Device as ZHDevice, Group as ZHGroup, Endpoint as ZHEndpoint} from 'zigbee-herdsman/dist/controller/model'; +import type {Device as ZHDevice, Endpoint as ZHEndpoint, Group as ZHGroup} from 'zigbee-herdsman/dist/controller/model'; import type {Cluster as ZHCluster, FrameControl as ZHFrameControl} from 'zigbee-herdsman/dist/zspec/zcl/definition/tstype'; -import type * as zhc from 'zigbee-herdsman-converters'; import {LogLevel} from 'lib/util/settings'; @@ -34,8 +33,7 @@ declare global { type Extension = TypeExtension; // Types - // eslint-disable-next-line @typescript-eslint/no-explicit-any - type ExternalDefinition = zhc.Definition & {homeassistant: any}; + type ExternalDefinition = zhc.Definition & {homeassistant: unknown}; interface MQTTResponse { data: KeyValue; status: 'error' | 'ok'; @@ -114,7 +112,6 @@ declare global { } // Settings - // eslint-disable camelcase interface Settings { homeassistant?: { discovery_topic: string; diff --git a/lib/types/zigbee2mqtt-frontend.d.ts b/lib/types/zigbee2mqtt-frontend.d.ts index c0e3ff84..02bb80f0 100644 --- a/lib/types/zigbee2mqtt-frontend.d.ts +++ b/lib/types/zigbee2mqtt-frontend.d.ts @@ -3,7 +3,7 @@ declare module 'zigbee2mqtt-frontend' { } declare module 'connect-gzip-static' { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - export type RequestHandler = (req: any, res: any) => void; + import {IncomingMessage, ServerResponse} from 'http'; + export type RequestHandler = (req: IncomingMessage, res: ServerResponse, finalhandler: (err: unknown) => void) => void; export default function gzipStatic(root: string, options?: Record): RequestHandler; } diff --git a/lib/util/logger.ts b/lib/util/logger.ts index d055a3f4..5c1643f6 100644 --- a/lib/util/logger.ts +++ b/lib/util/logger.ts @@ -1,8 +1,9 @@ import assert from 'assert'; import fs from 'fs'; +import path from 'path'; + import fx from 'mkdir-recursive'; import moment from 'moment'; -import path from 'path'; import {rimrafSync} from 'rimraf'; import winston from 'winston'; @@ -108,7 +109,7 @@ class Logger { /* istanbul ignore next */ if (this.output.includes('syslog')) { logging += `, syslog`; - // eslint-disable-next-line + // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unused-expressions require('winston-syslog').Syslog; const options: KeyValue = { @@ -117,7 +118,7 @@ class Logger { ...settings.get().advanced.log_syslog, }; - if (options.hasOwnProperty('type')) { + if (options['type'] !== undefined) { options.type = options.type.toString(); } diff --git a/lib/util/settings.ts b/lib/util/settings.ts index 872b51c4..41f9f045 100644 --- a/lib/util/settings.ts +++ b/lib/util/settings.ts @@ -1,11 +1,13 @@ +import path from 'path'; + import Ajv, {ValidateFunction} from 'ajv'; import objectAssignDeep from 'object-assign-deep'; -import path from 'path'; import data from './data'; import schemaJson from './settings.schema.json'; import utils from './utils'; import yaml, {YAMLFileException} from './yaml'; + export let schema: KeyValue = schemaJson; schema = {}; @@ -143,63 +145,66 @@ function loadSettingsWithDefaults(): void { 'homeassistant_legacy_entity_attributes', 'homeassistant_status_topic', ]) { - // @ts-expect-error + // @ts-expect-error ignore typing if (_settingsWithDefaults.advanced[key] !== undefined) { - // @ts-expect-error + // @ts-expect-error ignore typing sLegacy[key.replace('homeassistant_', '')] = _settingsWithDefaults.advanced[key]; } } } const s = typeof _settingsWithDefaults.homeassistant === 'object' ? _settingsWithDefaults.homeassistant : {}; - // @ts-expect-error + // @ts-expect-error ignore typing _settingsWithDefaults.homeassistant = {}; - // @ts-expect-error + // @ts-expect-error ignore typing objectAssignDeep(_settingsWithDefaults.homeassistant, defaults, sLegacy, s); } if (_settingsWithDefaults.availability || _settingsWithDefaults.advanced?.availability_timeout) { const defaults = {}; const s = typeof _settingsWithDefaults.availability === 'object' ? _settingsWithDefaults.availability : {}; - // @ts-expect-error + // @ts-expect-error ignore typing _settingsWithDefaults.availability = {}; - // @ts-expect-error + // @ts-expect-error ignore typing objectAssignDeep(_settingsWithDefaults.availability, defaults, s); } if (_settingsWithDefaults.frontend) { const defaults = {port: 8080, auth_token: false}; const s = typeof _settingsWithDefaults.frontend === 'object' ? _settingsWithDefaults.frontend : {}; - // @ts-expect-error + // @ts-expect-error ignore typing _settingsWithDefaults.frontend = {}; - // @ts-expect-error + // @ts-expect-error ignore typing objectAssignDeep(_settingsWithDefaults.frontend, defaults, s); } - if (_settings.advanced?.hasOwnProperty('baudrate') && _settings.serial?.baudrate == null) { - // @ts-expect-error + // @ts-expect-error ignore typing + if (_settings.advanced?.baudrate !== undefined && _settings.serial?.baudrate == null) { + // @ts-expect-error ignore typing _settingsWithDefaults.serial.baudrate = _settings.advanced.baudrate; } - if (_settings.advanced?.hasOwnProperty('rtscts') && _settings.serial?.rtscts == null) { - // @ts-expect-error + // @ts-expect-error ignore typing + if (_settings.advanced?.rtscts !== undefined && _settings.serial?.rtscts == null) { + // @ts-expect-error ignore typing _settingsWithDefaults.serial.rtscts = _settings.advanced.rtscts; } - if (_settings.advanced?.hasOwnProperty('ikea_ota_use_test_url') && _settings.ota?.ikea_ota_use_test_url == null) { - // @ts-expect-error + // @ts-expect-error ignore typing + if (_settings.advanced?.ikea_ota_use_test_url !== undefined && _settings.ota?.ikea_ota_use_test_url == null) { + // @ts-expect-error ignore typing _settingsWithDefaults.ota.ikea_ota_use_test_url = _settings.advanced.ikea_ota_use_test_url; } - // @ts-expect-error - if (_settings.experimental?.hasOwnProperty('transmit_power') && _settings.advanced?.transmit_power == null) { - // @ts-expect-error + // @ts-expect-error ignore typing + if (_settings.experimental?.transmit_power !== undefined && _settings.advanced?.transmit_power == null) { + // @ts-expect-error ignore typing _settingsWithDefaults.advanced.transmit_power = _settings.experimental.transmit_power; } - // @ts-expect-error - if (_settings.experimental?.hasOwnProperty('output') && _settings.advanced?.output == null) { - // @ts-expect-error + // @ts-expect-error ignore typing + if (_settings.experimental?.output !== undefined && _settings.advanced?.output == null) { + // @ts-expect-error ignore typing _settingsWithDefaults.advanced.output = _settings.experimental.output; } @@ -207,15 +212,15 @@ function loadSettingsWithDefaults(): void { _settingsWithDefaults.advanced.log_level = 'warning'; } - // @ts-expect-error + // @ts-expect-error ignore typing if (_settingsWithDefaults.ban) { - // @ts-expect-error + // @ts-expect-error ignore typing _settingsWithDefaults.blocklist.push(..._settingsWithDefaults.ban); } - // @ts-expect-error + // @ts-expect-error ignore typing if (_settingsWithDefaults.whitelist) { - // @ts-expect-error + // @ts-expect-error ignore typing _settingsWithDefaults.passlist.push(..._settingsWithDefaults.whitelist); } } @@ -377,14 +382,14 @@ function read(): Settings { applyEnvironmentVariables(s); // Read !secret MQTT username and password if set - // eslint-disable-next-line - const interpretValue = (value: any): any => { - const ref = parseValueRef(value); - if (ref) { - return yaml.read(data.joinPath(ref.filename))[ref.key]; - } else { - return value; + const interpretValue = (value: T): T => { + if (typeof value === 'string') { + const ref = parseValueRef(value); + if (ref) { + return yaml.read(data.joinPath(ref.filename))[ref.key]; + } } + return value; }; if (s.mqtt?.user) { @@ -410,7 +415,6 @@ function read(): Settings { // Read devices/groups configuration from separate file if specified. const readDevicesOrGroups = (type: 'devices' | 'groups'): void => { if (typeof s[type] === 'string' || (Array.isArray(s[type]) && Array(s[type]).length > 0)) { - /* eslint-disable-line */ const files: string[] = Array.isArray(s[type]) ? s[type] : [s[type]]; s[type] = {}; for (const file of files) { @@ -439,30 +443,30 @@ function applyEnvironmentVariables(settings: Partial): void { if (envVariable) { const setting = path.reduce((acc, val) => { - // @ts-expect-error + // @ts-expect-error ignore typing acc[val] = acc[val] || {}; - // @ts-expect-error + // @ts-expect-error ignore typing return acc[val]; }, settings); if (type.indexOf('object') >= 0 || type.indexOf('array') >= 0) { try { - // @ts-expect-error + // @ts-expect-error ignore typing setting[key] = JSON.parse(envVariable); } catch { - // @ts-expect-error + // @ts-expect-error ignore typing setting[key] = envVariable; } } else if (type.indexOf('number') >= 0) { - // @ts-expect-error + // @ts-expect-error ignore typing setting[key] = (envVariable as unknown as number) * 1; } else if (type.indexOf('boolean') >= 0) { - // @ts-expect-error + // @ts-expect-error ignore typing setting[key] = envVariable.toLowerCase() === 'true'; } else { /* istanbul ignore else */ if (type.indexOf('string') >= 0) { - // @ts-expect-error + // @ts-expect-error ignore typing setting[key] = envVariable; } } @@ -523,7 +527,7 @@ export function set(path: string[], value: string | number | boolean | KeyValue) export function apply(settings: Record): boolean { getInternalSettings(); // Ensure _settings is initialized. - // @ts-expect-error + // @ts-expect-error ignore typing const newSettings = objectAssignDeep.noMutate(_settings, settings); utils.removeNullPropertiesFromObject(newSettings, NULLABLE_SETTINGS); ajvSetting(newSettings); @@ -678,14 +682,14 @@ export function addGroup(name: string, ID?: string): GroupOptions { // look for free ID ID = '1'; - while (settings.groups.hasOwnProperty(ID)) { + while (settings.groups[ID]) { ID = (Number.parseInt(ID) + 1).toString(); } } else { // ensure provided ID is not in use ID = ID.toString(); - if (settings.groups.hasOwnProperty(ID)) { + if (settings.groups[ID]) { throw new Error(`Group ID '${ID}' is already in use`); } } diff --git a/lib/util/utils.ts b/lib/util/utils.ts index f599f61c..9513ab5a 100644 --- a/lib/util/utils.ts +++ b/lib/util/utils.ts @@ -1,12 +1,13 @@ import type * as zhc from 'zigbee-herdsman-converters'; import assert from 'assert'; -import equals from 'fast-deep-equal/es6'; import fs from 'fs'; -import humanizeDuration from 'humanize-duration'; import path from 'path'; import vm from 'vm'; +import equals from 'fast-deep-equal/es6'; +import humanizeDuration from 'humanize-duration'; + import data from './data'; // construct a local ISO8601 string (instead of UTC-based) @@ -101,7 +102,7 @@ function objectIsEmpty(object: object): boolean { function objectHasProperties(object: {[s: string]: unknown}, properties: string[]): boolean { for (const property of properties) { - if (!object.hasOwnProperty(property)) { + if (object[property] === undefined) { return false; } } @@ -120,7 +121,7 @@ function equalsPartial(object: KeyValue, expected: KeyValue): boolean { } function getObjectProperty(object: KeyValue, key: string, defaultValue: unknown): unknown { - return object && object.hasOwnProperty(key) ? object[key] : defaultValue; + return object && object[key] !== undefined ? object[key] : defaultValue; } function getResponse(request: KeyValue | string, data: KeyValue, error?: string): MQTTResponse { @@ -130,7 +131,7 @@ function getResponse(request: KeyValue | string, data: KeyValue, error?: string) response.error = error; } - if (typeof request === 'object' && request.hasOwnProperty('transaction')) { + if (typeof request === 'object' && request['transaction'] !== undefined) { response.transaction = request.transaction; } @@ -210,24 +211,24 @@ function toNetworkAddressHex(value: number): string { return `0x${'0'.repeat(4 - hex.length)}${hex}`; } -// eslint-disable-next-line -function toSnakeCase(value: string | KeyValue): any { - if (typeof value === 'object') { - value = {...value}; - for (const key of Object.keys(value)) { - const keySnakeCase = toSnakeCase(key); - if (key !== keySnakeCase) { - value[keySnakeCase] = value[key]; - delete value[key]; - } +function toSnakeCaseObject(value: KeyValue): KeyValue { + value = {...value}; + for (const key of Object.keys(value)) { + const keySnakeCase = toSnakeCaseString(key); + assert(typeof keySnakeCase === 'string'); + if (key !== keySnakeCase) { + value[keySnakeCase] = value[key]; + delete value[key]; } - return value; - } else { - return value - .replace(/\.?([A-Z])/g, (x, y) => '_' + y.toLowerCase()) - .replace(/^_/, '') - .replace('_i_d', '_id'); } + return value; +} + +function toSnakeCaseString(value: string): string { + return value + .replace(/\.?([A-Z])/g, (x, y) => '_' + y.toLowerCase()) + .replace(/^_/, '') + .replace('_i_d', '_id'); } function charRange(start: string, stop: string): number[] { @@ -447,7 +448,8 @@ export default { loadModuleFromFile, removeNullPropertiesFromObject, toNetworkAddressHex, - toSnakeCase, + toSnakeCaseString, + toSnakeCaseObject, isZHEndpoint, isZHGroup, hours, diff --git a/lib/util/yaml.ts b/lib/util/yaml.ts index 1b2fb438..2c77db2c 100644 --- a/lib/util/yaml.ts +++ b/lib/util/yaml.ts @@ -1,5 +1,6 @@ -import equals from 'fast-deep-equal/es6'; import fs from 'fs'; + +import equals from 'fast-deep-equal/es6'; import yaml, {YAMLException} from 'js-yaml'; export class YAMLFileException extends YAMLException { diff --git a/lib/zigbee.ts b/lib/zigbee.ts index e2796c9a..d0d9ab76 100644 --- a/lib/zigbee.ts +++ b/lib/zigbee.ts @@ -1,6 +1,8 @@ -import bind from 'bind-decorator'; import {randomInt} from 'crypto'; + +import bind from 'bind-decorator'; import stringify from 'json-stable-stringify-without-jsonify'; + import {Controller} from 'zigbee-herdsman'; import * as ZHEvents from 'zigbee-herdsman/dist/controller/events'; @@ -115,7 +117,7 @@ export default class Zigbee { logger.debug( `Received Zigbee message from '${device.name}', type '${data.type}', ` + `cluster '${data.cluster}', data '${stringify(data.data)}' from endpoint ${data.endpoint.ID}` + - (data.hasOwnProperty('groupID') ? ` with groupID ${data.groupID}` : ``) + + (data['groupID'] !== undefined ? ` with groupID ${data.groupID}` : ``) + (device.zh.type === 'Coordinator' ? `, ignoring since it is from coordinator` : ``), ); if (device.zh.type === 'Coordinator') return; diff --git a/package-lock.json b/package-lock.json index 0505f1ca..b5aaad71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,9 @@ "@babel/plugin-proposal-decorators": "^7.24.7", "@babel/preset-env": "^7.25.4", "@babel/preset-typescript": "^7.24.7", + "@eslint/js": "^9.9.1", + "@ianvs/prettier-plugin-sort-imports": "^4.3.1", + "@types/eslint__js": "^8.42.3", "@types/finalhandler": "^1.2.3", "@types/humanize-duration": "^3.27.4", "@types/jest": "^29.5.12", @@ -51,18 +54,16 @@ "@types/node": "^22.5.4", "@types/object-assign-deep": "^0.4.3", "@types/readable-stream": "4.0.15", + "@types/sd-notify": "^2.8.2", "@types/ws": "8.5.12", - "@typescript-eslint/eslint-plugin": "^8.4.0", - "@typescript-eslint/parser": "^8.4.0", "babel-jest": "^29.7.0", - "eslint": "^8.57.0", + "eslint": "^9.9.1", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-jest": "^28.8.3", - "eslint-plugin-perfectionist": "^3.5.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tmp": "^0.2.3", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "typescript-eslint": "^8.3.0" }, "engines": { "node": "^18 || ^20 || ^22" @@ -2069,17 +2070,52 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", "dev": true, - "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -2087,7 +2123,7 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -2098,7 +2134,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2115,23 +2150,18 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2141,15 +2171,13 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2157,27 +2185,22 @@ "node": "*" } }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/@eslint/js": { + "version": "9.9.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.1.tgz", + "integrity": "sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==", "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", "dev": true, - "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@folder/readdir": { @@ -2194,46 +2217,6 @@ "node": ">=10" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -2248,13 +2231,41 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", "dev": true, - "license": "BSD-3-Clause" + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@ianvs/prettier-plugin-sort-imports": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@ianvs/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.3.1.tgz", + "integrity": "sha512-ZHwbyjkANZOjaBm3ZosADD2OUYGFzQGxfy67HmGZU94mHqe7g1LCMA7YYKB1Cq+UTPCBqlAYapY0KXAjKEw8Sg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.24.0", + "@babel/generator": "^7.23.6", + "@babel/parser": "^7.24.0", + "@babel/traverse": "^7.24.0", + "@babel/types": "^7.24.0", + "semver": "^7.5.2" + }, + "peerDependencies": { + "@vue/compiler-sfc": "2.7.x || 3.x", + "prettier": "2 || 3" + }, + "peerDependenciesMeta": { + "@vue/compiler-sfc": { + "optional": true + } + } }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -3236,12 +3247,6 @@ } } }, - "node_modules/@serialport/bindings-cpp/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "license": "MIT" - }, "node_modules/@serialport/bindings-interface": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.2.tgz", @@ -3323,12 +3328,6 @@ } } }, - "node_modules/@serialport/stream/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "license": "MIT" - }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -3401,6 +3400,31 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint__js": { + "version": "8.42.3", + "resolved": "https://registry.npmjs.org/@types/eslint__js/-/eslint__js-8.42.3.tgz", + "integrity": "sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==", + "dev": true, + "dependencies": { + "@types/eslint": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, "node_modules/@types/finalhandler": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/finalhandler/-/finalhandler-1.2.3.tgz", @@ -3473,11 +3497,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, "node_modules/@types/node": { "version": "22.5.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", - "license": "MIT", "dependencies": { "undici-types": "~6.19.2" } @@ -3499,6 +3528,12 @@ "safe-buffer": "~5.1.1" } }, + "node_modules/@types/sd-notify": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/@types/sd-notify/-/sd-notify-2.8.2.tgz", + "integrity": "sha512-LVWtuGvzso9z3N89NISzseq8RVHkEeg2h275370yQYx8/CoNaV2NnG17TTjDavy2FrmcUBFaR6OymlPQjqfb2g==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -3539,17 +3574,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.4.0.tgz", - "integrity": "sha512-rg8LGdv7ri3oAlenMACk9e+AR4wUV0yrrG+XKsGKOK0EVgeEDqurkXMPILG2836fW4ibokTB5v4b6Z9+GYQDEw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.3.0.tgz", + "integrity": "sha512-FLAIn63G5KH+adZosDYiutqkOkYEx0nvcwNNfJAf+c7Ae/H35qWwTYvPZUKFj5AS+WfHG/WJJfWnDnyNUlp8UA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.4.0", - "@typescript-eslint/type-utils": "8.4.0", - "@typescript-eslint/utils": "8.4.0", - "@typescript-eslint/visitor-keys": "8.4.0", + "@typescript-eslint/scope-manager": "8.3.0", + "@typescript-eslint/type-utils": "8.3.0", + "@typescript-eslint/utils": "8.3.0", + "@typescript-eslint/visitor-keys": "8.3.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -3573,16 +3608,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.4.0.tgz", - "integrity": "sha512-NHgWmKSgJk5K9N16GIhQ4jSobBoJwrmURaLErad0qlLjrpP5bECYg+wxVTGlGZmJbU03jj/dfnb6V9bw+5icsA==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.3.0.tgz", + "integrity": "sha512-h53RhVyLu6AtpUzVCYLPhZGL5jzTD9fZL+SYf/+hYOx2bDkyQXztXSc4tbvKYHzfMXExMLiL9CWqJmVz6+78IQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.4.0", - "@typescript-eslint/types": "8.4.0", - "@typescript-eslint/typescript-estree": "8.4.0", - "@typescript-eslint/visitor-keys": "8.4.0", + "@typescript-eslint/scope-manager": "8.3.0", + "@typescript-eslint/types": "8.3.0", + "@typescript-eslint/typescript-estree": "8.3.0", + "@typescript-eslint/visitor-keys": "8.3.0", "debug": "^4.3.4" }, "engines": { @@ -3602,14 +3637,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.4.0.tgz", - "integrity": "sha512-n2jFxLeY0JmKfUqy3P70rs6vdoPjHK8P/w+zJcV3fk0b0BwRXC/zxRTEnAsgYT7MwdQDt/ZEbtdzdVC+hcpF0A==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.3.0.tgz", + "integrity": "sha512-mz2X8WcN2nVu5Hodku+IR8GgCOl4C0G/Z1ruaWN4dgec64kDBabuXyPAr+/RgJtumv8EEkqIzf3X2U5DUKB2eg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.4.0", - "@typescript-eslint/visitor-keys": "8.4.0" + "@typescript-eslint/types": "8.3.0", + "@typescript-eslint/visitor-keys": "8.3.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3620,14 +3655,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.4.0.tgz", - "integrity": "sha512-pu2PAmNrl9KX6TtirVOrbLPLwDmASpZhK/XU7WvoKoCUkdtq9zF7qQ7gna0GBZFN0hci0vHaSusiL2WpsQk37A==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.3.0.tgz", + "integrity": "sha512-wrV6qh//nLbfXZQoj32EXKmwHf4b7L+xXLrP3FZ0GOUU72gSvLjeWUl5J5Ue5IwRxIV1TfF73j/eaBapxx99Lg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.4.0", - "@typescript-eslint/utils": "8.4.0", + "@typescript-eslint/typescript-estree": "8.3.0", + "@typescript-eslint/utils": "8.3.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -3645,9 +3680,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.4.0.tgz", - "integrity": "sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.3.0.tgz", + "integrity": "sha512-y6sSEeK+facMaAyixM36dQ5NVXTnKWunfD1Ft4xraYqxP0lC0POJmIaL/mw72CUMqjY9qfyVfXafMeaUj0noWw==", "dev": true, "license": "MIT", "engines": { @@ -3659,14 +3694,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.4.0.tgz", - "integrity": "sha512-kJ2OIP4dQw5gdI4uXsaxUZHRwWAGpREJ9Zq6D5L0BweyOrWsL6Sz0YcAZGWhvKnH7fm1J5YFE1JrQL0c9dd53A==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.3.0.tgz", + "integrity": "sha512-Mq7FTHl0R36EmWlCJWojIC1qn/ZWo2YiWYc1XVtasJ7FIgjo0MVv9rZWXEE7IK2CGrtwe1dVOxWwqXUdNgfRCA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.4.0", - "@typescript-eslint/visitor-keys": "8.4.0", + "@typescript-eslint/types": "8.3.0", + "@typescript-eslint/visitor-keys": "8.3.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3688,16 +3723,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.4.0.tgz", - "integrity": "sha512-swULW8n1IKLjRAgciCkTCafyTHHfwVQFt8DovmaF69sKbOxTSFMmIZaSHjqO9i/RV0wIblaawhzvtva8Nmm7lQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.3.0.tgz", + "integrity": "sha512-F77WwqxIi/qGkIGOGXNBLV7nykwfjLsdauRB/DOFPdv6LTF3BHHkBpq81/b5iMPSF055oO2BiivDJV4ChvNtXA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.4.0", - "@typescript-eslint/types": "8.4.0", - "@typescript-eslint/typescript-estree": "8.4.0" + "@typescript-eslint/scope-manager": "8.3.0", + "@typescript-eslint/types": "8.3.0", + "@typescript-eslint/typescript-estree": "8.3.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3711,13 +3746,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.4.0.tgz", - "integrity": "sha512-zTQD6WLNTre1hj5wp09nBIDiOc2U5r/qmzo7wxPn4ZgAjHql09EofqhF9WF+fZHzL5aCyaIpPcT2hyxl73kr9A==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.3.0.tgz", + "integrity": "sha512-RmZwrTbQ9QveF15m/Cl28n0LXD6ea2CjkhH5rQ55ewz3H24w+AMCJHPVYaZ8/0HoG8Z3cLLFFycRXxeO2tz9FA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.4.0", + "@typescript-eslint/types": "8.3.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -3728,13 +3763,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true, - "license": "ISC" - }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -3752,7 +3780,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, - "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -3765,7 +3792,6 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -4353,9 +4379,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001658", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001658.tgz", - "integrity": "sha512-N2YVqWbJELVdrnsW5p+apoQyYt51aBMSsBZki1XZEfeBCexcM/sf4xiAHcXQBkuOwJBXtWF7aW1sYX6tKebPHw==", + "version": "1.0.30001655", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz", + "integrity": "sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg==", "dev": true, "funding": [ { @@ -4415,9 +4441,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", - "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.0.tgz", + "integrity": "sha512-N1NGmowPlGBLsOZLPvm48StN04V4YvQRL0i6b7ctrVY3epjP/ct7hFLOItz6pDIvRjwpfPxi52a2UWV2ziir8g==", "dev": true, "license": "MIT" }, @@ -4718,12 +4744,12 @@ } }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "ms": "2.1.2" }, "engines": { "node": ">=6.0" @@ -4826,19 +4852,6 @@ "node": ">=6" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -4852,9 +4865,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.18", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.18.tgz", - "integrity": "sha512-1OfuVACu+zKlmjsNdcJuVQuVE61sZOLbNM4JAQ1Rvh6EOj0/EUKhMJjRH73InPlXSh8HIJk1cVZ8pyOV/FMdUQ==", + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", + "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==", "dev": true, "license": "ISC" }, @@ -4929,42 +4942,37 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "9.9.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.9.1.tgz", + "integrity": "sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.9.1", "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.0.2", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", @@ -4978,10 +4986,18 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-config-prettier": { @@ -4997,81 +5013,17 @@ "eslint": ">=7.0.0" } }, - "node_modules/eslint-plugin-jest": { - "version": "28.8.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.8.3.tgz", - "integrity": "sha512-HIQ3t9hASLKm2IhIOqnu+ifw7uLZkIlR7RYNv7fMcEi/p0CIiJmfriStQS2LDkgtY4nyLbIZAD+JL347Yc2ETQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "engines": { - "node": "^16.10.0 || ^18.12.0 || >=20.0.0" - }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0 || ^8.0.0", - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", - "jest": "*" - }, - "peerDependenciesMeta": { - "@typescript-eslint/eslint-plugin": { - "optional": true - }, - "jest": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-perfectionist": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-3.5.0.tgz", - "integrity": "sha512-vwDNuxlAlbZJ3DjHo6GnfZrmMlJBLFrkOLBV/rYvVnLFD+x54u9VyJcGOfJ2DK9d1cd3a/C/vtBrbBNgAC6Mrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "^8.4.0", - "@typescript-eslint/utils": "^8.4.0", - "minimatch": "^9.0.5", - "natural-compare-lite": "^1.4.0" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "astro-eslint-parser": "^1.0.2", - "eslint": ">=8.0.0", - "svelte": ">=3.0.0", - "svelte-eslint-parser": "^0.41.0", - "vue-eslint-parser": ">=9.0.0" - }, - "peerDependenciesMeta": { - "astro-eslint-parser": { - "optional": true - }, - "svelte": { - "optional": true - }, - "svelte-eslint-parser": { - "optional": true - }, - "vue-eslint-parser": { - "optional": true - } - } - }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -5184,6 +5136,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint/node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -5201,22 +5165,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5292,32 +5240,30 @@ "node": ">=8" } }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/espree": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", "dev": true, - "license": "(MIT OR CC0-1.0)", + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + }, "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -5355,7 +5301,6 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -5562,16 +5507,15 @@ "license": "MIT" }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, - "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/file-uri-to-path": { @@ -5598,7 +5542,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.0.tgz", "integrity": "sha512-bmwQPHFq/qiWp9CbNbCQU73klT+i5qwP/0tah3MGHp26vUt2YV4WkdtXRqOZo+H+4m38k8epFHOvO4BRuAuohw==", - "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -5642,43 +5585,23 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, - "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=16" } }, "node_modules/flatted": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/fn.name": { "version": "1.1.0", @@ -5687,9 +5610,9 @@ "license": "MIT" }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -6063,7 +5986,6 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, - "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -6080,7 +6002,6 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } @@ -8078,8 +7999,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -8130,7 +8050,6 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, - "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -8501,9 +8420,9 @@ } }, "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "license": "MIT" }, "node_modules/multicast-dns": { @@ -8533,13 +8452,6 @@ "dev": true, "license": "MIT" }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true, - "license": "MIT" - }, "node_modules/node-addon-api": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", @@ -8750,7 +8662,6 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, - "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -8839,18 +8750,18 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", - "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", + "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", "license": "ISC", "engines": { "node": "20 || >=22" } }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true, "license": "ISC" }, @@ -9404,6 +9315,12 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/serve-static": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", @@ -9934,6 +9851,29 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.3.0.tgz", + "integrity": "sha512-EvWjwWLwwKDIJuBjk2I6UkV8KEQcwZ0VM10nR1rIunRDIP67QJTZAHBXTX0HW/oI1H10YESF8yWie8fRQxjvFA==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.3.0", + "@typescript-eslint/parser": "8.3.0", + "@typescript-eslint/utils": "8.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", @@ -10432,7 +10372,6 @@ "version": "0.57.3", "resolved": "https://registry.npmjs.org/zigbee-herdsman/-/zigbee-herdsman-0.57.3.tgz", "integrity": "sha512-WpJFb5GpqqCWTrUOSP4MbUZoPudVp+G2ehbpgSQOG8ITsVHsQyQ6iA52l77pG9DXGGwUgmycIXKFmsG6U5zCbA==", - "license": "MIT", "dependencies": { "@serialport/bindings-cpp": "^12.0.1", "@serialport/parser-delimiter": "^12.0.0", @@ -10448,7 +10387,6 @@ "version": "20.12.1", "resolved": "https://registry.npmjs.org/zigbee-herdsman-converters/-/zigbee-herdsman-converters-20.12.1.tgz", "integrity": "sha512-ylV6hfbe/+AIl5cVlGd3KI4s5OE7xZ1IEsEftxb/0atwS+ldToDqfB31dq256XMFNZU/Atet1H90OAwXS2IHrA==", - "license": "MIT", "dependencies": { "axios": "^1.7.7", "buffer-crc32": "^1.0.0", diff --git a/package.json b/package.json index bcedf5cd..40a376a9 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "scripts": { "build": "tsc && node index.js writehash", "build-watch": "tsc --watch", - "eslint": "eslint lib/ --max-warnings=0", + "eslint": "eslint --max-warnings=0", "pretty:write": "prettier --write .", "pretty:check": "prettier --check .", "start": "node index.js", @@ -69,6 +69,9 @@ "@babel/plugin-proposal-decorators": "^7.24.7", "@babel/preset-env": "^7.25.4", "@babel/preset-typescript": "^7.24.7", + "@eslint/js": "^9.9.1", + "@ianvs/prettier-plugin-sort-imports": "^4.3.1", + "@types/eslint__js": "^8.42.3", "@types/finalhandler": "^1.2.3", "@types/humanize-duration": "^3.27.4", "@types/jest": "^29.5.12", @@ -76,18 +79,16 @@ "@types/node": "^22.5.4", "@types/object-assign-deep": "^0.4.3", "@types/readable-stream": "4.0.15", + "@types/sd-notify": "^2.8.2", "@types/ws": "8.5.12", - "@typescript-eslint/eslint-plugin": "^8.4.0", - "@typescript-eslint/parser": "^8.4.0", "babel-jest": "^29.7.0", - "eslint": "^8.57.0", + "eslint": "^9.9.1", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-jest": "^28.8.3", - "eslint-plugin-perfectionist": "^3.5.0", "jest": "^29.7.0", "prettier": "^3.3.3", "tmp": "^0.2.3", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "typescript-eslint": "^8.3.0" }, "overrides": { "zigbee-herdsman-converters": { diff --git a/test/availability.test.js b/test/availability.test.js index 3294a88f..118f1bad 100644 --- a/test/availability.test.js +++ b/test/availability.test.js @@ -1,14 +1,13 @@ -import data from './stub/data'; -import logger from './stub/logger'; -import MQTT from './stub/mqtt'; -import zigbeeHerdsman from './stub/zigbeeHerdsman'; - -import utils from '../lib/util/utils'; -import * as settings from '../lib/util/settings'; -import Controller from '../lib/controller'; -import Availability from '../lib/extension/availability'; -import flushPromises from './lib/flushPromises'; -import stringify from 'json-stable-stringify-without-jsonify'; +const data = require('./stub/data'); +const logger = require('./stub/logger'); +const zigbeeHerdsman = require('./stub/zigbeeHerdsman'); +const MQTT = require('./stub/mqtt'); +const settings = require('../lib/util/settings'); +const Controller = require('../lib/controller'); +const flushPromises = require('./lib/flushPromises'); +const Availability = require('../lib/extension/availability').default; +const stringify = require('json-stable-stringify-without-jsonify'); +const utils = require('../lib/util/utils').default; const mocks = [MQTT.publish, logger.warning, logger.info]; const devices = zigbeeHerdsman.devices;