diff --git a/.github/auto-merge.yml b/.github/auto-merge.yml index ef3d7c49..b3869738 100644 --- a/.github/auto-merge.yml +++ b/.github/auto-merge.yml @@ -1,9 +1,9 @@ - match: - dependency_type: development - update_type: "semver:minor" + dependency_type: development + update_type: 'semver:minor' - match: - dependency_type: production - update_type: "security:minor" + dependency_type: production + update_type: 'security:minor' - match: - dependency_type: production - update_type: "semver:patch" + dependency_type: production + update_type: 'semver:patch' diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f410accb..aa59598a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,22 +1,22 @@ version: 2 updates: -- package-ecosystem: npm - directory: "/" - schedule: - interval: monthly - time: "04:00" - timezone: Europe/Berlin - open-pull-requests-limit: 5 - assignees: - - AlCalzone - versioning-strategy: increase + - package-ecosystem: npm + directory: '/' + schedule: + interval: monthly + time: '04:00' + timezone: Europe/Berlin + open-pull-requests-limit: 5 + assignees: + - AlCalzone + versioning-strategy: increase -- package-ecosystem: github-actions - directory: "/" - schedule: - interval: monthly - time: "04:00" - timezone: Europe/Berlin - open-pull-requests-limit: 5 - assignees: - - AlCalzone + - package-ecosystem: github-actions + directory: '/' + schedule: + interval: monthly + time: '04:00' + timezone: Europe/Berlin + open-pull-requests-limit: 5 + assignees: + - AlCalzone diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9f99ffee..7f085ecb 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,41 +1,41 @@ -name: "CodeQL" +name: 'CodeQL' on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - schedule: - - cron: "6 21 * * 5" + push: + branches: ['master'] + pull_request: + branches: ['master'] + schedule: + - cron: '6 21 * * 5' jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write - strategy: - fail-fast: false - matrix: - language: [ javascript ] + strategy: + fail-fast: false + matrix: + language: [javascript] - steps: - - name: Checkout - uses: actions/checkout@v4 + steps: + - name: Checkout + uses: actions/checkout@v4 - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - queries: +security-and-quality + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + queries: +security-and-quality - - name: Autobuild - uses: github/codeql-action/autobuild@v3 + - name: Autobuild + uses: github/codeql-action/autobuild@v3 - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:${{ matrix.language }}" + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: '/language:${{ matrix.language }}' diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index 23b41b0c..be8cf7f4 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -4,21 +4,21 @@ name: Auto-Merge Dependabot PRs on: - # WARNING: This needs to be run in the PR base, DO NOT build untrusted code in this action - # details under https://github.blog/changelog/2021-02-19-github-actions-workflows-triggered-by-dependabot-prs-will-run-with-read-only-permissions/ - pull_request_target: + # WARNING: This needs to be run in the PR base, DO NOT build untrusted code in this action + # details under https://github.blog/changelog/2021-02-19-github-actions-workflows-triggered-by-dependabot-prs-will-run-with-read-only-permissions/ + pull_request_target: jobs: - auto-merge: - if: github.actor == 'dependabot[bot]' - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 + auto-merge: + if: github.actor == 'dependabot[bot]' + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 - - name: Check if PR should be auto-merged - uses: ahmadnassri/action-dependabot-auto-merge@v2 - with: - github-token: ${{ secrets.AUTO_MERGE_TOKEN }} - # By default, squash and merge, so Github chooses nice commit messages - command: squash and merge + - name: Check if PR should be auto-merged + uses: ahmadnassri/action-dependabot-auto-merge@v2 + with: + github-token: ${{ secrets.AUTO_MERGE_TOKEN }} + # By default, squash and merge, so Github chooses nice commit messages + command: squash and merge diff --git a/.github/workflows/test-and-release.yml b/.github/workflows/test-and-release.yml index f085b478..a8d318f9 100644 --- a/.github/workflows/test-and-release.yml +++ b/.github/workflows/test-and-release.yml @@ -3,114 +3,114 @@ name: Test and Release # Run this job on all pushes and pull requests # as well as tags with a semantic version on: - push: - branches: - - "*" - tags: - # normal versions - - "v[0-9]+.[0-9]+.[0-9]+" - # pre-releases - - "v[0-9]+.[0-9]+.[0-9]+-**" - pull_request: {} + push: + branches: + - '*' + tags: + # normal versions + - 'v[0-9]+.[0-9]+.[0-9]+' + # pre-releases + - 'v[0-9]+.[0-9]+.[0-9]+-**' + pull_request: {} jobs: - # Performs quick checks before the expensive test runs - check-and-lint: - if: contains(github.event.head_commit.message, '[skip ci]') == false - - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [20.x] # This should be LTS - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: "npm" - - - name: Install dependencies - run: npm ci - - - name: Perform a type check - run: npm run check - env: - CI: true - - - name: Lint TypeScript code - run: npm run lint - - # =================== - - # Runs unit tests on all supported node versions and OSes - unit-tests: - if: contains(github.event.head_commit.message, '[skip ci]') == false - - needs: [check-and-lint] - - runs-on: ${{ matrix.os }} - strategy: - matrix: - node-version: [18.x, 20.x, 22.x] - os: [ubuntu-latest] - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: "npm" - - - name: Install dependencies - run: npm ci - - - name: Run component tests - run: npm run test - env: - CI: true - - # =================== - - # Deploys the final package to NPM - deploy: - # Trigger this step only when a commit on master is tagged with a version number - if: | - contains(github.event.head_commit.message, '[skip ci]') == false && - github.event_name == 'push' && - startsWith(github.ref, 'refs/tags/v') - - needs: [unit-tests] - - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [20.x] # This should be LTS - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: "npm" - - - name: Install dependencies - run: npm ci - - - name: Create a clean build - run: npm run build - - - name: Publish package to npm - run: | - npm config set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} - npm whoami - npm publish + # Performs quick checks before the expensive test runs + check-and-lint: + if: contains(github.event.head_commit.message, '[skip ci]') == false + + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [20.x] # This should be LTS + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Perform a type check + run: npm run check + env: + CI: true + + - name: Lint TypeScript code + run: npm run lint + + # =================== + + # Runs unit tests on all supported node versions and OSes + unit-tests: + if: contains(github.event.head_commit.message, '[skip ci]') == false + + needs: [check-and-lint] + + runs-on: ${{ matrix.os }} + strategy: + matrix: + node-version: [18.x, 20.x, 22.x] + os: [ubuntu-latest] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run component tests + run: npm run test + env: + CI: true + + # =================== + + # Deploys the final package to NPM + deploy: + # Trigger this step only when a commit on master is tagged with a version number + if: | + contains(github.event.head_commit.message, '[skip ci]') == false && + github.event_name == 'push' && + startsWith(github.ref, 'refs/tags/v') + + needs: [unit-tests] + + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [20.x] # This should be LTS + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Create a clean build + run: npm run build + + - name: Publish package to npm + run: | + npm config set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }} + npm whoami + npm publish diff --git a/.lgtm.yml b/.lgtm.yml index 22dfdf85..02b4f175 100644 --- a/.lgtm.yml +++ b/.lgtm.yml @@ -1,7 +1,7 @@ path_classifiers: generated: - - "build/**/*.*" - - "**/*.d.ts" - - "**/*.js" + - 'build/**/*.*' + - '**/*.d.ts' + - '**/*.js' library: - gulpfile.js diff --git a/.mocharc.json b/.mocharc.json index ba7ba14a..ec931467 100644 --- a/.mocharc.json +++ b/.mocharc.json @@ -1,8 +1,4 @@ { - "require": [ - "./test/mocha.setup.js", - "ts-node/register", - "source-map-support/register" - ], - "watch-files": ["src/**/*.test.ts"] + "require": ["./test/mocha.setup.js", "ts-node/register", "source-map-support/register"], + "watch-files": ["src/**/*.test.ts"] } diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 2eb46089..00000000 --- a/.prettierignore +++ /dev/null @@ -1,4 +0,0 @@ -package.json -package-lock.json -build/ -CHANGELOG.md diff --git a/.prettierrc.js b/.prettierrc.js deleted file mode 100644 index 536c9d71..00000000 --- a/.prettierrc.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = { - semi: true, - trailingComma: "all", - singleQuote: false, - printWidth: 80, - useTabs: true, - tabWidth: 4, - endOfLine: "lf", - - overrides: [ - { - files: ".github/workflows/*.yml", - options: { - useTabs: false, - tabWidth: 2, - }, - }, - ], -}; diff --git a/.releaseconfig.json b/.releaseconfig.json index 36bbc017..ec65c03f 100644 --- a/.releaseconfig.json +++ b/.releaseconfig.json @@ -1,3 +1,3 @@ { - "plugins": ["license"] + "plugins": ["license"] } diff --git a/CHANGELOG.md b/CHANGELOG.md index c59c3255..7879001c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,11 @@ +## **WORK IN PROGRESS** +* Packages were updated + ## 5.0.0 (2024-09-14) * Types were migrated to '@iobroker/types' from '@types/iobroker' diff --git a/LICENSE b/LICENSE index 8e547a09..628b00c9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 AlCalzone +Copyright (c) 2024-2025 AlCalzone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/build/index.d.ts b/build/index.d.ts index 5f6f0849..6d14afbc 100644 --- a/build/index.d.ts +++ b/build/index.d.ts @@ -1 +1 @@ -export * from "./tests"; +export * from './tests'; diff --git a/build/lib/adapterTools.d.ts b/build/lib/adapterTools.d.ts index b44162b5..8df727a8 100644 --- a/build/lib/adapterTools.d.ts +++ b/build/lib/adapterTools.d.ts @@ -8,7 +8,7 @@ export declare function loadNpmPackage(adapterDir: string): Record; * @param adapterDir The directory the adapter resides in */ export declare function loadIoPackage(adapterDir: string): Record; -export declare function getAdapterExecutionMode(adapterDir: string): ioBroker.AdapterCommon["mode"]; +export declare function getAdapterExecutionMode(adapterDir: string): ioBroker.AdapterCommon['mode']; /** * Locates an adapter's main file * @param adapterDir The directory the adapter resides in diff --git a/build/lib/adapterTools.js b/build/lib/adapterTools.js index c74353f2..2d57a847 100644 --- a/build/lib/adapterTools.js +++ b/build/lib/adapterTools.js @@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( }) : function(o, v) { o["default"] = v; }); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; @@ -38,24 +48,27 @@ exports.getAdapterName = getAdapterName; exports.getAdapterFullName = getAdapterFullName; exports.getAdapterDependencies = getAdapterDependencies; // Add debug logging for tests +// @ts-expect-error no types const typeguards_1 = require("alcalzone-shared/typeguards"); const debug_1 = __importDefault(require("debug")); const fs_extra_1 = require("fs-extra"); const path = __importStar(require("path")); -const debug = (0, debug_1.default)("testing:unit:adapterTools"); +const debug = (0, debug_1.default)('testing:unit:adapterTools'); /** * Loads an adapter's package.json * @param adapterDir The directory the adapter resides in */ function loadNpmPackage(adapterDir) { - return require(path.join(adapterDir, "package.json")); + // eslint-disable-next-line @typescript-eslint/no-require-imports + return require(path.join(adapterDir, 'package.json')); } /** * Loads an adapter's io-package.json * @param adapterDir The directory the adapter resides in */ function loadIoPackage(adapterDir) { - return require(path.join(adapterDir, "io-package.json")); + // eslint-disable-next-line @typescript-eslint/no-require-imports + return require(path.join(adapterDir, 'io-package.json')); } function getAdapterExecutionMode(adapterDir) { const ioPackage = loadIoPackage(adapterDir); @@ -70,11 +83,11 @@ async function locateAdapterMainFile(adapterDir) { const ioPackage = loadIoPackage(adapterDir); const npmPackage = loadNpmPackage(adapterDir); // First look for the file defined in io-package.json or package.json or use "main.js" as a fallback - const mainFile = typeof ioPackage.common.main === "string" + const mainFile = typeof ioPackage.common.main === 'string' ? ioPackage.common.main - : typeof npmPackage.main === "string" + : typeof npmPackage.main === 'string' ? npmPackage.main - : "main.js"; + : 'main.js'; let ret = path.join(adapterDir, mainFile); debug(` => trying ${ret}`); if (await (0, fs_extra_1.pathExists)(ret)) { @@ -82,7 +95,7 @@ async function locateAdapterMainFile(adapterDir) { return ret; } // If both don't exist, JS-Controller uses .js as another fallback - ret = path.join(adapterDir, ioPackage.common.name + ".js"); + ret = path.join(adapterDir, ioPackage.common.name + '.js'); debug(` => trying ${ret}`); if (await (0, fs_extra_1.pathExists)(ret)) { debug(` => found ${mainFile}`); @@ -117,7 +130,7 @@ function loadInstanceObjects(adapterDir) { /** Returns the branded name of "iobroker" */ function getAppName(adapterDir) { const npmPackage = loadNpmPackage(adapterDir); - return npmPackage.name.split(".")[0] || "iobroker"; + return npmPackage.name.split('.')[0] || 'iobroker'; } /** Returns the name of an adapter without the prefix */ function getAdapterName(adapterDir) { @@ -135,13 +148,13 @@ function getAdapterDependencies(adapterDir) { const ret = {}; if ((0, typeguards_1.isArray)(ioPackage.common.dependencies)) { for (const dep of ioPackage.common.dependencies) { - if (typeof dep === "string") { - ret[dep] = "latest"; + if (typeof dep === 'string') { + ret[dep] = 'latest'; } else if ((0, typeguards_1.isObject)(dep)) { const key = Object.keys(dep)[0]; if (key) - ret[key] = dep[key] || "latest"; + ret[key] = dep[key] || 'latest'; } } } diff --git a/build/lib/executeCommand.d.ts b/build/lib/executeCommand.d.ts index 54096923..bb30d2cc 100644 --- a/build/lib/executeCommand.d.ts +++ b/build/lib/executeCommand.d.ts @@ -6,9 +6,9 @@ export interface ExecuteCommandOptions { /** Where to redirect the stdin. Default: process.stdin */ stdin: NodeJS.ReadStream; /** A write stream to redirect the stdout, "ignore" to ignore it or "pipe" to return it as a string. Default: process.stdout */ - stdout: NodeJS.WriteStream | "pipe" | "ignore"; + stdout: NodeJS.WriteStream | 'pipe' | 'ignore'; /** A write stream to redirect the stderr, "ignore" to ignore it or "pipe" to return it as a string. Default: process.stderr */ - stderr: NodeJS.WriteStream | "pipe" | "ignore"; + stderr: NodeJS.WriteStream | 'pipe' | 'ignore'; } export interface ExecuteCommandResult { /** The exit code of the spawned process */ diff --git a/build/lib/executeCommand.js b/build/lib/executeCommand.js index 48afc001..970f8530 100644 --- a/build/lib/executeCommand.js +++ b/build/lib/executeCommand.js @@ -1,16 +1,15 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.executeCommand = executeCommand; -const typeguards_1 = require("alcalzone-shared/typeguards"); const child_process_1 = require("child_process"); const isWindows = /^win/.test(process.platform); function executeCommand(command, argsOrOptions, options) { - return new Promise((resolve) => { + return new Promise(resolve => { let args; - if ((0, typeguards_1.isArray)(argsOrOptions)) { + if (Array.isArray(argsOrOptions)) { args = argsOrOptions; } - else if ((0, typeguards_1.isObject)(argsOrOptions)) { + else if (argsOrOptions && typeof argsOrOptions === 'object') { // no args were given options = argsOrOptions; } @@ -19,37 +18,33 @@ function executeCommand(command, argsOrOptions, options) { if (args == null) args = []; const spawnOptions = { - stdio: [ - options.stdin || process.stdin, - options.stdout || process.stdout, - options.stderr || process.stderr, - ], + stdio: [options.stdin || process.stdin, options.stdout || process.stdout, options.stderr || process.stderr], windowsHide: true, }; if (options.cwd != null) spawnOptions.cwd = options.cwd; // Fix npm / node executable paths on Windows if (isWindows) { - if (command === "npm") { - command += ".cmd"; + if (command === 'npm') { + command += '.cmd'; // Needed since Node.js v18.20.2 and v20.12.2 // https://github.com/nodejs/node/releases/tag/v18.20.2 spawnOptions.shell = true; } - else if (command === "node") { - command += ".exe"; + else if (command === 'node') { + command += '.exe'; } } if (options.logCommandExecution == null) options.logCommandExecution = false; if (options.logCommandExecution) { - console.log("executing: " + `${command} ${args.join(" ")}`); + console.log('executing: ' + `${command} ${args.join(' ')}`); } // Now execute the npm process and avoid throwing errors try { let bufferedStdout; let bufferedStderr; - const cmd = (0, child_process_1.spawn)(command, args, spawnOptions).on("close", (code, signal) => { + const cmd = (0, child_process_1.spawn)(command, args, spawnOptions).on('close', (code, signal) => { resolve({ exitCode: code ?? undefined, signal: signal ?? undefined, @@ -58,24 +53,24 @@ function executeCommand(command, argsOrOptions, options) { }); }); // Capture stdout/stderr if requested - if (options.stdout === "pipe") { - bufferedStdout = ""; - cmd.stdout.on("data", (chunk) => { + if (options.stdout === 'pipe') { + bufferedStdout = ''; + cmd.stdout.on('data', (chunk) => { if (Buffer.isBuffer(chunk)) - chunk = chunk.toString("utf8"); + chunk = chunk.toString('utf8'); bufferedStdout += chunk; }); } - if (options.stderr === "pipe") { - bufferedStderr = ""; - cmd.stderr.on("data", (chunk) => { + if (options.stderr === 'pipe') { + bufferedStderr = ''; + cmd.stderr.on('data', (chunk) => { if (Buffer.isBuffer(chunk)) - chunk = chunk.toString("utf8"); + chunk = chunk.toString('utf8'); bufferedStderr += chunk; }); } } - catch (e) { + catch { // doesn't matter, we return the exit code in the "close" handler } }); diff --git a/build/lib/str2regex.js b/build/lib/str2regex.js index 81605e81..cbf4461d 100644 --- a/build/lib/str2regex.js +++ b/build/lib/str2regex.js @@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.str2regex = str2regex; function str2regex(pattern) { return new RegExp(pattern - .replace(/\\/g, "\\\\") // Backslashes escapen - .replace(/\./g, "\\.") // Punkte als solche matchen - .replace(/\*/g, ".*") // Wildcard in Regex umsetzen - .replace(/!/g, "?!")); + .replace(/\\/g, '\\\\') // Backslashes escapen + .replace(/\./g, '\\.') // Punkte als solche matchen + .replace(/\*/g, '.*') // Wildcard in Regex umsetzen + .replace(/!/g, '?!')); } diff --git a/build/lib/testAdapter.d.ts b/build/lib/testAdapter.d.ts index cb0ff5c3..e69de29b 100644 --- a/build/lib/testAdapter.d.ts +++ b/build/lib/testAdapter.d.ts @@ -1 +0,0 @@ -export {}; diff --git a/build/lib/testAdapter.js b/build/lib/testAdapter.js index 79eb1d54..b20cb7e9 100644 --- a/build/lib/testAdapter.js +++ b/build/lib/testAdapter.js @@ -1,7 +1,6 @@ "use strict"; // TODO: Do we need this file? // import * as utils from "@iobroker/adapter-core"; -Object.defineProperty(exports, "__esModule", { value: true }); // const adapter = utils.adapter({ // name: "foo", // ready() { diff --git a/build/tests/index.d.ts b/build/tests/index.d.ts index c9aa7c3e..25f4322f 100644 --- a/build/tests/index.d.ts +++ b/build/tests/index.d.ts @@ -1,11 +1,11 @@ -import { testAdapter } from "./integration"; -import { validatePackageFiles } from "./packageFiles"; -import { testAdapterWithMocks } from "./unit"; -import { createMocks } from "./unit/harness/createMocks"; -import { createAsserts } from "./unit/mocks/mockDatabase"; -export { TestHarness as IntegrationTestHarness } from "./integration/lib/harness"; -export { MockAdapter } from "./unit/mocks/mockAdapter"; -export { MockDatabase } from "./unit/mocks/mockDatabase"; +import { testAdapter } from './integration'; +import { validatePackageFiles } from './packageFiles'; +import { testAdapterWithMocks } from './unit'; +import { createMocks } from './unit/harness/createMocks'; +import { createAsserts } from './unit/mocks/mockDatabase'; +export { TestHarness as IntegrationTestHarness } from './integration/lib/harness'; +export { MockAdapter } from './unit/mocks/mockAdapter'; +export { MockDatabase } from './unit/mocks/mockDatabase'; /** Predefined test sets */ export declare const tests: { /** @deprecated Adapter startup unit tests are no longer supported */ diff --git a/build/tests/integration/index.d.ts b/build/tests/integration/index.d.ts index 511d79cf..26f90ab2 100644 --- a/build/tests/integration/index.d.ts +++ b/build/tests/integration/index.d.ts @@ -1,4 +1,4 @@ -import { TestHarness } from "./lib/harness"; +import { TestHarness } from './lib/harness'; export interface TestAdapterOptions { allowedExitCodes?: (number | string)[]; /** The loglevel to use for DB and adapter related logs */ diff --git a/build/tests/integration/index.js b/build/tests/integration/index.js index 1287d406..bbcf2b11 100644 --- a/build/tests/integration/index.js +++ b/build/tests/integration/index.js @@ -15,16 +15,25 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( }) : function(o, v) { o["default"] = v; }); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); Object.defineProperty(exports, "__esModule", { value: true }); exports.testAdapter = testAdapter; -const async_1 = require("alcalzone-shared/async"); const os = __importStar(require("os")); const path = __importStar(require("path")); const adapterTools_1 = require("../../lib/adapterTools"); @@ -52,7 +61,7 @@ function testAdapter(adapterDir, options = {}) { const oneMinute = 60000; this.timeout(30 * oneMinute); if (await controllerSetup.isJsControllerRunning()) { - throw new Error("JS-Controller is already running! Stop it for the first test run and try again!"); + throw new Error('JS-Controller is already running! Stop it for the first test run and try again!'); } const adapterSetup = new adapterSetup_1.AdapterSetup(adapterDir, testDir); // Installation happens in two steps: @@ -62,7 +71,7 @@ function testAdapter(adapterDir, options = {}) { // Only then we can install the adapter, because some (including VIS) try to access // the databases if JS Controller is installed await adapterSetup.installAdapterInTestDir(); - const dbConnection = new dbConnection_1.DBConnection(appName, testDir, (0, logger_1.createLogger)(options.loglevel ?? "debug")); + const dbConnection = new dbConnection_1.DBConnection(appName, testDir, (0, logger_1.createLogger)(options.loglevel ?? 'debug')); await dbConnection.start(); controllerSetup.setupSystemConfig(dbConnection); await controllerSetup.disableAdminInstances(dbConnection); @@ -70,8 +79,7 @@ function testAdapter(adapterDir, options = {}) { await adapterSetup.addAdapterInstance(); await dbConnection.stop(); // Create a copy of the databases that we can restore later - ({ objects: objectsBackup, states: statesBackup } = - await dbConnection.backup()); + ({ objects: objectsBackup, states: statesBackup } = await dbConnection.backup()); } async function shutdownTests() { // Stopping the processes may take a while @@ -82,7 +90,7 @@ function testAdapter(adapterDir, options = {}) { } async function resetDbAndStartHarness() { this.timeout(30000); - dbConnection = new dbConnection_1.DBConnection(appName, testDir, (0, logger_1.createLogger)(options.loglevel ?? "debug")); + dbConnection = new dbConnection_1.DBConnection(appName, testDir, (0, logger_1.createLogger)(options.loglevel ?? 'debug')); // Clean up before every single test await Promise.all([ controllerSetup.clearDBDir(), @@ -96,7 +104,7 @@ function testAdapter(adapterDir, options = {}) { await harness.changeAdapterConfig(adapterName, { common: { enabled: true, - loglevel: options.loglevel ?? "debug", + loglevel: options.loglevel ?? 'debug', }, }); // And enable the sendTo emulation @@ -104,60 +112,52 @@ function testAdapter(adapterDir, options = {}) { } describe(`Adapter integration tests`, () => { before(prepareTests); - describe("Adapter startup", () => { + describe('Adapter startup', () => { beforeEach(resetDbAndStartHarness); afterEach(shutdownTests); - it("The adapter starts", function () { + it('The adapter starts', function () { this.timeout(60000); const allowedExitCodes = new Set(options.allowedExitCodes ?? []); // Adapters with these modes are allowed to "immediately" exit with code 0 switch (harness.getAdapterExecutionMode()) { - case "schedule": - case "once": + case 'schedule': + case 'once': // @ts-expect-error subscribe was deprecated - case "subscribe": + case 'subscribe': allowedExitCodes.add(0); } return new Promise((resolve, reject) => { // Register a handler to check the alive state and exit codes harness - .on("stateChange", async (id, state) => { - if (id === - `system.adapter.${adapterName}.0.alive` && - state && - state.val === true) { + .on('stateChange', async (id, state) => { + if (id === `system.adapter.${adapterName}.0.alive` && state && state.val === true) { // Wait a bit so we can catch errors that do not happen immediately - await (0, async_1.wait)(options.waitBeforeStartupSuccess != - undefined + await new Promise(resolve => setTimeout(resolve, options.waitBeforeStartupSuccess !== undefined ? options.waitBeforeStartupSuccess - : 5000); + : 5000)); resolve(`The adapter started successfully.`); } }) - .on("failed", (code) => { + .on('failed', code => { if (!allowedExitCodes.has(code)) { - reject(new Error(`The adapter startup was interrupted unexpectedly with ${typeof code === "number" - ? "code" - : "signal"} ${code}`)); + reject(new Error(`The adapter startup was interrupted unexpectedly with ${typeof code === 'number' ? 'code' : 'signal'} ${code}`)); } else { // This was a valid exit code - resolve(`The expected ${typeof code === "number" - ? "exit code" - : "signal"} ${code} was received.`); + resolve(`The expected ${typeof code === 'number' ? 'exit code' : 'signal'} ${code} was received.`); } }); harness.startAdapter(); - }).then((msg) => console.log(msg)); + }).then(msg => console.log(msg)); }); }); // Call the user's tests - if (typeof options.defineAdditionalTests === "function") { + if (typeof options.defineAdditionalTests === 'function') { const originalIt = global.it; // Ensure no it() gets called outside of a suite() function assertSuite() { if (!isInSuite) { - throw new Error("In user-defined adapter tests, it() must NOT be called outside of a suite()"); + throw new Error('In user-defined adapter tests, it() must NOT be called outside of a suite()'); } } const patchedIt = new Proxy(originalIt, { @@ -170,7 +170,7 @@ function testAdapter(adapterDir, options = {}) { return target[propKey]; }, }); - describe("User-defined tests", () => { + describe('User-defined tests', () => { // patch the global it() function so nobody can bypass the checks global.it = patchedIt; // a test suite is a special describe which sets up and tears down the test environment before and after ALL tests diff --git a/build/tests/integration/lib/adapterSetup.d.ts b/build/tests/integration/lib/adapterSetup.d.ts index ae8c198e..9ecdd115 100644 --- a/build/tests/integration/lib/adapterSetup.d.ts +++ b/build/tests/integration/lib/adapterSetup.d.ts @@ -1,4 +1,4 @@ -import type { DBConnection } from "./dbConnection"; +import type { DBConnection } from './dbConnection'; export declare class AdapterSetup { private adapterDir; private testDir; diff --git a/build/tests/integration/lib/adapterSetup.js b/build/tests/integration/lib/adapterSetup.js index a5069c69..d0ae977d 100644 --- a/build/tests/integration/lib/adapterSetup.js +++ b/build/tests/integration/lib/adapterSetup.js @@ -15,20 +15,30 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( }) : function(o, v) { o["default"] = v; }); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AdapterSetup = void 0; -/* eslint-disable @typescript-eslint/no-empty-function */ // Add debug logging for tests +// @ts-expect-error no types const objects_1 = require("alcalzone-shared/objects"); const debug_1 = __importDefault(require("debug")); const fs_extra_1 = require("fs-extra"); @@ -36,12 +46,12 @@ const path = __importStar(require("path")); const adapterTools_1 = require("../../../lib/adapterTools"); const executeCommand_1 = require("../../../lib/executeCommand"); const tools_1 = require("./tools"); -const debug = (0, debug_1.default)("testing:integration:AdapterSetup"); +const debug = (0, debug_1.default)('testing:integration:AdapterSetup'); class AdapterSetup { constructor(adapterDir, testDir) { this.adapterDir = adapterDir; this.testDir = testDir; - debug("Creating AdapterSetup..."); + debug('Creating AdapterSetup...'); this.adapterName = (0, adapterTools_1.getAdapterName)(this.adapterDir); this.adapterFullName = (0, adapterTools_1.getAdapterFullName)(this.adapterDir); this.appName = (0, adapterTools_1.getAppName)(this.adapterDir); @@ -62,13 +72,13 @@ class AdapterSetup { } /** Copies all adapter files (except a few) to the test directory */ async installAdapterInTestDir() { - debug("Copying adapter files to test directory..."); + debug('Copying adapter files to test directory...'); // We install the adapter almost like it would be installed in the real world // Therefore pack it into a tarball and put it in the test dir for installation - const packResult = await (0, executeCommand_1.executeCommand)("npm", ["pack", "--loglevel", "silent"], { - stdout: "pipe", + const packResult = await (0, executeCommand_1.executeCommand)('npm', ['pack', '--loglevel', 'silent'], { + stdout: 'pipe', }); - if (packResult.exitCode !== 0 || typeof packResult.stdout !== "string") + if (packResult.exitCode !== 0 || typeof packResult.stdout !== 'string') throw new Error(`Packing the adapter tarball failed!`); // The last non-empty line of `npm pack`s STDOUT contains the tarball path const stdoutLines = packResult.stdout.trim().split(/[\r\n]+/); @@ -80,63 +90,49 @@ class AdapterSetup { // so that the installation in the following step // won't grab the cached files. // See https://github.com/ioBroker/testing/issues/612 - debug("Removing the adapter from package-lock.json"); - await (0, executeCommand_1.executeCommand)("npm", [ - "uninstall", - this.adapterFullName, - "--package-lock-only", - "--omit=dev", - ], { + debug('Removing the adapter from package-lock.json'); + await (0, executeCommand_1.executeCommand)('npm', ['uninstall', this.adapterFullName, '--package-lock-only', '--omit=dev'], { cwd: this.testDir, }); // Complete the package.json, so npm can do it's magic - debug("Saving the adapter in package.json"); - const packageJsonPath = path.join(this.testDir, "package.json"); + debug('Saving the adapter in package.json'); + const packageJsonPath = path.join(this.testDir, 'package.json'); const packageJson = await (0, fs_extra_1.readJSON)(packageJsonPath); packageJson.dependencies[this.adapterFullName] = `file:./${tarballName}`; for (const [dep, version] of (0, objects_1.entries)((0, adapterTools_1.getAdapterDependencies)(this.adapterDir))) { - // Don't overwrite the js-controller github dependency with a probably lower one - if (dep === "js-controller") + // Don't overwrite the js-controller GitHub dependency with a probably lower one + if (dep === 'js-controller') continue; packageJson.dependencies[`${this.appName}.${dep}`] = version; } await (0, fs_extra_1.writeJSON)(packageJsonPath, packageJson, { spaces: 2 }); - debug("Deleting old remains of this adapter"); + debug('Deleting old remains of this adapter'); if (await (0, fs_extra_1.pathExists)(this.testAdapterDir)) await (0, fs_extra_1.remove)(this.testAdapterDir); - debug("Installing adapter"); + debug('Installing adapter'); // Defer to npm to install the controller (if it wasn't already) - await (0, executeCommand_1.executeCommand)("npm", ["i", "--omit=dev"], { + await (0, executeCommand_1.executeCommand)('npm', ['i', '--omit=dev'], { cwd: this.testDir, }); - debug(" => done!"); + debug(' => done!'); } /** * Adds an instance for an already installed adapter in the test directory */ async addAdapterInstance() { - debug("Adding adapter instance..."); + debug('Adding adapter instance...'); // execute iobroker add -- This also installs missing dependencies - const addResult = await (0, executeCommand_1.executeCommand)("node", [ - `${this.appName}.js`, - "add", - this.adapterName, - "--enabled", - "false", - ], { + const addResult = await (0, executeCommand_1.executeCommand)('node', [`${this.appName}.js`, 'add', this.adapterName, '--enabled', 'false'], { cwd: this.testControllerDir, - stdout: "ignore", + stdout: 'ignore', }); if (addResult.exitCode !== 0) throw new Error(`Adding the adapter instance failed!`); - debug(" => done!"); + debug(' => done!'); } async deleteOldInstances(dbConnection) { - debug("Removing old adapter instances..."); - const allKeys = new Set([ - ...(await dbConnection.getObjectIDs()), - ...(await dbConnection.getStateIDs()), - ]); + debug('Removing old adapter instances...'); + const allKeys = new Set([...(await dbConnection.getObjectIDs()), ...(await dbConnection.getStateIDs())]); const instanceRegex = new RegExp(`^system\\.adapter\\.${this.adapterName}\\.\\d+`); const instanceObjsRegex = new RegExp(`^${this.adapterName}\\.\\d+\.`); const belongsToAdapter = (id) => { @@ -145,12 +141,12 @@ class AdapterSetup { id === this.adapterName || id === `${this.adapterName}.admin`); }; - const idsToDelete = [...allKeys].filter((id) => belongsToAdapter(id)); + const idsToDelete = [...allKeys].filter(id => belongsToAdapter(id)); for (const id of idsToDelete) { await dbConnection.delObject(id).catch(() => { }); await dbConnection.delState(id).catch(() => { }); } - debug(" => done!"); + debug(' => done!'); } } exports.AdapterSetup = AdapterSetup; diff --git a/build/tests/integration/lib/controllerSetup.d.ts b/build/tests/integration/lib/controllerSetup.d.ts index 505c5ee6..1b2d8e99 100644 --- a/build/tests/integration/lib/controllerSetup.d.ts +++ b/build/tests/integration/lib/controllerSetup.d.ts @@ -1,4 +1,4 @@ -import type { DBConnection } from "./dbConnection"; +import type { DBConnection } from './dbConnection'; export declare class ControllerSetup { private adapterDir; private testDir; diff --git a/build/tests/integration/lib/controllerSetup.js b/build/tests/integration/lib/controllerSetup.js index be0f9537..be55cf2a 100644 --- a/build/tests/integration/lib/controllerSetup.js +++ b/build/tests/integration/lib/controllerSetup.js @@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( }) : function(o, v) { o["default"] = v; }); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; @@ -35,12 +45,12 @@ const path = __importStar(require("path")); const adapterTools_1 = require("../../../lib/adapterTools"); const executeCommand_1 = require("../../../lib/executeCommand"); const tools_1 = require("./tools"); -const debug = (0, debug_1.default)("testing:integration:ControllerSetup"); +const debug = (0, debug_1.default)('testing:integration:ControllerSetup'); class ControllerSetup { constructor(adapterDir, testDir) { this.adapterDir = adapterDir; this.testDir = testDir; - debug("Creating ControllerSetup..."); + debug('Creating ControllerSetup...'); this.adapterName = (0, adapterTools_1.getAdapterName)(this.adapterDir); this.appName = (0, adapterTools_1.getAppName)(this.adapterDir); this.testAdapterDir = (0, tools_1.getTestAdapterDir)(this.adapterDir, this.testDir); @@ -53,49 +63,49 @@ class ControllerSetup { debug(` appName: ${this.appName}`); debug(` adapterName: ${this.adapterName}`); } - async prepareTestDir(controllerVersion = "dev") { + async prepareTestDir(controllerVersion = 'dev') { debug(`Preparing the test directory. JS-Controller version: "${controllerVersion}"...`); // Make sure the test dir exists await (0, fs_extra_1.ensureDir)(this.testDir); // Write the package.json const packageJson = { name: path.basename(this.testDir), - version: "1.0.0", - main: "index.js", + version: '1.0.0', + main: 'index.js', scripts: { test: 'echo "Error: no test specified" && exit 1', }, keywords: [], - author: "", - license: "ISC", + author: '', + license: 'ISC', dependencies: { [`${this.appName}.js-controller`]: controllerVersion, }, - description: "", + description: '', }; - await (0, fs_extra_1.writeJSON)(path.join(this.testDir, "package.json"), packageJson, { + await (0, fs_extra_1.writeJSON)(path.join(this.testDir, 'package.json'), packageJson, { spaces: 2, }); // Delete a possible package-lock.json as it can mess with future installations - const pckLockPath = path.join(this.testDir, "package-lock.json"); + const pckLockPath = path.join(this.testDir, 'package-lock.json'); if (await (0, fs_extra_1.pathExists)(pckLockPath)) await (0, fs_extra_1.unlink)(pckLockPath); // Set the engineStrict flag on new Node.js versions to be in line with newer ioBroker installations - const nodeMajorVersion = parseInt(process.versions.node.split(".")[0], 10); + const nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10); if (nodeMajorVersion >= 10) { - await (0, fs_extra_1.writeFile)(path.join(this.testDir, ".npmrc"), "engine-strict=true", "utf8"); + await (0, fs_extra_1.writeFile)(path.join(this.testDir, '.npmrc'), 'engine-strict=true', 'utf8'); } // Remember if JS-Controller is installed already. If so, we need to call `setup first` afterwards const wasJsControllerInstalled = await this.isJsControllerInstalled(); // Defer to npm to install the controller (if it wasn't already) - debug("(Re-)installing JS Controller..."); - await (0, executeCommand_1.executeCommand)("npm", ["i", "--omit=dev"], { + debug('(Re-)installing JS Controller...'); + await (0, executeCommand_1.executeCommand)('npm', ['i', '--omit=dev'], { cwd: this.testDir, }); // Prepare/clean the databases and config if (wasJsControllerInstalled) await this.setupJsController(); - debug(" => done!"); + debug(' => done!'); } /** * Tests if JS-Controller is already installed @@ -103,10 +113,9 @@ class ControllerSetup { * @param testDir The directory the integration tests are executed in */ async isJsControllerInstalled() { - debug("Testing if JS-Controller is installed..."); + debug('Testing if JS-Controller is installed...'); // We expect js-controller to be installed if the dir in /node_modules and the data directory exist - const isInstalled = (await (0, fs_extra_1.pathExists)(this.testControllerDir)) && - (await (0, fs_extra_1.pathExists)(this.testDataDir)); + const isInstalled = (await (0, fs_extra_1.pathExists)(this.testControllerDir)) && (await (0, fs_extra_1.pathExists)(this.testDataDir)); debug(` => ${isInstalled}`); return isInstalled; } @@ -114,8 +123,8 @@ class ControllerSetup { * Tests if an instance of JS-Controller is already running by attempting to connect to the Objects DB */ isJsControllerRunning() { - debug("Testing if JS-Controller is running..."); - return new Promise((resolve) => { + debug('Testing if JS-Controller is running...'); + return new Promise(resolve => { const client = new net_1.Socket(); const timeout = setTimeout(() => { // Assume the connection failed after 1 s @@ -127,16 +136,16 @@ class ControllerSetup { client .connect({ port: 9000, - host: "127.0.0.1", + host: '127.0.0.1', }) - .on("connect", () => { + .on('connect', () => { // The connection succeeded client.destroy(); debug(` => true`); clearTimeout(timeout); resolve(true); }) - .on("error", () => { + .on('error', () => { client.destroy(); debug(` => false`); clearTimeout(timeout); @@ -168,19 +177,19 @@ class ControllerSetup { * Sets up an existing JS-Controller instance for testing by executing "iobroker setup first" */ async setupJsController() { - debug("Initializing JS-Controller installation..."); + debug('Initializing JS-Controller installation...'); // Stop the controller before calling setup first - await (0, executeCommand_1.executeCommand)("node", [`${this.appName}.js`, "stop"], { + await (0, executeCommand_1.executeCommand)('node', [`${this.appName}.js`, 'stop'], { cwd: this.testControllerDir, - stdout: "ignore", + stdout: 'ignore', }); - const setupResult = await (0, executeCommand_1.executeCommand)("node", [`${this.appName}.js`, "setup", "first", "--console"], { + const setupResult = await (0, executeCommand_1.executeCommand)('node', [`${this.appName}.js`, 'setup', 'first', '--console'], { cwd: this.testControllerDir, - stdout: "ignore", + stdout: 'ignore', }); if (setupResult.exitCode !== 0) throw new Error(`${this.appName} setup first failed!`); - debug(" => done!"); + debug(' => done!'); } /** * Changes the objects and states db to use alternative ports @@ -193,7 +202,7 @@ class ControllerSetup { systemConfig.objects.port = 19001; systemConfig.states.port = 19000; dbConnection.setSystemConfig(systemConfig); - debug(" => done!"); + debug(' => done!'); } /** * Clears the log dir for integration tests (and creates it if it doesn't exist) @@ -201,7 +210,7 @@ class ControllerSetup { * @param testDir The directory the integration tests are executed in */ clearLogDir() { - debug("Cleaning log directory..."); + debug('Cleaning log directory...'); return (0, fs_extra_1.emptyDir)((0, tools_1.getTestLogDir)(this.appName, this.testDir)); } /** @@ -210,7 +219,7 @@ class ControllerSetup { * @param testDir The directory the integration tests are executed in */ clearDBDir() { - debug("Cleaning SQLite directory..."); + debug('Cleaning SQLite directory...'); return (0, fs_extra_1.emptyDir)((0, tools_1.getTestDBDir)(this.appName, this.testDir)); } /** @@ -218,10 +227,10 @@ class ControllerSetup { * @param objects The contents of objects.json */ async disableAdminInstances(dbConnection) { - debug("Disabling admin instances..."); - const instanceObjects = await dbConnection.getObjectViewAsync("system", "instance", { - startkey: "system.adapter.admin.", - endkey: "system.adapter.admin.\u9999", + debug('Disabling admin instances...'); + const instanceObjects = await dbConnection.getObjectViewAsync('system', 'instance', { + startkey: 'system.adapter.admin.', + endkey: 'system.adapter.admin.\u9999', }); for (const { id, value: obj } of instanceObjects.rows) { if (obj && obj.common) { @@ -229,7 +238,7 @@ class ControllerSetup { await dbConnection.setObject(id, obj); } } - debug(" => done!"); + debug(' => done!'); } } exports.ControllerSetup = ControllerSetup; diff --git a/build/tests/integration/lib/dbConnection.d.ts b/build/tests/integration/lib/dbConnection.d.ts index 782e35c5..abf5fe1e 100644 --- a/build/tests/integration/lib/dbConnection.d.ts +++ b/build/tests/integration/lib/dbConnection.d.ts @@ -1,9 +1,9 @@ -import EventEmitter from "events"; +import EventEmitter from 'events'; export type ObjectsDB = Record; export type StatesDB = Record; export interface DBConnection { - on(event: "objectChange", handler: ioBroker.ObjectChangeHandler): this; - on(event: "stateChange", handler: ioBroker.StateChangeHandler): this; + on(event: 'objectChange', handler: ioBroker.ObjectChangeHandler): this; + on(event: 'stateChange', handler: ioBroker.StateChangeHandler): this; } /** The DB connection capsules access to the states and objects DB */ export declare class DBConnection extends EventEmitter { @@ -25,9 +25,9 @@ export declare class DBConnection extends EventEmitter { private _statesClient; /** The underlying states client instance that can be used to access the states DB */ get statesClient(): any; - get objectsType(): "file" | "jsonl"; + get objectsType(): 'file' | 'jsonl'; get objectsPath(): string; - get statesType(): "file" | "jsonl"; + get statesType(): 'file' | 'jsonl'; get statesPath(): string; getSystemConfig(): any; backup(): Promise<{ @@ -44,15 +44,15 @@ export declare class DBConnection extends EventEmitter { private createObjectsDB; /** Creates the states DB and sets up listeners for it */ private createStatesDB; - readonly getObject: ioBroker.Adapter["getForeignObjectAsync"]; - readonly setObject: ioBroker.Adapter["setForeignObjectAsync"]; - readonly delObject: ioBroker.Adapter["delForeignObjectAsync"]; - readonly getState: ioBroker.Adapter["getForeignStateAsync"]; - readonly setState: ioBroker.Adapter["setForeignStateAsync"]; - readonly delState: ioBroker.Adapter["delForeignStateAsync"]; + readonly getObject: ioBroker.Adapter['getForeignObjectAsync']; + readonly setObject: ioBroker.Adapter['setForeignObjectAsync']; + readonly delObject: ioBroker.Adapter['delForeignObjectAsync']; + readonly getState: ioBroker.Adapter['getForeignStateAsync']; + readonly setState: ioBroker.Adapter['setForeignStateAsync']; + readonly delState: ioBroker.Adapter['delForeignStateAsync']; subscribeMessage(id: string): void; pushMessage(instanceId: string, msg: any, callback: (err: Error | null, id: any) => void): void; - readonly getObjectViewAsync: ioBroker.Adapter["getObjectViewAsync"]; + readonly getObjectViewAsync: ioBroker.Adapter['getObjectViewAsync']; getStateIDs(pattern?: string): Promise; getObjectIDs(pattern?: string): Promise; } diff --git a/build/tests/integration/lib/dbConnection.js b/build/tests/integration/lib/dbConnection.js index d561cfde..503de8d9 100644 --- a/build/tests/integration/lib/dbConnection.js +++ b/build/tests/integration/lib/dbConnection.js @@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( }) : function(o, v) { o["default"] = v; }); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; @@ -32,7 +42,7 @@ const events_1 = __importDefault(require("events")); const fs_extra_1 = require("fs-extra"); const path = __importStar(require("path")); const tools_1 = require("./tools"); -const debug = (0, debug_1.default)("testing:integration:DBConnection"); +const debug = (0, debug_1.default)('testing:integration:DBConnection'); /** The DB connection capsules access to the states and objects DB */ class DBConnection extends events_1.default { /** @@ -47,43 +57,43 @@ class DBConnection extends events_1.default { this._isRunning = false; this.getObject = async (id) => { if (!this._objectsClient) { - throw new Error("Objects DB is not running"); + throw new Error('Objects DB is not running'); } return this._objectsClient.getObjectAsync(id); }; this.setObject = async (...args) => { if (!this._objectsClient) { - throw new Error("Objects DB is not running"); + throw new Error('Objects DB is not running'); } return this._objectsClient.setObjectAsync(...args); }; this.delObject = async (...args) => { if (!this._objectsClient) { - throw new Error("Objects DB is not running"); + throw new Error('Objects DB is not running'); } return this._objectsClient.delObjectAsync(...args); }; this.getState = async (id) => { if (!this._statesClient) { - throw new Error("States DB is not running"); + throw new Error('States DB is not running'); } return this._statesClient.getStateAsync(id); }; this.setState = (async (...args) => { if (!this._statesClient) { - throw new Error("States DB is not running"); + throw new Error('States DB is not running'); } return this._statesClient.setStateAsync(...args); }); this.delState = async (...args) => { if (!this._statesClient) { - throw new Error("States DB is not running"); + throw new Error('States DB is not running'); } return this._statesClient.delStateAsync(...args); }; this.getObjectViewAsync = async (...args) => { if (!this._objectsClient) { - throw new Error("Objects DB is not running"); + throw new Error('Objects DB is not running'); } return this._objectsClient.getObjectViewAsync(...args); }; @@ -102,20 +112,20 @@ class DBConnection extends events_1.default { return this.getSystemConfig().objects.type; } get objectsPath() { - return path.join(this.testDataDir, this.objectsType === "file" ? "objects.json" : "objects.jsonl"); + return path.join(this.testDataDir, this.objectsType === 'file' ? 'objects.json' : 'objects.jsonl'); } get statesType() { return this.getSystemConfig().states.type; } get statesPath() { - return path.join(this.testDataDir, this.statesType === "file" ? "states.json" : "states.jsonl"); + return path.join(this.testDataDir, this.statesType === 'file' ? 'states.json' : 'states.jsonl'); } getSystemConfig() { const systemFilename = path.join(this.testDataDir, `${this.appName}.json`); return (0, fs_extra_1.readJSONSync)(systemFilename); } async backup() { - debug("Creating DB backup..."); + debug('Creating DB backup...'); const wasRunning = this._isRunning; await this.stop(); const objects = await (0, fs_extra_1.readFile)(this.objectsPath); @@ -125,7 +135,7 @@ class DBConnection extends events_1.default { return { objects, states }; } async restore(objects, states) { - debug("Restoring DB backup..."); + debug('Restoring DB backup...'); const wasRunning = this._isRunning; await this.stop(); await (0, fs_extra_1.writeFile)(this.objectsPath, objects); @@ -142,21 +152,21 @@ class DBConnection extends events_1.default { } async start() { if (this._isRunning) { - debug("At least one DB instance is already running, not starting again..."); + debug('At least one DB instance is already running, not starting again...'); return; } - debug("starting DB instances..."); + debug('starting DB instances...'); await this.createObjectsDB(); await this.createStatesDB(); this._isRunning = true; - debug("DB instances started"); + debug('DB instances started'); } async stop() { if (!this._isRunning) { - debug("No DB instance is running, nothing to stop..."); + debug('No DB instance is running, nothing to stop...'); return; } - debug("Stopping DB instances..."); + debug('Stopping DB instances...'); // Stop clients before servers await this._objectsClient?.destroy(); await this._objectsServer?.destroy(); @@ -167,36 +177,33 @@ class DBConnection extends events_1.default { this._statesClient = null; this._statesServer = null; this._isRunning = false; - debug("DB instances stopped"); + debug('DB instances stopped'); } /** Creates the objects DB and sets up listeners for it */ async createObjectsDB() { - debug("creating objects DB"); + debug('creating objects DB'); const objectsType = this.objectsType; debug(` => objects DB type: ${objectsType}`); const settings = { connection: { type: objectsType, - host: "127.0.0.1", + host: '127.0.0.1', port: 19001, - user: "", - pass: "", + user: '', + pass: '', noFileCache: false, connectTimeout: 2000, }, logger: this.logger, }; const objectsDbPath = require.resolve(`@iobroker/db-objects-${objectsType}`, { - paths: [ - path.join(this.testDir, "node_modules"), - path.join(this.testControllerDir, "node_modules"), - ], + paths: [path.join(this.testDir, 'node_modules'), path.join(this.testControllerDir, 'node_modules')], }); debug(` => objects DB lib found at ${objectsDbPath}`); - // eslint-disable-next-line @typescript-eslint/no-var-requires + // eslint-disable-next-line @typescript-eslint/no-require-imports const { Server, Client } = require(objectsDbPath); // First create the server - await new Promise((resolve) => { + await new Promise(resolve => { this._objectsServer = new Server({ ...settings, connected: () => { @@ -205,17 +212,17 @@ class DBConnection extends events_1.default { }); }); // Then the client - await new Promise((resolve) => { + await new Promise(resolve => { this._objectsClient = new Client({ ...settings, connected: () => { - this._objectsClient.subscribe("*"); + this._objectsClient.subscribe('*'); resolve(); }, - change: this.emit.bind(this, "objectChange"), + change: this.emit.bind(this, 'objectChange'), }); }); - debug(" => done!"); + debug(' => done!'); } /** Creates the states DB and sets up listeners for it */ async createStatesDB() { @@ -225,7 +232,7 @@ class DBConnection extends events_1.default { const settings = { connection: { type: statesType, - host: "127.0.0.1", + host: '127.0.0.1', port: 19000, options: { auth_pass: null, @@ -235,16 +242,13 @@ class DBConnection extends events_1.default { logger: this.logger, }; const statesDbPath = require.resolve(`@iobroker/db-states-${statesType}`, { - paths: [ - path.join(this.testDir, "node_modules"), - path.join(this.testControllerDir, "node_modules"), - ], + paths: [path.join(this.testDir, 'node_modules'), path.join(this.testControllerDir, 'node_modules')], }); debug(` => states DB lib found at ${statesDbPath}`); - // eslint-disable-next-line @typescript-eslint/no-var-requires + // eslint-disable-next-line @typescript-eslint/no-require-imports const { Server, Client } = require(statesDbPath); // First create the server - await new Promise((resolve) => { + await new Promise(resolve => { this._statesServer = new Server({ ...settings, connected: () => { @@ -253,43 +257,41 @@ class DBConnection extends events_1.default { }); }); // Then the client - await new Promise((resolve) => { + await new Promise(resolve => { this._statesClient = new Client({ ...settings, connected: () => { - this._statesClient.subscribe("*"); + this._statesClient.subscribe('*'); resolve(); }, - change: this.emit.bind(this, "stateChange"), + change: this.emit.bind(this, 'stateChange'), }); }); - debug(" => done!"); + debug(' => done!'); } subscribeMessage(id) { if (!this._statesClient) { - throw new Error("States DB is not running"); + throw new Error('States DB is not running'); } return this._statesClient.subscribeMessage(id); } pushMessage(instanceId, msg, callback) { if (!this._statesClient) { - throw new Error("States DB is not running"); + throw new Error('States DB is not running'); } this._statesClient.pushMessage(instanceId, msg, callback); } - async getStateIDs(pattern = "*") { + async getStateIDs(pattern = '*') { if (!this._statesClient) { - throw new Error("States DB is not running"); + throw new Error('States DB is not running'); } - return (this._statesClient.getKeysAsync?.(pattern) || - this._statesClient.getKeys?.(pattern)); + return this._statesClient.getKeysAsync?.(pattern) || this._statesClient.getKeys?.(pattern); } - async getObjectIDs(pattern = "*") { + async getObjectIDs(pattern = '*') { if (!this._objectsClient) { - throw new Error("Objects DB is not running"); + throw new Error('Objects DB is not running'); } - return (this._objectsClient.getKeysAsync?.(pattern) || - this._objectsClient.getKeys?.(pattern)); + return this._objectsClient.getKeysAsync?.(pattern) || this._objectsClient.getKeys?.(pattern); } } exports.DBConnection = DBConnection; diff --git a/build/tests/integration/lib/harness.d.ts b/build/tests/integration/lib/harness.d.ts index e875bca3..8c4bd083 100644 --- a/build/tests/integration/lib/harness.d.ts +++ b/build/tests/integration/lib/harness.d.ts @@ -1,10 +1,10 @@ -import { ChildProcess } from "child_process"; -import { EventEmitter } from "events"; -import type { DBConnection } from "./dbConnection"; +import { ChildProcess } from 'child_process'; +import { EventEmitter } from 'events'; +import type { DBConnection } from './dbConnection'; export interface TestHarness { - on(event: "objectChange", handler: ioBroker.ObjectChangeHandler): this; - on(event: "stateChange", handler: ioBroker.StateChangeHandler): this; - on(event: "failed", handler: (codeOrSignal: number | string) => void): this; + on(event: 'objectChange', handler: ioBroker.ObjectChangeHandler): this; + on(event: 'stateChange', handler: ioBroker.StateChangeHandler): this; + on(event: 'failed', handler: (codeOrSignal: number | string) => void): this; } /** * The test harness capsules the execution of the JS-Controller and the adapter instance and monitors their status. @@ -60,7 +60,7 @@ export declare class TestHarness extends EventEmitter { * Updates the adapter config. The changes can be a subset of the target object */ changeAdapterConfig(adapterName: string, changes: Record): Promise; - getAdapterExecutionMode(): ioBroker.AdapterCommon["mode"]; + getAdapterExecutionMode(): ioBroker.AdapterCommon['mode']; /** Enables the sendTo method */ enableSendTo(): Promise; private sendToID; diff --git a/build/tests/integration/lib/harness.js b/build/tests/integration/lib/harness.js index 06a370db..f9e1c78e 100644 --- a/build/tests/integration/lib/harness.js +++ b/build/tests/integration/lib/harness.js @@ -15,20 +15,31 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( }) : function(o, v) { o["default"] = v; }); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TestHarness = void 0; -/* eslint-disable @typescript-eslint/no-inferrable-types */ +// @ts-expect-error no types const async_1 = require("alcalzone-shared/async"); +// @ts-expect-error no types const objects_1 = require("alcalzone-shared/objects"); const child_process_1 = require("child_process"); const debug_1 = __importDefault(require("debug")); @@ -36,9 +47,9 @@ const events_1 = require("events"); const path = __importStar(require("path")); const adapterTools_1 = require("../../../lib/adapterTools"); const tools_1 = require("./tools"); -const debug = (0, debug_1.default)("testing:integration:TestHarness"); +const debug = (0, debug_1.default)('testing:integration:TestHarness'); const isWindows = /^win/.test(process.platform); -const fromAdapterID = "system.adapter.test.0"; +const fromAdapterID = 'system.adapter.test.0'; /** * The test harness capsules the execution of the JS-Controller and the adapter instance and monitors their status. * Use it in every test to start a fresh adapter instance @@ -54,7 +65,7 @@ class TestHarness extends events_1.EventEmitter { this.testDir = testDir; this.dbConnection = dbConnection; this.sendToID = 1; - debug("Creating instance"); + debug('Creating instance'); this.adapterName = (0, adapterTools_1.getAdapterName)(this.adapterDir); this.appName = (0, adapterTools_1.getAppName)(adapterDir); this.testControllerDir = (0, tools_1.getTestControllerDir)(this.appName, testDir); @@ -64,24 +75,24 @@ class TestHarness extends events_1.EventEmitter { debug(` adapter: ${this.testAdapterDir}`); debug(` appName: ${this.appName}`); debug(` adapterName: ${this.adapterName}`); - dbConnection.on("objectChange", (id, obj) => { - this.emit("objectChange", id, obj); + dbConnection.on('objectChange', (id, obj) => { + this.emit('objectChange', id, obj); }); - dbConnection.on("stateChange", (id, state) => { - this.emit("stateChange", id, state); + dbConnection.on('stateChange', (id, state) => { + this.emit('stateChange', id, state); }); } /** Gives direct access to the Objects DB */ get objects() { if (!this.dbConnection.objectsClient) { - throw new Error("Objects DB is not running"); + throw new Error('Objects DB is not running'); } return this.dbConnection.objectsClient; } /** Gives direct access to the States DB */ get states() { if (!this.dbConnection.statesClient) { - throw new Error("States DB is not running"); + throw new Error('States DB is not running'); } return this.dbConnection.statesClient; } @@ -108,27 +119,28 @@ class TestHarness extends events_1.EventEmitter { if (!this.isControllerRunning()) return; if (!this.didAdapterStop()) { - debug("Stopping adapter instance..."); + debug('Stopping adapter instance...'); // Give the adapter time to stop (as long as configured in the io-package.json) let stopTimeout; try { - stopTimeout = (await this.dbConnection.getObject(`system.adapter.${this.adapterName}.0`)).common.stopTimeout; + stopTimeout = (await this.dbConnection.getObject(`system.adapter.${this.adapterName}.0`)) + .common.stopTimeout; stopTimeout += 1000; } catch { } - stopTimeout || (stopTimeout = 5000); // default 5s + stopTimeout ||= 5000; // default 5s debug(` => giving it ${stopTimeout}ms to terminate`); await Promise.race([this.stopAdapter(), (0, async_1.wait)(stopTimeout)]); if (this.isAdapterRunning()) { - debug("Adapter did not terminate, killing it"); - this._adapterProcess.kill("SIGKILL"); + debug('Adapter did not terminate, killing it'); + this._adapterProcess.kill('SIGKILL'); } else { - debug("Adapter terminated"); + debug('Adapter terminated'); } } else { - debug("Adapter failed to start - no need to terminate!"); + debug('Adapter failed to start - no need to terminate!'); } await this.dbConnection.stop(); } @@ -138,23 +150,23 @@ class TestHarness extends events_1.EventEmitter { */ async startAdapter(env = {}) { if (this.isAdapterRunning()) - throw new Error("The adapter is already running!"); + throw new Error('The adapter is already running!'); else if (this.didAdapterStop()) - throw new Error("This test harness has already been used. Please create a new one for each test!"); + throw new Error('This test harness has already been used. Please create a new one for each test!'); const mainFileAbsolute = await (0, adapterTools_1.locateAdapterMainFile)(this.testAdapterDir); const mainFileRelative = path.relative(this.testAdapterDir, mainFileAbsolute); const onClose = (code, signal) => { this._adapterProcess.removeAllListeners(); this._adapterExit = code != undefined ? code : signal; - this.emit("failed", this._adapterExit); + this.emit('failed', this._adapterExit); }; - this._adapterProcess = (0, child_process_1.spawn)(isWindows ? "node.exe" : "node", [mainFileRelative, "--console"], { + this._adapterProcess = (0, child_process_1.spawn)(isWindows ? 'node.exe' : 'node', [mainFileRelative, '--console'], { cwd: this.testAdapterDir, - stdio: ["inherit", "inherit", "inherit"], + stdio: ['inherit', 'inherit', 'inherit'], env: { ...process.env, ...env }, }) - .on("close", onClose) - .on("exit", onClose); + .on('close', onClose) + .on('exit', onClose); } /** * Starts the adapter in a separate process and resolves after it has started @@ -166,13 +178,13 @@ class TestHarness extends events_1.EventEmitter { const waitForStateId = waitForConnection ? `${this.adapterName}.0.info.connection` : `system.adapter.${this.adapterName}.0.alive`; - this.on("stateChange", async (id, state) => { + this.on('stateChange', async (id, state) => { if (id === waitForStateId && state && state.val === true) { resolve(); } }) - .on("failed", (code) => { - reject(new Error(`The adapter startup was interrupted unexpectedly with ${typeof code === "number" ? "code" : "signal"} ${code}`)); + .on('failed', code => { + reject(new Error(`The adapter startup was interrupted unexpectedly with ${typeof code === 'number' ? 'code' : 'signal'} ${code}`)); }) .startAdapter(env); }); @@ -196,24 +208,22 @@ class TestHarness extends events_1.EventEmitter { this._adapterProcess.removeAllListeners(); this._adapterExit = code != undefined ? code : signal; this._adapterProcess = undefined; - debug("Adapter process terminated:"); + debug('Adapter process terminated:'); debug(` Code: ${code}`); debug(` Signal: ${signal}`); resolve(); }; - this._adapterProcess.removeAllListeners() - .on("close", onClose) - .on("exit", onClose); + this._adapterProcess.removeAllListeners().on('close', onClose).on('exit', onClose); // Tell adapter to stop try { await this.dbConnection.setState(`system.adapter.${this.adapterName}.0.sigKill`, { val: -1, - from: "system.host.testing", + from: 'system.host.testing', }); } catch { // DB connection may be closed already, kill the process - this._adapterProcess?.kill("SIGTERM"); + this._adapterProcess?.kill('SIGTERM'); } }); } @@ -234,7 +244,7 @@ class TestHarness extends events_1.EventEmitter { /** Enables the sendTo method */ async enableSendTo() { await this.dbConnection.setObject(fromAdapterID, { - type: "instance", + type: 'instance', common: {}, native: {}, instanceObjects: [], @@ -247,10 +257,10 @@ class TestHarness extends events_1.EventEmitter { const stateChangedHandler = (id, state) => { if (id === `messagebox.${fromAdapterID}`) { callback(state.message); - this.removeListener("stateChange", stateChangedHandler); + this.removeListener('stateChange', stateChangedHandler); } }; - this.addListener("stateChange", stateChangedHandler); + this.addListener('stateChange', stateChangedHandler); this.dbConnection.pushMessage(`system.adapter.${target}`, { command: command, message: message, @@ -261,7 +271,7 @@ class TestHarness extends events_1.EventEmitter { ack: false, time: Date.now(), }, - }, (err, id) => console.log("published message " + id)); + }, (err, id) => console.log('published message ' + id)); } } exports.TestHarness = TestHarness; diff --git a/build/tests/integration/lib/logger.js b/build/tests/integration/lib/logger.js index 20ffcb8a..d6af6395 100644 --- a/build/tests/integration/lib/logger.js +++ b/build/tests/integration/lib/logger.js @@ -10,8 +10,7 @@ var LoglevelOrder; LoglevelOrder[LoglevelOrder["silly"] = 4] = "silly"; })(LoglevelOrder || (LoglevelOrder = {})); function createLogger(loglevel) { - const loglevelNumeric = LoglevelOrder[loglevel ?? "debug"] ?? LoglevelOrder.debug; - // eslint-disable-next-line @typescript-eslint/no-empty-function + const loglevelNumeric = LoglevelOrder[loglevel ?? 'debug'] ?? LoglevelOrder.debug; const ignore = () => { }; return { error: loglevelNumeric >= LoglevelOrder.error ? console.error : ignore, diff --git a/build/tests/integration/lib/tools.js b/build/tests/integration/lib/tools.js index ebac7f03..25923a49 100644 --- a/build/tests/integration/lib/tools.js +++ b/build/tests/integration/lib/tools.js @@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( }) : function(o, v) { o["default"] = v; }); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); Object.defineProperty(exports, "__esModule", { value: true }); exports.getTestControllerDir = getTestControllerDir; exports.getTestDataDir = getTestDataDir; @@ -36,7 +46,7 @@ const adapterTools_1 = require("../../../lib/adapterTools"); * @param testDir The directory the integration tests are executed in */ function getTestControllerDir(appName, testDir) { - return path.resolve(testDir, "node_modules", `${appName}.js-controller`); + return path.resolve(testDir, 'node_modules', `${appName}.js-controller`); } /** * Locates the directory where JS-Controller stores its data for integration tests @@ -52,7 +62,7 @@ function getTestDataDir(appName, testDir) { * @param testDir The directory the integration tests are executed in */ function getTestLogDir(appName, testDir) { - return path.resolve(testDir, "log"); + return path.resolve(testDir, 'log'); } /** * Locates the directory where JS-Controller stores its sqlite db during integration tests @@ -60,7 +70,7 @@ function getTestLogDir(appName, testDir) { * @param testDir The directory the integration tests are executed in */ function getTestDBDir(appName, testDir) { - return path.resolve(getTestDataDir(appName, testDir), "sqlite"); + return path.resolve(getTestDataDir(appName, testDir), 'sqlite'); } /** * Locates the directory where the adapter will be be stored for integration tests @@ -69,5 +79,5 @@ function getTestDBDir(appName, testDir) { */ function getTestAdapterDir(adapterDir, testDir) { const adapterName = (0, adapterTools_1.getAdapterFullName)(adapterDir); - return path.resolve(testDir, "node_modules", adapterName); + return path.resolve(testDir, 'node_modules', adapterName); } diff --git a/build/tests/packageFiles/index.js b/build/tests/packageFiles/index.js index c257bfde..f0c06286 100644 --- a/build/tests/packageFiles/index.js +++ b/build/tests/packageFiles/index.js @@ -15,16 +15,26 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( }) : function(o, v) { o["default"] = v; }); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); Object.defineProperty(exports, "__esModule", { value: true }); exports.validatePackageFiles = validatePackageFiles; -/* eslint-disable @typescript-eslint/no-var-requires */ +// @ts-expect-error no types const typeguards_1 = require("alcalzone-shared/typeguards"); const chai_1 = require("chai"); const fs = __importStar(require("fs")); @@ -34,30 +44,30 @@ const path = __importStar(require("path")); * This is meant to be executed in a mocha context. */ function validatePackageFiles(adapterDir) { - const packageJsonPath = path.join(adapterDir, "package.json"); - const ioPackageJsonPath = path.join(adapterDir, "io-package.json"); + const packageJsonPath = path.join(adapterDir, 'package.json'); + const ioPackageJsonPath = path.join(adapterDir, 'io-package.json'); // This allows us to skip tests that require valid JSON files const invalidFiles = { - "package.json": false, - "io-package.json": false, + 'package.json': false, + 'io-package.json': false, }; function skipIfInvalid(...filenames) { - if (filenames.some((f) => invalidFiles[f])) + if (filenames.some(f => invalidFiles[f])) return this.skip(); } function markAsInvalid(filename) { - if (this.currentTest.state === "failed" && - invalidFiles[filename] === false) { + if (this.currentTest.state === 'failed' && invalidFiles[filename] === false) { invalidFiles[filename] = true; console.error(`Skipping subsequent tests including "${filename}" because they require valid JSON files!`); } } /** Ensures that a given property exists on the target object */ function ensurePropertyExists(propertyPath, targetObj) { - const propertyParts = propertyPath.split("."); + const propertyParts = propertyPath.split('.'); it(`The property "${propertyPath}" exists`, () => { let prev = targetObj; for (const part of propertyParts) { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions (0, chai_1.expect)(prev[part]).to.not.be.undefined; prev = prev[part]; } @@ -65,7 +75,7 @@ function validatePackageFiles(adapterDir) { } describe(`Validate the package files`, () => { describe(`Ensure they are readable`, () => { - for (const filename of ["package.json", "io-package.json"]) { + for (const filename of ['package.json', 'io-package.json']) { const packagePath = path.join(adapterDir, filename); describe(`${filename}`, () => { afterEach(function () { @@ -74,61 +84,66 @@ function validatePackageFiles(adapterDir) { beforeEach(function () { skipIfInvalid.call(this, filename); }); - it("exists", () => { + it('exists', () => { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions (0, chai_1.expect)(fs.existsSync(packagePath), `${filename} is missing in the adapter dir. Please create it!`).to.be.true; }); - it("contains valid JSON", () => { + it('contains valid JSON', () => { (0, chai_1.expect)(() => { - JSON.parse(fs.readFileSync(packagePath, "utf8")); + JSON.parse(fs.readFileSync(packagePath, 'utf8')); }, `${filename} contains invalid JSON!`).not.to.throw(); }); - it("is an object", () => { - (0, chai_1.expect)(require(packagePath), `${filename} must contain an object!`).to.be.an("object"); + it('is an object', () => { + (0, chai_1.expect)( + // eslint-disable-next-line @typescript-eslint/no-require-imports + require(packagePath), `${filename} must contain an object!`).to.be.an('object'); }); }); } }); describe(`Check contents of package.json`, () => { beforeEach(function () { - skipIfInvalid.call(this, "package.json"); + skipIfInvalid.call(this, 'package.json'); }); + // eslint-disable-next-line @typescript-eslint/no-require-imports const packageContent = require(packageJsonPath); + // eslint-disable-next-line @typescript-eslint/no-require-imports const iopackContent = require(ioPackageJsonPath); const requiredProperties = [ - "name", - "version", - "description", - "author", - "license", - "repository", - "repository.type", + 'name', + 'version', + 'description', + 'author', + 'license', + 'repository', + 'repository.type', ]; - requiredProperties.forEach((prop) => ensurePropertyExists(prop, packageContent)); - it("The package name is correct", () => { + requiredProperties.forEach(prop => ensurePropertyExists(prop, packageContent)); + it('The package name is correct', () => { let name = packageContent.name; (0, chai_1.expect)(name).to.match(/^iobroker\./, `The npm package name must start with lowercase "iobroker."!`); - name = name.replace(/^iobroker\./, ""); + name = name.replace(/^iobroker\./, ''); (0, chai_1.expect)(name).to.match(/[a-z0-9_\-]+/, `The adapter name must only contain lowercase letters, numbers, "-" and "_"!`); (0, chai_1.expect)(name).to.match(/^[a-z]/, `The adapter name must start with a letter!`); (0, chai_1.expect)(name).to.match(/[a-z0-9]$/, `The adapter name must end with a letter or number!`); }); if (!iopackContent.common.onlyWWW) { it(`property main is defined for non onlyWWW adapters`, () => { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions (0, chai_1.expect)(packageContent.main).to.not.be.undefined; }); } it(`The repository type is "git"`, () => { - (0, chai_1.expect)(packageContent.repository.type).to.equal("git"); + (0, chai_1.expect)(packageContent.repository.type).to.equal('git'); }); - it("npm is not listed as a dependency", () => { + it('npm is not listed as a dependency', () => { for (const depType of [ - "dependencies", - "devDependencies", - "optionalDependencies", - "peerDependencies", + 'dependencies', + 'devDependencies', + 'optionalDependencies', + 'peerDependencies', ]) { - if ((0, typeguards_1.isObject)(packageContent[depType]) && - "npm" in packageContent[depType]) { + if ((0, typeguards_1.isObject)(packageContent[depType]) && 'npm' in packageContent[depType]) { throw new chai_1.AssertionError(`npm must not be listed in ${depType}, found "${packageContent[depType].npm}"!`); } } @@ -136,29 +151,30 @@ function validatePackageFiles(adapterDir) { }); describe(`Check contents of io-package.json`, () => { beforeEach(function () { - skipIfInvalid.call(this, "io-package.json"); + skipIfInvalid.call(this, 'io-package.json'); }); + // eslint-disable-next-line @typescript-eslint/no-require-imports const iopackContent = require(ioPackageJsonPath); const requiredProperties = [ - "common.name", - "common.titleLang", - "common.version", - "common.news", - "common.desc", - "common.icon", - "common.extIcon", - "common.type", - "common.authors", - "native", + 'common.name', + 'common.titleLang', + 'common.version', + 'common.news', + 'common.desc', + 'common.icon', + 'common.extIcon', + 'common.type', + 'common.authors', + 'native', ]; - requiredProperties.forEach((prop) => ensurePropertyExists(prop, iopackContent)); + requiredProperties.forEach(prop => ensurePropertyExists(prop, iopackContent)); it(`The title does not contain "adapter" or "iobroker"`, () => { if (!iopackContent.title) return; (0, chai_1.expect)(iopackContent.common.title).not.to.match(/iobroker|adapter/i); }); it(`titleLang is an object to support multiple languages`, () => { - (0, chai_1.expect)(iopackContent.common.titleLang).to.be.an("object"); + (0, chai_1.expect)(iopackContent.common.titleLang).to.be.an('object'); }); it(`titleLang does not contain "adapter" or "iobroker"`, () => { for (const title of Object.values(iopackContent.common.titleLang)) { @@ -166,33 +182,44 @@ function validatePackageFiles(adapterDir) { } }); it(`The description is an object to support multiple languages`, () => { - (0, chai_1.expect)(iopackContent.common.desc).to.be.an("object"); + (0, chai_1.expect)(iopackContent.common.desc).to.be.an('object'); }); it(`common.authors is an array that is not empty`, () => { const authors = iopackContent.common.authors; + // eslint-disable-next-line @typescript-eslint/no-unused-expressions (0, chai_1.expect)((0, typeguards_1.isArray)(authors)).to.be.true; (0, chai_1.expect)(authors.length).to.be.at.least(1); }); it(`common.news is an object that contains maximum 20 entries`, () => { const news = iopackContent.common.news; + // eslint-disable-next-line @typescript-eslint/no-unused-expressions (0, chai_1.expect)((0, typeguards_1.isObject)(news)).to.be.true; (0, chai_1.expect)(Object.keys(news).length).to.be.at.most(20); }); if (iopackContent.common.licenseInformation) { it(`if common.licenseInformation exists, it is an object with required properties`, () => { - (0, chai_1.expect)(iopackContent.common.licenseInformation).to.be.an("object"); - (0, chai_1.expect)(iopackContent.common.licenseInformation.type).to.be.oneOf(["free", "commercial", "paid", "limited"]); - if (iopackContent.common.licenseInformation.type !== "free") { - (0, chai_1.expect)(iopackContent.common.licenseInformation.link, "License link is missing").to.not.be.undefined; + (0, chai_1.expect)(iopackContent.common.licenseInformation).to.be.an('object'); + (0, chai_1.expect)(iopackContent.common.licenseInformation.type).to.be.oneOf([ + 'free', + 'commercial', + 'paid', + 'limited', + ]); + if (iopackContent.common.licenseInformation.type !== 'free') { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + (0, chai_1.expect)(iopackContent.common.licenseInformation.link, 'License link is missing').to.not.be + .undefined; } }); it(`common.license should not exist together with common.licenseInformation`, () => { - (0, chai_1.expect)(iopackContent.common.license, "common.license must be removed").to.be.undefined; + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + (0, chai_1.expect)(iopackContent.common.license, 'common.license must be removed').to.be.undefined; }); } else { it(`common.license must exist without common.licenseInformation`, () => { - (0, chai_1.expect)(iopackContent.common.license, "common.licenseInformation (preferred) or common.license (deprecated) must exist").to.not.be.undefined; + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + (0, chai_1.expect)(iopackContent.common.license, 'common.licenseInformation (preferred) or common.license (deprecated) must exist').to.not.be.undefined; }); } if (iopackContent.common.tier != undefined) { @@ -203,31 +230,35 @@ function validatePackageFiles(adapterDir) { } // If the adapter has a configuration page, check that a supported admin UI is used const hasNoConfigPage = iopackContent.common.noConfig === true || - iopackContent.common.noConfig === "true" || - iopackContent.common.adminUI?.config === "none"; + iopackContent.common.noConfig === 'true' || + iopackContent.common.adminUI?.config === 'none'; if (!hasNoConfigPage) { - it("The adapter uses a supported admin UI", () => { + it('The adapter uses a supported admin UI', () => { const hasSupportedUI = !!iopackContent.common.materialize || - iopackContent.common.adminUI?.config === "html" || - iopackContent.common.adminUI?.config === "json" || - iopackContent.common.adminUI?.config === "materialize"; - (0, chai_1.expect)(hasSupportedUI, "Unsupported Admin UI, must be html, materialize or JSON config!").to.be.true; + iopackContent.common.adminUI?.config === 'html' || + iopackContent.common.adminUI?.config === 'json' || + iopackContent.common.adminUI?.config === 'materialize'; + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + (0, chai_1.expect)(hasSupportedUI, 'Unsupported Admin UI, must be html, materialize or JSON config!').to.be + .true; }); } }); describe(`Compare contents of package.json and io-package.json`, () => { beforeEach(function () { - skipIfInvalid.call(this, "package.json", "io-package.json"); + skipIfInvalid.call(this, 'package.json', 'io-package.json'); }); + // eslint-disable-next-line @typescript-eslint/no-require-imports const packageContent = require(packageJsonPath); + // eslint-disable-next-line @typescript-eslint/no-require-imports const iopackContent = require(ioPackageJsonPath); - it("The name matches", () => { - (0, chai_1.expect)("iobroker." + iopackContent.common.name).to.equal(packageContent.name); + it('The name matches', () => { + (0, chai_1.expect)('iobroker.' + iopackContent.common.name).to.equal(packageContent.name); }); - it("The version matches", () => { + it('The version matches', () => { (0, chai_1.expect)(iopackContent.common.version).to.equal(packageContent.version); }); - it("The license matches", () => { + it('The license matches', () => { if (iopackContent.common.licenseInformation) { (0, chai_1.expect)(iopackContent.common.licenseInformation.license).to.equal(packageContent.license); } diff --git a/build/tests/unit/harness/createMocks.d.ts b/build/tests/unit/harness/createMocks.d.ts index 9026e983..9c228c3a 100644 --- a/build/tests/unit/harness/createMocks.d.ts +++ b/build/tests/unit/harness/createMocks.d.ts @@ -1,9 +1,9 @@ -import { MockDatabase } from "../mocks/mockDatabase"; +import { MockDatabase } from '../mocks/mockDatabase'; /** * Creates a new set of mocks, including a mock database and a mock adapter. * To test the startup of an adapter, use `startMockAdapter` instead. */ export declare function createMocks(adapterOptions: Partial): { database: MockDatabase; - adapter: import("../mocks/mockAdapter").MockAdapter; + adapter: any; }; diff --git a/build/tests/unit/index.js b/build/tests/unit/index.js index f9563d6d..d8240357 100644 --- a/build/tests/unit/index.js +++ b/build/tests/unit/index.js @@ -9,12 +9,12 @@ exports.testAdapterWithMocks = testAdapterWithMocks; function testAdapterWithMocks(_adapterDir, options = {}) { describe(`Unit tests`, async () => { // Call the user's tests - if (typeof options.defineAdditionalTests === "function") { + if (typeof options.defineAdditionalTests === 'function') { options.defineAdditionalTests(); } else { - it("DEPRECATED!", () => { - console.warn("\u001b[33mUnit tests for adapter startup are deprecated!"); + it('DEPRECATED!', () => { + console.warn('\u001b[33mUnit tests for adapter startup are deprecated!'); console.warn(`If you do not define your own tests, you can remove the "test:unit" script`); console.warn(`from package.json and from your Travis/Github Actions workflow.\u001b[0m`); }); diff --git a/build/tests/unit/mocks/mockAdapter.d.ts b/build/tests/unit/mocks/mockAdapter.d.ts index 26948fef..7e5eb0e4 100644 --- a/build/tests/unit/mocks/mockAdapter.d.ts +++ b/build/tests/unit/mocks/mockAdapter.d.ts @@ -1,6 +1,6 @@ -import type { MockDatabase } from "./mockDatabase"; -import { MockLogger } from "./mockLogger"; -import { Mock } from "./tools"; +import type { MockDatabase } from './mockDatabase'; +import { MockLogger } from './mockLogger'; +import { Mock } from './tools'; export type MockAdapter = Mock & { readyHandler: ioBroker.ReadyHandler | undefined; objectChangeHandler: ioBroker.ObjectChangeHandler | undefined; diff --git a/build/tests/unit/mocks/mockAdapter.js b/build/tests/unit/mocks/mockAdapter.js index 4b399f41..55afba82 100644 --- a/build/tests/unit/mocks/mockAdapter.js +++ b/build/tests/unit/mocks/mockAdapter.js @@ -1,6 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createAdapterMock = createAdapterMock; +// @ts-expect-error no types const objects_1 = require("alcalzone-shared/objects"); const sinon_1 = require("sinon"); const mockLogger_1 = require("./mockLogger"); @@ -8,79 +9,79 @@ const tools_1 = require("./tools"); // Define here which methods were implemented manually, so we can hook them up with a real stub // The value describes if and how the async version of the callback is constructed const implementedMethods = { - getObject: "normal", - setObject: "normal", - setObjectNotExists: "normal", - extendObject: "normal", - getForeignObject: "normal", - getForeignObjects: "normal", - setForeignObject: "normal", - setForeignObjectNotExists: "normal", - extendForeignObject: "normal", - getState: "normal", - getStates: "normal", - setState: "normal", - setStateChanged: "normal", - delState: "normal", - getForeignState: "normal", - setForeignState: "normal", - setForeignStateChanged: "normal", - subscribeStates: "normal", - subscribeForeignStates: "normal", - subscribeObjects: "normal", - subscribeForeignObjects: "normal", - getAdapterObjects: "no error", - getObjectView: "normal", - getObjectList: "normal", - on: "none", - removeListener: "none", - removeAllListeners: "none", - terminate: "none", - getPort: "no error", - checkPassword: "no error", - setPassword: "normal", - checkGroup: "no error", - calculatePermissions: "no error", - getCertificates: "normal", - sendTo: "no error", - sendToHost: "no error", - getHistory: "normal", + getObject: 'normal', + setObject: 'normal', + setObjectNotExists: 'normal', + extendObject: 'normal', + getForeignObject: 'normal', + getForeignObjects: 'normal', + setForeignObject: 'normal', + setForeignObjectNotExists: 'normal', + extendForeignObject: 'normal', + getState: 'normal', + getStates: 'normal', + setState: 'normal', + setStateChanged: 'normal', + delState: 'normal', + getForeignState: 'normal', + setForeignState: 'normal', + setForeignStateChanged: 'normal', + subscribeStates: 'normal', + subscribeForeignStates: 'normal', + subscribeObjects: 'normal', + subscribeForeignObjects: 'normal', + getAdapterObjects: 'no error', + getObjectView: 'normal', + getObjectList: 'normal', + on: 'none', + removeListener: 'none', + removeAllListeners: 'none', + terminate: 'none', + getPort: 'no error', + checkPassword: 'no error', + setPassword: 'normal', + checkGroup: 'no error', + calculatePermissions: 'no error', + getCertificates: 'normal', + sendTo: 'no error', + sendToHost: 'no error', + getHistory: 'normal', // @ts-expect-error This method was deprecated - setBinaryState: "normal", - getBinaryState: "normal", - getEnum: "normal", - getEnums: "normal", - addChannelToEnum: "normal", - deleteChannelFromEnum: "normal", - addStateToEnum: "normal", - deleteStateFromEnum: "normal", - createDevice: "normal", - deleteDevice: "normal", - createChannel: "normal", - deleteChannel: "normal", - createState: "normal", - deleteState: "normal", - getDevices: "normal", - getChannelsOf: "normal", - getStatesOf: "normal", - readDir: "normal", - mkDir: "normal", - readFile: "normal", - writeFile: "normal", - delFile: "normal", - unlink: "normal", - rename: "normal", - chmodFile: "normal", + setBinaryState: 'normal', + getBinaryState: 'normal', + getEnum: 'normal', + getEnums: 'normal', + addChannelToEnum: 'normal', + deleteChannelFromEnum: 'normal', + addStateToEnum: 'normal', + deleteStateFromEnum: 'normal', + createDevice: 'normal', + deleteDevice: 'normal', + createChannel: 'normal', + deleteChannel: 'normal', + createState: 'normal', + deleteState: 'normal', + getDevices: 'normal', + getChannelsOf: 'normal', + getStatesOf: 'normal', + readDir: 'normal', + mkDir: 'normal', + readFile: 'normal', + writeFile: 'normal', + delFile: 'normal', + unlink: 'normal', + rename: 'normal', + chmodFile: 'normal', }; function getCallback(...args) { const lastArg = args[args.length - 1]; - if (typeof lastArg === "function") + if (typeof lastArg === 'function') return lastArg; } /** Stub implementation which can be promisified */ const asyncEnabledStub = ((...args) => { const callback = getCallback(...args); - if (typeof callback === "function") + if (typeof callback === 'function') callback(); }); /** @@ -90,18 +91,18 @@ function createAdapterMock(db, options = {}) { // In order to support ES6-style adapters with inheritance, we need to work on the instance directly const ret = this || {}; Object.assign(ret, { - name: options.name || "test", - host: "testhost", + name: options.name || 'test', + host: 'testhost', instance: options.instance || 0, - namespace: `${options.name || "test"}.${options.instance || 0}`, + namespace: `${options.name || 'test'}.${options.instance || 0}`, config: options.config || {}, common: {}, systemConfig: null, - adapterDir: "", + adapterDir: '', ioPack: {}, pack: {}, log: (0, mockLogger_1.createLoggerMock)(), - version: "any", + version: 'any', connected: true, getPort: asyncEnabledStub, stop: (0, sinon_1.stub)(), @@ -115,14 +116,14 @@ function createAdapterMock(db, options = {}) { idToDCS: (0, sinon_1.stub)(), getObject: ((id, ...args) => { if (!id.startsWith(ret.namespace)) - id = ret.namespace + "." + id; + id = ret.namespace + '.' + id; const callback = getCallback(...args); if (callback) callback(null, db.getObject(id)); }), setObject: ((id, obj, ...args) => { if (!id.startsWith(ret.namespace)) - id = ret.namespace + "." + id; + id = ret.namespace + '.' + id; obj._id = id; db.publishObject(obj); const callback = getCallback(...args); @@ -131,7 +132,7 @@ function createAdapterMock(db, options = {}) { }), setObjectNotExists: ((id, obj, ...args) => { if (!id.startsWith(ret.namespace)) - id = ret.namespace + "." + id; + id = ret.namespace + '.' + id; const callback = getCallback(...args); if (db.hasObject(id)) { if (callback) @@ -145,19 +146,19 @@ function createAdapterMock(db, options = {}) { callback(db.getObjects(`${ret.namespace}.*`)); }), getObjectView: ((design, search, { startkey, endkey }, ...args) => { - if (design !== "system") { - throw new Error("If you want to use a custom design for getObjectView, you need to mock it yourself!"); + if (design !== 'system') { + throw new Error('If you want to use a custom design for getObjectView, you need to mock it yourself!'); } const callback = getCallback(...args); - if (typeof callback === "function") { - let objects = (0, objects_1.values)(db.getObjects("*")); - objects = objects.filter((obj) => obj.type === search); + if (typeof callback === 'function') { + let objects = (0, objects_1.values)(db.getObjects('*')); + objects = objects.filter(obj => obj.type === search); if (startkey) - objects = objects.filter((obj) => obj._id >= startkey); + objects = objects.filter(obj => obj._id >= startkey); if (endkey) - objects = objects.filter((obj) => obj._id <= endkey); + objects = objects.filter(obj => obj._id <= endkey); callback(null, { - rows: objects.map((obj) => ({ + rows: objects.map(obj => ({ id: obj._id, value: obj, })), @@ -166,16 +167,19 @@ function createAdapterMock(db, options = {}) { }), getObjectList: (({ startkey, endkey, include_docs, }, ...args) => { const callback = getCallback(...args); - if (typeof callback === "function") { - let objects = (0, objects_1.values)(db.getObjects("*")); - if (startkey) - objects = objects.filter((obj) => obj._id >= startkey); - if (endkey) - objects = objects.filter((obj) => obj._id <= endkey); - if (!include_docs) - objects = objects.filter((obj) => !obj._id.startsWith("_")); + if (typeof callback === 'function') { + let objects = (0, objects_1.values)(db.getObjects('*')); + if (startkey) { + objects = objects.filter(obj => obj._id >= startkey); + } + if (endkey) { + objects = objects.filter(obj => obj._id <= endkey); + } + if (!include_docs) { + objects = objects.filter(obj => !obj._id.startsWith('_')); + } callback(null, { - rows: objects.map((obj) => ({ + rows: objects.map(obj => ({ id: obj._id, value: obj, doc: obj, @@ -185,7 +189,7 @@ function createAdapterMock(db, options = {}) { }), extendObject: ((id, obj, ...args) => { if (!id.startsWith(ret.namespace)) - id = ret.namespace + "." + id; + id = ret.namespace + '.' + id; const existing = db.getObject(id) || {}; const target = (0, objects_1.extend)({}, existing, obj); target._id = id; @@ -196,7 +200,7 @@ function createAdapterMock(db, options = {}) { }), delObject: ((id, ...args) => { if (!id.startsWith(ret.namespace)) - id = ret.namespace + "." + id; + id = ret.namespace + '.' + id; db.deleteObject(id); const callback = getCallback(...args); if (callback) @@ -208,9 +212,7 @@ function createAdapterMock(db, options = {}) { callback(null, db.getObject(id)); }), getForeignObjects: ((pattern, ...args) => { - const type = typeof args[0] === "string" - ? args[0] - : undefined; + const type = typeof args[0] === 'string' ? args[0] : undefined; const callback = getCallback(...args); if (callback) callback(null, db.getObjects(pattern, type)); @@ -251,14 +253,14 @@ function createAdapterMock(db, options = {}) { setState: ((id, state, ...args) => { const callback = getCallback(...args); if (!id.startsWith(ret.namespace)) - id = ret.namespace + "." + id; + id = ret.namespace + '.' + id; let ack; - if (state != null && typeof state === "object") { + if (state != null && typeof state === 'object') { ack = !!state.ack; state = state.val; } else { - ack = typeof args[0] === "boolean" ? args[0] : false; + ack = typeof args[0] === 'boolean' ? args[0] : false; } db.publishState(id, { val: state, ack }); if (callback) @@ -267,15 +269,15 @@ function createAdapterMock(db, options = {}) { setStateChanged: ((id, state, ...args) => { const callback = getCallback(...args); let ack; - if (state != null && typeof state === "object") { + if (state != null && typeof state === 'object') { ack = !!state.ack; state = state.val; } else { - ack = typeof args[0] === "boolean" ? args[0] : false; + ack = typeof args[0] === 'boolean' ? args[0] : false; } if (!id.startsWith(ret.namespace)) - id = ret.namespace + "." + id; + id = ret.namespace + '.' + id; if (!db.hasState(id) || db.getState(id).val !== state) { db.publishState(id, { val: state, ack }); } @@ -285,12 +287,12 @@ function createAdapterMock(db, options = {}) { setForeignState: ((id, state, ...args) => { const callback = getCallback(...args); let ack; - if (state != null && typeof state === "object") { + if (state != null && typeof state === 'object') { ack = !!state.ack; state = state.val; } else { - ack = typeof args[0] === "boolean" ? args[0] : false; + ack = typeof args[0] === 'boolean' ? args[0] : false; } db.publishState(id, { val: state, ack }); if (callback) @@ -299,12 +301,12 @@ function createAdapterMock(db, options = {}) { setForeignStateChanged: ((id, state, ...args) => { const callback = getCallback(...args); let ack; - if (state != null && typeof state === "object") { + if (state != null && typeof state === 'object') { ack = !!state.ack; state = state.val; } else { - ack = typeof args[0] === "boolean" ? args[0] : false; + ack = typeof args[0] === 'boolean' ? args[0] : false; } if (!db.hasState(id) || db.getState(id).val !== state) { db.publishState(id, { val: state, ack }); @@ -314,7 +316,7 @@ function createAdapterMock(db, options = {}) { }), getState: ((id, ...args) => { if (!id.startsWith(ret.namespace)) - id = ret.namespace + "." + id; + id = ret.namespace + '.' + id; const callback = getCallback(...args); if (callback) callback(null, db.getState(id)); @@ -326,7 +328,7 @@ function createAdapterMock(db, options = {}) { }), getStates: ((pattern, ...args) => { if (!pattern.startsWith(ret.namespace)) - pattern = ret.namespace + "." + pattern; + pattern = ret.namespace + '.' + pattern; const callback = getCallback(...args); if (callback) callback(null, db.getStates(pattern)); @@ -338,7 +340,7 @@ function createAdapterMock(db, options = {}) { }), delState: ((id, ...args) => { if (!id.startsWith(ret.namespace)) - id = ret.namespace + "." + id; + id = ret.namespace + '.' + id; db.deleteState(id); const callback = getCallback(...args); if (callback) @@ -388,16 +390,16 @@ function createAdapterMock(db, options = {}) { formatValue: (0, sinon_1.stub)(), formatDate: (0, sinon_1.stub)(), terminate: ((reason, exitCode) => { - if (typeof reason === "number") { + if (typeof reason === 'number') { // Only the exit code was passed exitCode = reason; reason = undefined; } - const errorMessage = `Adapter.terminate was called${typeof exitCode === "number" ? ` (exit code ${exitCode})` : ""}: ${reason ? reason : "Without reason"}`; + const errorMessage = `Adapter.terminate was called${typeof exitCode === 'number' ? ` (exit code ${exitCode})` : ''}: ${reason ? reason : 'Without reason'}`; // Terminates execution by const err = new Error(errorMessage); // @ts-expect-error I'm too lazy to add terminateReason to the error type - err.terminateReason = reason || "no reason given!"; + err.terminateReason = reason || 'no reason given!'; throw err; }), supportsFeature: (0, sinon_1.stub)(), @@ -407,19 +409,19 @@ function createAdapterMock(db, options = {}) { on: ((event, handler) => { // Remember the event handlers so we can call them on demand switch (event) { - case "ready": + case 'ready': ret.readyHandler = handler; break; - case "message": + case 'message': ret.messageHandler = handler; break; - case "objectChange": + case 'objectChange': ret.objectChangeHandler = handler; break; - case "stateChange": + case 'stateChange': ret.stateChangeHandler = handler; break; - case "unload": + case 'unload': ret.unloadHandler = handler; break; } @@ -428,38 +430,38 @@ function createAdapterMock(db, options = {}) { removeListener: ((event, _listener) => { // TODO This is not entirely correct switch (event) { - case "ready": + case 'ready': ret.readyHandler = undefined; break; - case "message": + case 'message': ret.messageHandler = undefined; break; - case "objectChange": + case 'objectChange': ret.objectChangeHandler = undefined; break; - case "stateChange": + case 'stateChange': ret.stateChangeHandler = undefined; break; - case "unload": + case 'unload': ret.unloadHandler = undefined; break; } return ret; }), removeAllListeners: ((event) => { - if (!event || event === "ready") { + if (!event || event === 'ready') { ret.readyHandler = undefined; } - if (!event || event === "message") { + if (!event || event === 'message') { ret.messageHandler = undefined; } - if (!event || event === "objectChange") { + if (!event || event === 'objectChange') { ret.objectChangeHandler = undefined; } - if (!event || event === "stateChange") { + if (!event || event === 'stateChange') { ret.stateChangeHandler = undefined; } - if (!event || event === "unload") { + if (!event || event === 'unload') { ret.unloadHandler = undefined; } return ret; @@ -480,10 +482,7 @@ function createAdapterMock(db, options = {}) { ret.resetMockBehavior(); }, }); - (0, tools_1.stubAndPromisifyImplementedMethods)(ret, implementedMethods, [ - "getObjectView", - "getObjectList", - ]); + (0, tools_1.stubAndPromisifyImplementedMethods)(ret, implementedMethods, ['getObjectView', 'getObjectList']); // Access the options object directly, so we can react to later changes Object.defineProperties(ret, { readyHandler: { diff --git a/build/tests/unit/mocks/mockAdapterCore.d.ts b/build/tests/unit/mocks/mockAdapterCore.d.ts index f26078c3..10d27ccd 100644 --- a/build/tests/unit/mocks/mockAdapterCore.d.ts +++ b/build/tests/unit/mocks/mockAdapterCore.d.ts @@ -1,5 +1,5 @@ -import { MockAdapter } from "./mockAdapter"; -import type { MockDatabase } from "./mockDatabase"; +import { MockAdapter } from './mockAdapter'; +import type { MockDatabase } from './mockDatabase'; interface MockAdapterConstructor { new (nameOrOptions: string | ioBroker.AdapterOptions): MockAdapter; (nameOrOptions: string | ioBroker.AdapterOptions): MockAdapter; diff --git a/build/tests/unit/mocks/mockAdapterCore.js b/build/tests/unit/mocks/mockAdapterCore.js index cc536897..b54d62b5 100644 --- a/build/tests/unit/mocks/mockAdapterCore.js +++ b/build/tests/unit/mocks/mockAdapterCore.js @@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( }) : function(o, v) { o["default"] = v; }); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); Object.defineProperty(exports, "__esModule", { value: true }); exports.mockAdapterCore = mockAdapterCore; const os = __importStar(require("os")); @@ -33,7 +43,7 @@ function mockAdapterCore(database, options = {}) { * The root directory of JS-Controller * If this has to exist in the test, the user/tester has to take care of it! */ - const controllerDir = path.join(options.adapterDir || "", "..", "iobroker.js-controller"); + const controllerDir = path.join(options.adapterDir || '', '..', 'iobroker.js-controller'); const dataDir = path.join(os.tmpdir(), `test-iobroker-data`); /** * The test location for iobroker-data @@ -57,11 +67,9 @@ function mockAdapterCore(database, options = {}) { // This needs to be a class with the correct `this` context or the ES6 tests won't work if (!(this instanceof AdapterConstructor)) return new AdapterConstructor(nameOrOptions); - const createAdapterMockOptions = typeof nameOrOptions === "string" - ? { name: nameOrOptions } - : nameOrOptions; + const createAdapterMockOptions = typeof nameOrOptions === 'string' ? { name: nameOrOptions } : nameOrOptions; mockAdapter_1.createAdapterMock.bind(this)(database, createAdapterMockOptions); - if (typeof options.onAdapterCreated === "function") + if (typeof options.onAdapterCreated === 'function') options.onAdapterCreated(this); return this; }; diff --git a/build/tests/unit/mocks/mockDatabase.d.ts b/build/tests/unit/mocks/mockDatabase.d.ts index 2903f395..07ad459e 100644 --- a/build/tests/unit/mocks/mockDatabase.d.ts +++ b/build/tests/unit/mocks/mockDatabase.d.ts @@ -1,4 +1,4 @@ -import type { MockAdapter } from "./mockAdapter"; +import type { MockAdapter } from './mockAdapter'; /** * A minimalistic version of ioBroker's Objects and States DB that just operates on a Map */ diff --git a/build/tests/unit/mocks/mockDatabase.js b/build/tests/unit/mocks/mockDatabase.js index 0cf86304..85481508 100644 --- a/build/tests/unit/mocks/mockDatabase.js +++ b/build/tests/unit/mocks/mockDatabase.js @@ -2,12 +2,12 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.MockDatabase = void 0; exports.createAsserts = createAsserts; +// @ts-expect-error no types const objects_1 = require("alcalzone-shared/objects"); -const typeguards_1 = require("alcalzone-shared/typeguards"); const str2regex_1 = require("../../../lib/str2regex"); const objectTemplate = Object.freeze({ - type: "state", - common: { name: "an object" }, + type: 'state', + common: { name: 'an object' }, native: {}, }); const stateTemplate = Object.freeze({ @@ -34,9 +34,9 @@ class MockDatabase { } publishObject(obj) { if (obj._id == null) - throw new Error("An object must have an ID"); + throw new Error('An object must have an ID'); if (obj.type == null) - throw new Error("An object must have a type"); + throw new Error('An object must have a type'); const completeObject = (0, objects_1.extend)({}, objectTemplate, obj); this.objects.set(obj._id, completeObject); } @@ -44,22 +44,16 @@ class MockDatabase { objects.forEach(this.publishObject.bind(this)); } publishStateObjects(...objects) { - objects - .map((obj) => (0, objects_1.extend)({}, obj, { type: "state" })) - .forEach(this.publishObject.bind(this)); + objects.map(obj => (0, objects_1.extend)({}, obj, { type: 'state' })).forEach(this.publishObject.bind(this)); } publishChannelObjects(...objects) { - objects - .map((obj) => (0, objects_1.extend)({}, obj, { type: "channel" })) - .forEach(this.publishObject.bind(this)); + objects.map(obj => (0, objects_1.extend)({}, obj, { type: 'channel' })).forEach(this.publishObject.bind(this)); } publishDeviceObjects(...objects) { - objects - .map((obj) => (0, objects_1.extend)({}, obj, { type: "device" })) - .forEach(this.publishObject.bind(this)); + objects.map(obj => (0, objects_1.extend)({}, obj, { type: 'device' })).forEach(this.publishObject.bind(this)); } deleteObject(objOrID) { - this.objects.delete(typeof objOrID === "string" ? objOrID : objOrID._id); + this.objects.delete(typeof objOrID === 'string' ? objOrID : objOrID._id); } publishState(id, state) { // if (typeof id !== "string") throw new Error("The id must be given!"); @@ -79,37 +73,36 @@ class MockDatabase { } } hasObject(namespaceOrId, id) { - id = namespaceOrId + (id ? "." + id : ""); + id = namespaceOrId + (id ? '.' + id : ''); return this.objects.has(id); } getObject(namespaceOrId, id) { // combines getObject and getForeignObject into one - id = namespaceOrId + (id ? "." + id : ""); + id = namespaceOrId + (id ? '.' + id : ''); return this.objects.get(id); } hasState(namespaceOrId, id) { - id = namespaceOrId + (id ? "." + id : ""); + id = namespaceOrId + (id ? '.' + id : ''); return this.states.has(id); } getState(namespaceOrId, id) { // combines getObject and getForeignObject into one - id = namespaceOrId + (id ? "." + id : ""); + id = namespaceOrId + (id ? '.' + id : ''); return this.states.get(id); } getObjects(namespaceOrPattern, patternOrType, type) { // combines getObjects and getForeignObjects into one let pattern; if (type != null) { - pattern = - namespaceOrPattern + (patternOrType ? "." + patternOrType : ""); + pattern = namespaceOrPattern + (patternOrType ? '.' + patternOrType : ''); } else if (patternOrType != null) { - if (["state", "channel", "device"].indexOf(patternOrType) > -1) { + if (['state', 'channel', 'device'].indexOf(patternOrType) > -1) { type = patternOrType; pattern = namespaceOrPattern; } else { - pattern = namespaceOrPattern + "." + patternOrType; + pattern = namespaceOrPattern + '.' + patternOrType; } } else { @@ -140,11 +133,12 @@ exports.MockDatabase = MockDatabase; // eslint-disable-next-line @typescript-eslint/explicit-function-return-type function createAsserts(db, adapter) { function normalizeID(id) { - if ((0, typeguards_1.isArray)(id)) - id = id.join("."); + if (Array.isArray(id)) { + id = id.join('.'); + } // Test if this ID is fully qualified if (!/^[a-z0-9\-_]+\.\d+\./.test(id)) { - id = adapter.namespace + "." + id; + id = `${adapter.namespace}.${id}`; } return id; } @@ -158,31 +152,29 @@ function createAsserts(db, adapter) { db.hasState(id).should.equal(true, `The state "${adapter.namespace}.${id}" does not exist but it was expected to!`); }, assertStateHasValue(id, value) { - ret.assertStateProperty(id, "val", value); + ret.assertStateProperty(id, 'val', value); }, assertStateIsAcked(id, ack = true) { - ret.assertStateProperty(id, "ack", ack); + ret.assertStateProperty(id, 'ack', ack); }, assertStateProperty(id, property, value) { id = normalizeID(id); ret.assertStateExists(id); - db.getState(id) - .should.be.an("object") - .that.has.property(property, value); + db.getState(id).should.be.an('object').that.has.property(property, value); }, assertObjectCommon(id, common) { id = normalizeID(id); ret.assertObjectExists(id); const dbObj = db.getObject(id); - dbObj.should.be.an("object").that.has.property("common"); - dbObj.common.should.be.an("object").that.nested.include(common); + dbObj.should.be.an('object').that.has.property('common'); + dbObj.common.should.be.an('object').that.nested.include(common); }, assertObjectNative(id, native) { id = normalizeID(id); ret.assertObjectExists(id); const dbObj = db.getObject(id); - dbObj.should.be.an("object").that.has.property("native"); - dbObj.native.should.be.an("object").that.nested.include(native); + dbObj.should.be.an('object').that.has.property('native'); + dbObj.native.should.be.an('object').that.nested.include(native); }, }; return ret; diff --git a/build/tests/unit/mocks/mockLogger.d.ts b/build/tests/unit/mocks/mockLogger.d.ts index 8387202d..0a3928f5 100644 --- a/build/tests/unit/mocks/mockLogger.d.ts +++ b/build/tests/unit/mocks/mockLogger.d.ts @@ -1,4 +1,4 @@ -import { Mock } from "./tools"; +import { Mock } from './tools'; export type MockLogger = Mock & { resetMock(): void; resetMockHistory(): void; diff --git a/build/tests/unit/mocks/mockLogger.js b/build/tests/unit/mocks/mockLogger.js index b9752964..b7fc3770 100644 --- a/build/tests/unit/mocks/mockLogger.js +++ b/build/tests/unit/mocks/mockLogger.js @@ -16,7 +16,7 @@ function createLoggerMock() { error: (0, sinon_1.stub)(), debug: (0, sinon_1.stub)(), silly: (0, sinon_1.stub)(), - level: "info", + level: 'info', // Mock-specific methods resetMockHistory() { // reset Logger diff --git a/build/tests/unit/mocks/tools.d.ts b/build/tests/unit/mocks/tools.d.ts index 2019f4a7..06d78358 100644 --- a/build/tests/unit/mocks/tools.d.ts +++ b/build/tests/unit/mocks/tools.d.ts @@ -1,4 +1,4 @@ -import type { Equals, Overwrite } from "alcalzone-shared/types"; +import type { Equals, Overwrite } from 'alcalzone-shared/types'; export type IsAny = Equals; export type MockableMethods, NoAny = { [K in keyof All]: IsAny extends true ? never : All[K] extends (...args: any[]) => void ? K : never; @@ -9,4 +9,4 @@ export type Mock = Overwrite): void; export declare function doResetBehavior(parent: Record, implementedMethods: Record): void; export declare function stubAndPromisifyImplementedMethods(parent: Record, implementedMethods: Partial>, allowUserOverrides?: T[]): void; -export type ImplementedMethodDictionary = Partial, "none" | "normal" | "no error">>; +export type ImplementedMethodDictionary = Partial, 'none' | 'normal' | 'no error'>>; diff --git a/build/tests/unit/mocks/tools.js b/build/tests/unit/mocks/tools.js index d7ab0740..dea384e4 100644 --- a/build/tests/unit/mocks/tools.js +++ b/build/tests/unit/mocks/tools.js @@ -3,33 +3,33 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.doResetHistory = doResetHistory; exports.doResetBehavior = doResetBehavior; exports.stubAndPromisifyImplementedMethods = stubAndPromisifyImplementedMethods; +// @ts-expect-error no types const async_1 = require("alcalzone-shared/async"); const sinon_1 = require("sinon"); function doResetHistory(parent) { for (const prop of Object.keys(parent)) { const val = parent[prop]; - if (val && typeof val.resetHistory === "function") + if (val && typeof val.resetHistory === 'function') val.resetHistory(); } } function doResetBehavior(parent, implementedMethods) { for (const prop of Object.keys(parent)) { - if (prop in implementedMethods || - (prop.endsWith("Async") && prop.slice(0, -5) in implementedMethods)) + if (prop in implementedMethods || (prop.endsWith('Async') && prop.slice(0, -5) in implementedMethods)) continue; const val = parent[prop]; - if (val && typeof val.resetBehavior === "function") + if (val && typeof val.resetBehavior === 'function') val.resetBehavior(); } } function dontOverwriteThis() { - throw new Error("You must not overwrite the behavior of this stub!"); + throw new Error('You must not overwrite the behavior of this stub!'); } function stubAndPromisifyImplementedMethods(parent, implementedMethods, allowUserOverrides = []) { // The methods implemented above are no stubs, but we claimed they are // Therefore hook them up with a real stub for (const methodName of Object.keys(implementedMethods)) { - if (methodName.endsWith("Async")) + if (methodName.endsWith('Async')) continue; const originalMethod = parent[methodName]; const callbackFake = (parent[methodName] = (0, sinon_1.stub)()); @@ -41,14 +41,14 @@ function stubAndPromisifyImplementedMethods(parent, implementedMethods, allowUse } // Construct the async fake if there's any const asyncType = implementedMethods[methodName]; - if (asyncType === "none") + if (asyncType === 'none') continue; - const promisifyMethod = asyncType === "no error" ? async_1.promisifyNoError : async_1.promisify; + const promisifyMethod = asyncType === 'no error' ? async_1.promisifyNoError : async_1.promisify; const asyncFake = (0, sinon_1.stub)().callsFake(promisifyMethod(originalMethod, parent)); parent[`${methodName}Async`] = asyncFake; // Prevent the user from changing the stub's behavior if (allowUserOverrides.indexOf(methodName) === -1 || - allowUserOverrides.indexOf((methodName + "Async")) === -1) { + allowUserOverrides.indexOf((methodName + 'Async')) === -1) { asyncFake.returns = dontOverwriteThis; asyncFake.callsFake = dontOverwriteThis; } diff --git a/eslint.config.mjs b/eslint.config.mjs index c0ba5eab..77d5ce6d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,96 +1,93 @@ -import path from "node:path"; -import { fileURLToPath } from "node:url"; -import js from "@eslint/js"; -import { FlatCompat } from "@eslint/eslintrc"; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import js from '@eslint/js'; +import { FlatCompat } from '@eslint/eslintrc'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const compat = new FlatCompat({ - baseDirectory: __dirname, - recommendedConfig: js.configs.recommended, - allConfig: js.configs.all, + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all, }); export default [ - { - ignores: ["**/build/", "**/node_modules/"], - }, - ...compat.extends( - "plugin:@typescript-eslint/recommended", - "plugin:prettier/recommended", - ), - { - plugins: {}, + { + ignores: ['**/build/', '**/node_modules/'], + }, + ...compat.extends('plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'), + { + plugins: {}, - files: ["src/**/*.ts"], - linterOptions: { - reportUnusedDisableDirectives: true, - }, + files: ['src/**/*.ts'], + linterOptions: { + reportUnusedDisableDirectives: true, + }, - rules: { - "@typescript-eslint/no-unsafe-declaration-merging": "off", - "@typescript-eslint/no-parameter-properties": "off", - "@typescript-eslint/no-explicit-any": "off", + rules: { + '@typescript-eslint/no-unsafe-declaration-merging': 'off', + '@typescript-eslint/no-parameter-properties': 'off', + '@typescript-eslint/no-explicit-any': 'off', - "@typescript-eslint/no-use-before-define": [ - "error", - { - functions: false, - typedefs: false, - classes: false, - }, - ], + '@typescript-eslint/no-use-before-define': [ + 'error', + { + functions: false, + typedefs: false, + classes: false, + }, + ], - "@typescript-eslint/no-unused-vars": [ - "error", - { - ignoreRestSiblings: true, - argsIgnorePattern: "^_", - }, - ], + '@typescript-eslint/no-unused-vars': [ + 'error', + { + ignoreRestSiblings: true, + argsIgnorePattern: '^_', + }, + ], - "@typescript-eslint/explicit-function-return-type": [ - "warn", - { - allowExpressions: true, - allowTypedFunctionExpressions: true, - }, - ], + '@typescript-eslint/explicit-function-return-type': [ + 'warn', + { + allowExpressions: true, + allowTypedFunctionExpressions: true, + }, + ], - "@typescript-eslint/no-object-literal-type-assertion": "off", - "@typescript-eslint/interface-name-prefix": "off", - "@typescript-eslint/no-non-null-assertion": "off", + '@typescript-eslint/no-object-literal-type-assertion': 'off', + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', - "@typescript-eslint/no-inferrable-types": [ - "error", - { - ignoreParameters: true, - ignoreProperties: true, - }, - ], - }, - }, - { - files: ["**/*.test.ts"], + '@typescript-eslint/no-inferrable-types': [ + 'error', + { + ignoreParameters: true, + ignoreProperties: true, + }, + ], + }, + }, + { + files: ['**/*.test.ts'], - rules: { - "@typescript-eslint/explicit-function-return-type": "off", - }, - }, - { - files: ["**/*.js"], - rules: { - // Disable all TypeScript-specific rules for JavaScript files - "@typescript-eslint/no-require-imports": "off", - "@typescript-eslint/no-parameter-properties": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-use-before-define": "off", - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/no-object-literal-type-assertion": "off", - "@typescript-eslint/interface-name-prefix": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-inferrable-types": "off", - }, - }, + rules: { + '@typescript-eslint/explicit-function-return-type': 'off', + }, + }, + { + files: ['**/*.js'], + rules: { + // Disable all TypeScript-specific rules for JavaScript files + '@typescript-eslint/no-require-imports': 'off', + '@typescript-eslint/no-parameter-properties': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-object-literal-type-assertion': 'off', + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-inferrable-types': 'off', + }, + }, ]; diff --git a/package-lock.json b/package-lock.json index f50a1f45..6ba5bfb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,41 +9,34 @@ "version": "5.0.0", "license": "MIT", "dependencies": { - "alcalzone-shared": "~4.0.8", + "alcalzone-shared": "~5.0.0", "chai": "^4.5.0", "chai-as-promised": "^7.1.2", - "debug": "^4.3.7", - "fs-extra": "^11.2.0", - "mocha": "^10.7.3", + "debug": "^4.4.0", + "fs-extra": "^11.3.0", + "mocha": "^11.0.1", "sinon": "^19.0.2", "sinon-chai": "^3.7.0" }, "devDependencies": { "@alcalzone/release-script": "^3.8.0", "@alcalzone/release-script-plugin-license": "^3.7.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "^9.17.0", - "@iobroker/adapter-core": "^3.2.2", - "@iobroker/types": "^6.0.11", + "@iobroker/adapter-core": "^3.2.3", + "@iobroker/eslint-config": "^1.0.0", + "@iobroker/types": "^7.0.6", "@tsconfig/node14": "^14.1.2", - "@types/chai": "^4.3.19", - "@types/chai-as-promised": "^8.0.0", + "@types/chai": "^4.3.20", + "@types/chai-as-promised": "^8.0.1", "@types/debug": "4.1.12", "@types/fs-extra": "^11.0.4", - "@types/mocha": "^10.0.8", - "@types/node": "^22.10.1", + "@types/mocha": "^10.0.10", + "@types/node": "^22.10.7", "@types/sinon": "^17.0.3", "@types/sinon-chai": "^3.2.12", - "@typescript-eslint/eslint-plugin": "^8.16.0", - "@typescript-eslint/parser": "^8.19.0", - "eslint": "^9.17.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.2.1", - "prettier": "^3.3.3", "rimraf": "^6.0.1", "source-map-support": "^0.5.21", "ts-node": "^10.9.2", - "typescript": "~5.7.2" + "typescript": "~5.7.3" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -51,6 +44,7 @@ "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", "dev": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -164,6 +158,19 @@ "node": ">=12.20" } }, + "node_modules/@alcalzone/release-script-plugin-changelog/node_modules/alcalzone-shared": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/alcalzone-shared/-/alcalzone-shared-4.0.8.tgz", + "integrity": "sha512-Rr0efCjNL9lw7miDvU8exL87Y42ehsLU2jUGNQUphhnlvxnTMrHeApWgoOSGZvsE2PhxC3KO7Z+VpQ/IbuV3aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@alcalzone/release-script-plugin-changelog/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -192,6 +199,19 @@ "node": ">=12.20" } }, + "node_modules/@alcalzone/release-script-plugin-exec/node_modules/alcalzone-shared": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/alcalzone-shared/-/alcalzone-shared-4.0.8.tgz", + "integrity": "sha512-Rr0efCjNL9lw7miDvU8exL87Y42ehsLU2jUGNQUphhnlvxnTMrHeApWgoOSGZvsE2PhxC3KO7Z+VpQ/IbuV3aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@alcalzone/release-script-plugin-git": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/@alcalzone/release-script-plugin-git/-/release-script-plugin-git-3.8.0.tgz", @@ -268,6 +288,19 @@ "node": ">=12.20" } }, + "node_modules/@alcalzone/release-script-plugin-package/node_modules/alcalzone-shared": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/alcalzone-shared/-/alcalzone-shared-4.0.8.tgz", + "integrity": "sha512-Rr0efCjNL9lw7miDvU8exL87Y42ehsLU2jUGNQUphhnlvxnTMrHeApWgoOSGZvsE2PhxC3KO7Z+VpQ/IbuV3aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@alcalzone/release-script-plugin-package/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -299,6 +332,19 @@ "node": ">=12.20" } }, + "node_modules/@alcalzone/release-script-plugin-version/node_modules/alcalzone-shared": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/alcalzone-shared/-/alcalzone-shared-4.0.8.tgz", + "integrity": "sha512-Rr0efCjNL9lw7miDvU8exL87Y42ehsLU2jUGNQUphhnlvxnTMrHeApWgoOSGZvsE2PhxC3KO7Z+VpQ/IbuV3aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@alcalzone/release-script-plugin-version/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -314,6 +360,19 @@ "node": ">=12" } }, + "node_modules/@alcalzone/release-script/node_modules/alcalzone-shared": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/alcalzone-shared/-/alcalzone-shared-4.0.8.tgz", + "integrity": "sha512-Rr0efCjNL9lw7miDvU8exL87Y42ehsLU2jUGNQUphhnlvxnTMrHeApWgoOSGZvsE2PhxC3KO7Z+VpQ/IbuV3aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@alcalzone/release-script/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -356,6 +415,33 @@ "node": ">=12" } }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -368,11 +454,28 @@ "node": ">=12" } }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.49.0.tgz", + "integrity": "sha512-xjZTSFgECpb9Ohuk5yMX5RhUEbfeQcuOp8IF60e+wyzWEF0M5xeSgqsfLtvPEX8BIyOX9saZqzuGPmZ8oWc+5Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "comment-parser": "1.4.1", + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~4.1.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, + "peer": true, "dependencies": { "eslint-visitor-keys": "^3.3.0" }, @@ -388,6 +491,7 @@ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, + "peer": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -397,6 +501,7 @@ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz", "integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==", "dev": true, + "peer": true, "dependencies": { "@eslint/object-schema": "^2.1.4", "debug": "^4.3.1", @@ -411,6 +516,7 @@ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz", "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==", "dev": true, + "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -420,6 +526,7 @@ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", "dev": true, + "peer": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -438,11 +545,26 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@eslint/js": { "version": "9.17.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", "dev": true, + "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -452,6 +574,7 @@ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", "dev": true, + "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -461,6 +584,7 @@ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", "dev": true, + "peer": true, "dependencies": { "levn": "^0.4.1" }, @@ -473,6 +597,7 @@ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, + "peer": true, "engines": { "node": ">=18.18.0" } @@ -482,6 +607,7 @@ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", "dev": true, + "peer": true, "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" @@ -495,6 +621,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", "dev": true, + "peer": true, "engines": { "node": ">=18.18" }, @@ -508,6 +635,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "peer": true, "engines": { "node": ">=12.22" }, @@ -521,6 +649,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", "dev": true, + "peer": true, "engines": { "node": ">=18.18" }, @@ -530,21 +659,44 @@ } }, "node_modules/@iobroker/adapter-core": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@iobroker/adapter-core/-/adapter-core-3.2.2.tgz", - "integrity": "sha512-Cuiga42WRkJ/NduyrIGgSVWpLgD6ihIflIvZiWVThwgWfDyI+Okgw3snVSlf/p4ki6BAr3MH6Dfa0qPySCGWvQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@iobroker/adapter-core/-/adapter-core-3.2.3.tgz", + "integrity": "sha512-ktk2KGexzOAbUZpkv/9JATJvO2DUHIfQAWhtpRjLxcR3+hs5/73hvt46XPSqwSpY/wnw5+E7kM7BqkfN7G19IQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=16" }, "peerDependencies": { - "@iobroker/types": "^6.0.11" + "@iobroker/types": ">=6.0.11" + } + }, + "node_modules/@iobroker/eslint-config": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@iobroker/eslint-config/-/eslint-config-1.0.0.tgz", + "integrity": "sha512-84gqXXsmAFKjpb2iXOvhMRAR0qgLb5xaWATnuwD7GW029J7ofqweFpgdDb7JxJ6Jdtwk4J0kwz1jmp4FsqDETA==", + "dev": true, + "peerDependencies": { + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "^9.10.0", + "@typescript-eslint/eslint-plugin": "^8.4.0", + "@typescript-eslint/parser": "^8.4.0", + "eslint": ">=9.10.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-jsdoc": "^50.2.2", + "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-react": "^7.36.0", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-unicorn": "^55.0.0", + "globals": "^15.9.0", + "prettier": "^3.3.3", + "typescript-eslint": "^8.5.0" } }, "node_modules/@iobroker/types": { - "version": "6.0.11", - "resolved": "https://registry.npmjs.org/@iobroker/types/-/types-6.0.11.tgz", - "integrity": "sha512-RNDURjtL5Cm9wt6ocCqdRi86Qx1350zBIvvrJ9+Fjgasoi6cWCdoOghkwEeb95TH2j//q/uLqWwL8SZ0vxx6Kw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@iobroker/types/-/types-7.0.6.tgz", + "integrity": "sha512-hCAw/ICjxQ48cIAVBvBAkj2NMkBkYILNFsPLH5yhcAH+B57SldS/2uz37+8i+dFzn4GlMXC7X04flJK02kyIdw==", "dev": true, "license": "MIT", "engines": { @@ -555,7 +707,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -573,7 +724,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -586,7 +736,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -599,14 +748,12 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -624,7 +771,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -640,7 +786,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -684,6 +829,8 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -697,6 +844,8 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, + "license": "MIT", + "peer": true, "engines": { "node": ">= 8" } @@ -706,6 +855,8 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -718,7 +869,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -731,6 +881,7 @@ "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, @@ -808,16 +959,16 @@ "dev": true }, "node_modules/@types/chai": { - "version": "4.3.19", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.19.tgz", - "integrity": "sha512-2hHHvQBVE2FiSK4eN0Br6snX9MtolHaTo/batnLjlGRhoQzlCL61iVpxoqO7SfFyOw+P/pwv+0zNHzKoGWz9Cw==", + "version": "4.3.20", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", + "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", "dev": true, "license": "MIT" }, "node_modules/@types/chai-as-promised": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-8.0.0.tgz", - "integrity": "sha512-YbYaXFqJwSABp9OXQTVrPPmstZgNjkRieWVd/xAl5Yc/e5+F44bXLeQggpvm0sjsS1bg+2Y5cwU+rquwwD2dXA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-8.0.1.tgz", + "integrity": "sha512-dAlDhLjJlABwAVYObo9TPWYTRg9NaQM5CXeaeJYcYAkvzUf0JRLIiog88ao2Wqy/20WUnhbbUZcgvngEbJ3YXQ==", "dev": true, "license": "MIT", "dependencies": { @@ -838,7 +989,8 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@types/fs-extra": { "version": "11.0.4", @@ -855,7 +1007,8 @@ "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 + "dev": true, + "peer": true }, "node_modules/@types/jsonfile": { "version": "6.1.4", @@ -868,9 +1021,9 @@ } }, "node_modules/@types/mocha": { - "version": "10.0.8", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.8.tgz", - "integrity": "sha512-HfMcUmy9hTMJh66VNcmeC9iVErIZJli2bszuXc6julh5YGuRb/W5OnkHjwLNYdFlMis0sY3If5SEAp+PktdJjw==", + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", "dev": true, "license": "MIT" }, @@ -881,14 +1034,23 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", - "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "version": "22.10.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz", + "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==", "dev": true, + "license": "MIT", "dependencies": { "undici-types": "~6.20.0" } }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/sinon": { "version": "17.0.3", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-17.0.3.tgz", @@ -917,20 +1079,22 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.16.0.tgz", - "integrity": "sha512-5YTHKV8MYlyMI6BaEG7crQ9BhSc8RxzshOReKwZwRWN0+XvvTOm+L/UYLCYxFpfwYuAAqhxiq4yae0CMFwbL7Q==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.21.0.tgz", + "integrity": "sha512-eTH+UOR4I7WbdQnG4Z48ebIA6Bgi7WO8HvFEneeYBxG8qCOYgTOFPSg6ek9ITIDvGjDQzWHcoWHCDO2biByNzA==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.16.0", - "@typescript-eslint/type-utils": "8.16.0", - "@typescript-eslint/utils": "8.16.0", - "@typescript-eslint/visitor-keys": "8.16.0", + "@typescript-eslint/scope-manager": "8.21.0", + "@typescript-eslint/type-utils": "8.21.0", + "@typescript-eslint/utils": "8.21.0", + "@typescript-eslint/visitor-keys": "8.21.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -941,83 +1105,22 @@ }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz", - "integrity": "sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.16.0", - "@typescript-eslint/visitor-keys": "8.16.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.16.0.tgz", - "integrity": "sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz", - "integrity": "sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.16.0", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.19.0.tgz", - "integrity": "sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.21.0.tgz", + "integrity": "sha512-Wy+/sdEH9kI3w9civgACwabHbKl+qIOu0uFZ9IMKzX3Jpv9og0ZBJrZExGrPpFAY7rWsXuxs5e7CPPP17A4eYA==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.19.0", - "@typescript-eslint/types": "8.19.0", - "@typescript-eslint/typescript-estree": "8.19.0", - "@typescript-eslint/visitor-keys": "8.19.0", + "@typescript-eslint/scope-manager": "8.21.0", + "@typescript-eslint/types": "8.21.0", + "@typescript-eslint/typescript-estree": "8.21.0", + "@typescript-eslint/visitor-keys": "8.21.0", "debug": "^4.3.4" }, "engines": { @@ -1033,13 +1136,15 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.0.tgz", - "integrity": "sha512-hkoJiKQS3GQ13TSMEiuNmSCvhz7ujyqD1x3ShbaETATHrck+9RaDdUbt+osXaUuns9OFwrDTTrjtwsU8gJyyRA==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.21.0.tgz", + "integrity": "sha512-G3IBKz0/0IPfdeGRMbp+4rbjfSSdnGkXsM/pFZA8zM9t9klXDnB/YnKOBQ0GoPmoROa4bCq2NeHgJa5ydsQ4mA==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "@typescript-eslint/types": "8.19.0", - "@typescript-eslint/visitor-keys": "8.19.0" + "@typescript-eslint/types": "8.21.0", + "@typescript-eslint/visitor-keys": "8.21.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1050,15 +1155,17 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.16.0.tgz", - "integrity": "sha512-IqZHGG+g1XCWX9NyqnI/0CX5LL8/18awQqmkZSl2ynn8F76j579dByc0jhfVSnSnhf7zv76mKBQv9HQFKvDCgg==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.21.0.tgz", + "integrity": "sha512-95OsL6J2BtzoBxHicoXHxgk3z+9P3BEcQTpBKriqiYzLKnM2DeSqs+sndMKdamU8FosiadQFT3D+BSL9EKnAJQ==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.16.0", - "@typescript-eslint/utils": "8.16.0", + "@typescript-eslint/typescript-estree": "8.21.0", + "@typescript-eslint/utils": "8.21.0", "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1068,19 +1175,17 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.16.0.tgz", - "integrity": "sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==", + "node_modules/@typescript-eslint/types": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.21.0.tgz", + "integrity": "sha512-PAL6LUuQwotLW2a8VsySDBwYMm129vFm4tMVlylzdoTybTHaAi0oBp7Ac6LhSrHHOdLM3efH+nAR6hAWoMF89A==", "dev": true, + "license": "MIT", + "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1089,20 +1194,22 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz", - "integrity": "sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.21.0.tgz", + "integrity": "sha512-x+aeKh/AjAArSauz0GiQZsjT8ciadNMHdkUSwBB9Z6PrKc/4knM4g3UfHml6oDJmKC88a6//cdxnO/+P2LkMcg==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "@typescript-eslint/types": "8.16.0", - "@typescript-eslint/visitor-keys": "8.16.0", + "@typescript-eslint/types": "8.21.0", + "@typescript-eslint/visitor-keys": "8.21.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1111,55 +1218,28 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz", - "integrity": "sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.16.0", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": { + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { "balanced-match": "^1.0.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", + "peer": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -1170,33 +1250,41 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@typescript-eslint/types": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.0.tgz", - "integrity": "sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==", + "node_modules/@typescript-eslint/utils": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.21.0.tgz", + "integrity": "sha512-xcXBfcq0Kaxgj7dwejMbFyq7IOHgpNMtVuDveK7w3ZGwG9owKzhALVwKpTF2yrZmEwl9SWdetf3fxNzJQaVuxw==", "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.21.0", + "@typescript-eslint/types": "8.21.0", + "@typescript-eslint/typescript-estree": "8.21.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.0.tgz", - "integrity": "sha512-WW9PpDaLIFW9LCbucMSdYUuGeFUz1OkWYS/5fwZwTA+l2RwlWFdJvReQqMUMBw4yJWJOfqd7An9uwut2Oj8sLw==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.21.0.tgz", + "integrity": "sha512-BkLMNpdV6prozk8LlyK/SOoWLmUFi+ZD+pcqti9ILCbVvHGk1ui1g4jJOc2WDLaeExz2qWwojxlPce5PljcT3w==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "@typescript-eslint/types": "8.19.0", - "@typescript-eslint/visitor-keys": "8.19.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "@typescript-eslint/types": "8.21.0", + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1204,195 +1292,15 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.16.0.tgz", - "integrity": "sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.16.0", - "@typescript-eslint/types": "8.16.0", - "@typescript-eslint/typescript-estree": "8.16.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.16.0.tgz", - "integrity": "sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.16.0", - "@typescript-eslint/visitor-keys": "8.16.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.16.0.tgz", - "integrity": "sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.16.0.tgz", - "integrity": "sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.16.0", - "@typescript-eslint/visitor-keys": "8.16.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.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/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.16.0.tgz", - "integrity": "sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.16.0", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.0.tgz", - "integrity": "sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.19.0", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, + "license": "Apache-2.0", + "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1418,6 +1326,7 @@ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -1437,6 +1346,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1449,15 +1359,12 @@ } }, "node_modules/alcalzone-shared": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/alcalzone-shared/-/alcalzone-shared-4.0.8.tgz", - "integrity": "sha512-Rr0efCjNL9lw7miDvU8exL87Y42ehsLU2jUGNQUphhnlvxnTMrHeApWgoOSGZvsE2PhxC3KO7Z+VpQ/IbuV3aA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/alcalzone-shared/-/alcalzone-shared-5.0.0.tgz", + "integrity": "sha512-X73hgVWcrIKUUB6jZgHj5flRbTft8AAoJ2MqRKEcAX1whW3OeGkxsQ6ol4nd4/rKxd1eoCRXUGW3cIhXrXU4Sg==", "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/ansi-colors": { @@ -1503,6 +1410,17 @@ "node": ">= 8" } }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -1514,6 +1432,149 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -1530,6 +1591,23 @@ "dev": true, "license": "MIT" }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/axios": { "version": "1.7.7", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", @@ -1560,6 +1638,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1581,33 +1660,157 @@ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "engines": { - "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001695", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001695.tgz", + "integrity": "sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0", + "peer": true + }, "node_modules/chai": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", @@ -1691,6 +1894,48 @@ "fsevents": "~2.3.2" } }, + "node_modules/ci-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.1.0.tgz", + "integrity": "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz", + "integrity": "sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/clean-regexp/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -1730,11 +1975,38 @@ "node": ">= 0.8" } }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "dev": true, + "peer": true + }, + "node_modules/core-js-compat": { + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.40.0.tgz", + "integrity": "sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "browserslist": "^4.24.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } }, "node_modules/create-require": { "version": "1.1.1", @@ -1746,7 +2018,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1756,10 +2027,67 @@ "node": ">= 8" } }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "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.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1800,7 +2128,46 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "peer": true + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/delayed-stream": { "version": "1.0.0", @@ -1821,13 +2188,50 @@ "node": ">=0.3.1" } }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, "license": "MIT" }, + "node_modules/electron-to-chromium": { + "version": "1.5.84", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.84.tgz", + "integrity": "sha512-I+DQ8xgafao9Ha6y0qjHHvpZ9OfyA1qKlkHkjywxzniORU2awxyz7f/iVJcULmrF2yrM3nHQf+iDjJtbbexd/g==", + "dev": true, + "license": "ISC", + "peer": true + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1845,10 +2249,209 @@ "node": ">=8.6" } }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.9", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", + "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.18" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -1869,6 +2472,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -1929,6 +2533,7 @@ "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -1936,12 +2541,40 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-jsdoc": { + "version": "50.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.6.2.tgz", + "integrity": "sha512-n7GNZ4czMAAbDg7DsDA7PvHo1IPIUwAXYmxTx6j/hTlXbt5V0x5q/kGkiJ7s4wA9SpB/yaiK8jF7CO237lOLew==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@es-joy/jsdoccomment": "~0.49.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.3.6", + "escape-string-regexp": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.6.0", + "parse-imports": "^2.1.1", + "semver": "^7.6.3", + "spdx-expression-parse": "^4.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, "node_modules/eslint-plugin-prettier": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prettier-linter-helpers": "^1.0.0", "synckit": "^0.9.1" @@ -1967,11 +2600,106 @@ } } }, - "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "node_modules/eslint-plugin-react": { + "version": "7.37.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz", + "integrity": "sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz", + "integrity": "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-unicorn": { + "version": "55.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-55.0.0.tgz", + "integrity": "sha512-n3AKiVpY2/uDcGrS3+QsYDkjPfaOrNrsfQxU9nt5nitd9KuvVXrfAvgCO9DYPSfap+Gqjw9EOrXIsBp5tlHZjA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.5", + "@eslint-community/eslint-utils": "^4.4.0", + "ci-info": "^4.0.0", + "clean-regexp": "^1.0.0", + "core-js-compat": "^3.37.0", + "esquery": "^1.5.0", + "globals": "^15.7.0", + "indent-string": "^4.0.0", + "is-builtin-module": "^3.2.1", + "jsesc": "^3.0.2", + "pluralize": "^8.0.0", + "read-pkg-up": "^7.0.1", + "regexp-tree": "^0.1.27", + "regjsparser": "^0.10.0", + "semver": "^7.6.1", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=18.18" + }, + "funding": { + "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1" + }, + "peerDependencies": { + "eslint": ">=8.56.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", "dev": true, + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -1988,6 +2716,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -2000,6 +2729,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, + "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -2012,6 +2742,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "peer": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -2024,6 +2755,7 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, + "peer": true, "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", @@ -2041,6 +2773,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, + "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -2054,6 +2787,7 @@ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -2066,6 +2800,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "peer": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -2079,6 +2814,7 @@ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=4.0" } @@ -2089,6 +2825,7 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -2121,26 +2858,29 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fast-diff": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true + "dev": true, + "peer": true }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -2151,19 +2891,23 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", + "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", "dev": true, + "license": "ISC", + "peer": true, "dependencies": { "reusify": "^1.0.4" } @@ -2174,6 +2918,7 @@ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "flat-cache": "^4.0.0" }, @@ -2221,6 +2966,7 @@ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" @@ -2234,7 +2980,8 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "node_modules/follow-redirects": { "version": "1.15.9", @@ -2257,11 +3004,21 @@ } } }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.0", @@ -2278,7 +3035,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -2303,9 +3059,9 @@ } }, "node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -2316,11 +3072,49 @@ "node": ">=14.14" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "license": "ISC" + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/get-caller-file": { "version": "2.0.5", @@ -2339,6 +3133,47 @@ "node": "*" } }, + "node_modules/get-intrinsic": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -2351,21 +3186,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": ">=12" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2391,24 +3245,59 @@ "balanced-match": "^1.0.0" } }, + "node_modules/glob/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -2416,6 +3305,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/globalyzer": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", @@ -2428,6 +3335,20 @@ "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", "dev": true }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", @@ -2437,7 +3358,22 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "dev": true, + "peer": true + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/has-flag": { "version": "4.0.0", @@ -2447,6 +3383,82 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -2455,6 +3467,14 @@ "he": "bin/he" } }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true, + "license": "ISC", + "peer": true + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -2470,6 +3490,7 @@ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 4" } @@ -2480,6 +3501,7 @@ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -2496,26 +3518,101 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true, + "peer": true, "engines": { "node": ">=0.8.19" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "license": "ISC", + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/is-async-function": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.0.tgz", + "integrity": "sha512-GExz9MtyhlZyXYLxzlJRj5WUCE661zhDa1Yna52CN57AJsymh+DvXXjyveSioqSRdxvUrdKdvqB1b5cVKsNpWQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -2528,6 +3625,109 @@ "node": ">=8" } }, + "node_modules/is-boolean-object": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz", + "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2536,6 +3736,23 @@ "node": ">=0.10.0" } }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -2544,31 +3761,134 @@ "node": ">=8" } }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dependencies": { - "is-extglob": "^2.1.1" + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "peer": true, "engines": { - "node": ">=0.12.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.3" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-stream": { @@ -2583,6 +3903,60 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -2594,11 +3968,86 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.0.tgz", + "integrity": "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/jackspeak": { "version": "4.0.1", @@ -2619,6 +4068,14 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -2630,25 +4087,61 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", + "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-buffer": { "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" + "license": "MIT", + "peer": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT", + "peer": true }, "node_modules/json-schema-traverse": { "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" + "license": "MIT", + "peer": true }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true + "dev": true, + "peer": true }, "node_modules/jsonfile": { "version": "6.1.0", @@ -2661,6 +4154,23 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/just-extend": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", @@ -2673,6 +4183,7 @@ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "json-buffer": "3.0.1" } @@ -2682,6 +4193,7 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "peer": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -2690,6 +4202,14 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2714,7 +4234,8 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/log-symbols": { "version": "4.1.0", @@ -2731,6 +4252,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -2741,16 +4276,10 @@ } }, "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" }, "node_modules/make-error": { "version": "1.3.6", @@ -2758,6 +4287,17 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -2770,6 +4310,7 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 8" } @@ -2780,6 +4321,7 @@ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -2820,11 +4362,23 @@ "node": ">=6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, "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, + "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2836,16 +4390,15 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, "node_modules/mocha": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", - "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.0.1.tgz", + "integrity": "sha512-+3GkODfsDG71KSCQhc4IekSW+ItCK/kiez1Z28ksWvYhKXV/syxMlerR/sC7whDp7IyreZ4YxceMLdTs5hQE8A==", "license": "MIT", "dependencies": { "ansi-colors": "^4.1.3", @@ -2855,7 +4408,7 @@ "diff": "^5.2.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", - "glob": "^8.1.0", + "glob": "^10.4.5", "he": "^1.2.0", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", @@ -2874,7 +4427,7 @@ "mocha": "bin/mocha.js" }, "engines": { - "node": ">= 14.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/mocha/node_modules/brace-expansion": { @@ -2922,7 +4475,8 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true + "dev": true, + "peer": true }, "node_modules/nise": { "version": "6.1.1", @@ -2937,6 +4491,61 @@ "path-to-regexp": "^8.1.0" } }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -2957,13 +4566,118 @@ "node": ">=8" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "wrappy": "1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/onetime": { @@ -2986,6 +4700,7 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, + "peer": true, "dependencies": { "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", @@ -2995,7 +4710,26 @@ "type-check": "^0.4.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/p-limit": { @@ -3026,11 +4760,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", - "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { @@ -3039,6 +4783,7 @@ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "callsites": "^3.0.0" }, @@ -3046,6 +4791,41 @@ "node": ">=6" } }, + "node_modules/parse-imports": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.2.1.tgz", + "integrity": "sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==", + "dev": true, + "license": "Apache-2.0 AND MIT", + "peer": true, + "dependencies": { + "es-module-lexer": "^1.5.3", + "slashes": "^3.0.12" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3058,11 +4838,18 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/path-scurry": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", @@ -3125,11 +4912,34 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "peer": true, "engines": { "node": ">= 0.8.0" } @@ -3140,6 +4950,7 @@ "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -3155,6 +4966,7 @@ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", "dev": true, + "peer": true, "dependencies": { "fast-diff": "^1.1.2" }, @@ -3162,6 +4974,19 @@ "node": ">=6.0.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -3175,6 +5000,7 @@ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -3197,7 +5023,9 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT", + "peer": true }, "node_modules/randombytes": { "version": "2.1.0", @@ -3208,6 +5036,121 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -3219,6 +5162,87 @@ "node": ">=8.10.0" } }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regjsparser": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.10.0.tgz", + "integrity": "sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -3227,12 +5251,32 @@ "node": ">=0.10.0" } }, + "node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=4" } @@ -3242,6 +5286,8 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true, + "license": "MIT", + "peer": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -3336,10 +5382,33 @@ "url": "https://feross.org/support" } ], + "license": "MIT", + "peer": true, "dependencies": { "queue-microtask": "^1.2.2" } }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -3360,14 +5429,49 @@ ], "license": "MIT" }, - "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "lru-cache": "^6.0.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -3384,25 +5488,155 @@ "randombytes": "^2.1.0" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "shebang-regex": "^3.0.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/signal-exit": { @@ -3448,6 +5682,14 @@ "node": ">=0.3.1" } }, + "node_modules/slashes": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", + "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", + "dev": true, + "license": "ISC", + "peer": true + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -3467,6 +5709,58 @@ "source-map": "^0.6.0" } }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0", + "peer": true + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "dev": true, + "license": "CC0-1.0", + "peer": true + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -3485,7 +5779,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -3496,6 +5789,109 @@ "node": ">=8" } }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -3512,7 +5908,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -3530,6 +5925,20 @@ "node": ">=6" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3552,12 +5961,27 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/synckit": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@pkgr/core": "^0.1.0", "tslib": "^2.6.2" @@ -3591,16 +6015,17 @@ } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/ts-node": { @@ -3668,13 +6093,15 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true, - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "peer": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -3691,11 +6118,105 @@ "node": ">=4" } }, + "node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", - "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3704,6 +6225,50 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.21.0.tgz", + "integrity": "sha512-txEKYY4XMKwPXxNkN8+AxAdX6iIJAPiJbHE/FpQccs/sxw8Lf26kqwC3cn0xkHlW8kEbLhkhCsjWuMveaY9Rxw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.21.0", + "@typescript-eslint/parser": "8.21.0", + "@typescript-eslint/utils": "8.21.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", @@ -3718,12 +6283,53 @@ "node": ">= 10.0.0" } }, + "node_modules/update-browserslist-db": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", + "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/update-browserslist-db/node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC", + "peer": true + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "punycode": "^2.1.0" } @@ -3734,11 +6340,34 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -3749,6 +6378,98 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", + "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/workerpool": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", @@ -3776,7 +6497,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -3790,12 +6510,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, "node_modules/y18n": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", @@ -3804,12 +6518,6 @@ "node": ">=10" } }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", diff --git a/package.json b/package.json index 9938b0ad..2c56a5de 100644 --- a/package.json +++ b/package.json @@ -41,37 +41,30 @@ "devDependencies": { "@alcalzone/release-script": "^3.8.0", "@alcalzone/release-script-plugin-license": "^3.7.0", - "@eslint/js": "^9.17.0", - "@eslint/eslintrc": "^3.1.0", - "@iobroker/adapter-core": "^3.2.2", - "@iobroker/types": "^6.0.11", - "@tsconfig/node14": "^14.1.2", - "@types/chai": "^4.3.19", - "@types/chai-as-promised": "^8.0.0", + "@iobroker/adapter-core": "^3.2.3", + "@iobroker/eslint-config": "^1.0.0", + "@iobroker/types": "^7.0.6", + "@tsconfig/node16": "^16.1.3", + "@types/chai": "^4.3.20", + "@types/chai-as-promised": "^8.0.1", "@types/debug": "4.1.12", "@types/fs-extra": "^11.0.4", - "@types/mocha": "^10.0.8", - "@types/node": "^22.10.1", + "@types/mocha": "^10.0.10", + "@types/node": "^22.10.7", "@types/sinon": "^17.0.3", "@types/sinon-chai": "^3.2.12", - "@typescript-eslint/eslint-plugin": "^8.16.0", - "@typescript-eslint/parser": "^8.19.0", - "eslint": "^9.17.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.2.1", - "prettier": "^3.3.3", "rimraf": "^6.0.1", "source-map-support": "^0.5.21", "ts-node": "^10.9.2", - "typescript": "~5.7.2" + "typescript": "~5.7.3" }, "dependencies": { - "alcalzone-shared": "~4.0.8", + "alcalzone-shared": "~5.0.0", "chai": "^4.5.0", "chai-as-promised": "^7.1.2", - "debug": "^4.3.7", - "fs-extra": "^11.2.0", - "mocha": "^10.7.3", + "debug": "^4.4.0", + "fs-extra": "^11.3.0", + "mocha": "^11.0.1", "sinon": "^19.0.2", "sinon-chai": "^3.7.0" } diff --git a/prettier.config.mjs b/prettier.config.mjs new file mode 100644 index 00000000..2f007081 --- /dev/null +++ b/prettier.config.mjs @@ -0,0 +1,3 @@ +import prettierConfig from '@iobroker/eslint-config/prettier.config.mjs'; + +export default prettierConfig; diff --git a/src/index.ts b/src/index.ts index 5f6f0849..6d14afbc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1 @@ -export * from "./tests"; +export * from './tests'; diff --git a/src/lib/adapterTools.ts b/src/lib/adapterTools.ts index b54888a3..019b443f 100644 --- a/src/lib/adapterTools.ts +++ b/src/lib/adapterTools.ts @@ -1,17 +1,18 @@ // Add debug logging for tests -import { isArray, isObject } from "alcalzone-shared/typeguards"; -import debugModule from "debug"; -import { pathExists } from "fs-extra"; -import * as path from "path"; -const debug = debugModule("testing:unit:adapterTools"); +// @ts-expect-error no types +import { isArray, isObject } from 'alcalzone-shared/typeguards'; +import debugModule from 'debug'; +import { pathExists } from 'fs-extra'; +import * as path from 'path'; +const debug = debugModule('testing:unit:adapterTools'); /** * Loads an adapter's package.json * @param adapterDir The directory the adapter resides in */ export function loadNpmPackage(adapterDir: string): Record { - // eslint-disable-next-line @typescript-eslint/no-require-imports - return require(path.join(adapterDir, "package.json")); + // eslint-disable-next-line @typescript-eslint/no-require-imports + return require(path.join(adapterDir, 'package.json')); } /** @@ -19,52 +20,48 @@ export function loadNpmPackage(adapterDir: string): Record { * @param adapterDir The directory the adapter resides in */ export function loadIoPackage(adapterDir: string): Record { - // eslint-disable-next-line @typescript-eslint/no-require-imports - return require(path.join(adapterDir, "io-package.json")); + // eslint-disable-next-line @typescript-eslint/no-require-imports + return require(path.join(adapterDir, 'io-package.json')); } -export function getAdapterExecutionMode( - adapterDir: string, -): ioBroker.AdapterCommon["mode"] { - const ioPackage = loadIoPackage(adapterDir); - return ioPackage.common.mode; +export function getAdapterExecutionMode(adapterDir: string): ioBroker.AdapterCommon['mode'] { + const ioPackage = loadIoPackage(adapterDir); + return ioPackage.common.mode; } /** * Locates an adapter's main file * @param adapterDir The directory the adapter resides in */ -export async function locateAdapterMainFile( - adapterDir: string, -): Promise { - debug(`locating adapter main file in ${adapterDir}...`); - const ioPackage = loadIoPackage(adapterDir); - const npmPackage = loadNpmPackage(adapterDir); +export async function locateAdapterMainFile(adapterDir: string): Promise { + debug(`locating adapter main file in ${adapterDir}...`); + const ioPackage = loadIoPackage(adapterDir); + const npmPackage = loadNpmPackage(adapterDir); - // First look for the file defined in io-package.json or package.json or use "main.js" as a fallback - const mainFile = - typeof ioPackage.common.main === "string" - ? ioPackage.common.main - : typeof npmPackage.main === "string" - ? npmPackage.main - : "main.js"; + // First look for the file defined in io-package.json or package.json or use "main.js" as a fallback + const mainFile = + typeof ioPackage.common.main === 'string' + ? ioPackage.common.main + : typeof npmPackage.main === 'string' + ? npmPackage.main + : 'main.js'; - let ret = path.join(adapterDir, mainFile); - debug(` => trying ${ret}`); - if (await pathExists(ret)) { - debug(` => found ${mainFile}`); - return ret; - } + let ret = path.join(adapterDir, mainFile); + debug(` => trying ${ret}`); + if (await pathExists(ret)) { + debug(` => found ${mainFile}`); + return ret; + } - // If both don't exist, JS-Controller uses .js as another fallback - ret = path.join(adapterDir, ioPackage.common.name + ".js"); - debug(` => trying ${ret}`); - if (await pathExists(ret)) { - debug(` => found ${mainFile}`); - return ret; - } + // If both don't exist, JS-Controller uses .js as another fallback + ret = path.join(adapterDir, ioPackage.common.name + '.js'); + debug(` => trying ${ret}`); + if (await pathExists(ret)) { + debug(` => found ${mainFile}`); + return ret; + } - throw new Error(`The adapter main file was not found in ${adapterDir}`); + throw new Error(`The adapter main file was not found in ${adapterDir}`); } /** @@ -72,8 +69,8 @@ export async function locateAdapterMainFile( * @param adapterDir The directory the adapter resides in */ export function loadAdapterConfig(adapterDir: string): Record { - const ioPackage = loadIoPackage(adapterDir); - return ioPackage.native || {}; + const ioPackage = loadIoPackage(adapterDir); + return ioPackage.native || {}; } /** @@ -81,8 +78,8 @@ export function loadAdapterConfig(adapterDir: string): Record { * @param adapterDir The directory the adapter resides in */ export function loadAdapterCommon(adapterDir: string): Record { - const ioPackage = loadIoPackage(adapterDir); - return ioPackage.common || {}; + const ioPackage = loadIoPackage(adapterDir); + return ioPackage.common || {}; } /** @@ -90,44 +87,41 @@ export function loadAdapterCommon(adapterDir: string): Record { * @param adapterDir The directory the adapter resides in */ export function loadInstanceObjects(adapterDir: string): ioBroker.Object[] { - const ioPackage = loadIoPackage(adapterDir); - return ioPackage.instanceObjects || []; + const ioPackage = loadIoPackage(adapterDir); + return ioPackage.instanceObjects || []; } /** Returns the branded name of "iobroker" */ export function getAppName(adapterDir: string): string { - const npmPackage = loadNpmPackage(adapterDir); - return npmPackage.name.split(".")[0] || "iobroker"; + const npmPackage = loadNpmPackage(adapterDir); + return npmPackage.name.split('.')[0] || 'iobroker'; } /** Returns the name of an adapter without the prefix */ export function getAdapterName(adapterDir: string): string { - const ioPackage = loadIoPackage(adapterDir); - return ioPackage.common.name; + const ioPackage = loadIoPackage(adapterDir); + return ioPackage.common.name; } /** Returns the full name of an adapter, including the prefix */ export function getAdapterFullName(adapterDir: string): string { - const npmPackage = loadNpmPackage(adapterDir); - return npmPackage.name; + const npmPackage = loadNpmPackage(adapterDir); + return npmPackage.name; } /** Reads other ioBroker modules this adapter depends on from io-package.json */ -export function getAdapterDependencies( - adapterDir: string, -): Record { - const ioPackage = loadIoPackage(adapterDir); - const ret: Record = {}; - if (isArray(ioPackage.common.dependencies)) { - for (const dep of ioPackage.common.dependencies) { - if (typeof dep === "string") { - ret[dep] = "latest"; - } else if (isObject(dep)) { - const key = Object.keys(dep)[0]; - if (key) - ret[key] = (dep as Record)[key] || "latest"; - } - } - } - return ret; +export function getAdapterDependencies(adapterDir: string): Record { + const ioPackage = loadIoPackage(adapterDir); + const ret: Record = {}; + if (isArray(ioPackage.common.dependencies)) { + for (const dep of ioPackage.common.dependencies) { + if (typeof dep === 'string') { + ret[dep] = 'latest'; + } else if (isObject(dep)) { + const key = Object.keys(dep)[0]; + if (key) ret[key] = (dep as Record)[key] || 'latest'; + } + } + } + return ret; } diff --git a/src/lib/executeCommand.ts b/src/lib/executeCommand.ts index 344bdd4a..7aa82e90 100644 --- a/src/lib/executeCommand.ts +++ b/src/lib/executeCommand.ts @@ -1,35 +1,34 @@ -import { isArray, isObject } from "alcalzone-shared/typeguards"; -import { SpawnOptions, spawn } from "child_process"; +import { SpawnOptions, spawn } from 'child_process'; const isWindows = /^win/.test(process.platform); export interface ExecuteCommandOptions { - /** Whether the executed command should be logged to the stdout. Default: false */ - logCommandExecution: boolean; - /** The directory to execute the command in */ - cwd: string; - /** Where to redirect the stdin. Default: process.stdin */ - stdin: NodeJS.ReadStream; - /** A write stream to redirect the stdout, "ignore" to ignore it or "pipe" to return it as a string. Default: process.stdout */ - stdout: NodeJS.WriteStream | "pipe" | "ignore"; - /** A write stream to redirect the stderr, "ignore" to ignore it or "pipe" to return it as a string. Default: process.stderr */ - stderr: NodeJS.WriteStream | "pipe" | "ignore"; + /** Whether the executed command should be logged to the stdout. Default: false */ + logCommandExecution: boolean; + /** The directory to execute the command in */ + cwd: string; + /** Where to redirect the stdin. Default: process.stdin */ + stdin: NodeJS.ReadStream; + /** A write stream to redirect the stdout, "ignore" to ignore it or "pipe" to return it as a string. Default: process.stdout */ + stdout: NodeJS.WriteStream | 'pipe' | 'ignore'; + /** A write stream to redirect the stderr, "ignore" to ignore it or "pipe" to return it as a string. Default: process.stderr */ + stderr: NodeJS.WriteStream | 'pipe' | 'ignore'; } export interface ExecuteCommandResult { - /** The exit code of the spawned process */ - exitCode?: number; - /** The signal the process received before termination */ - signal?: string; - /** If options.stdout was set to "buffer", this contains the stdout of the spawned process */ - stdout?: string; - /** If options.stderr was set to "buffer", this contains the stderr of the spawned process */ - stderr?: string; + /** The exit code of the spawned process */ + exitCode?: number; + /** The signal the process received before termination */ + signal?: string; + /** If options.stdout was set to "buffer", this contains the stdout of the spawned process */ + stdout?: string; + /** If options.stderr was set to "buffer", this contains the stderr of the spawned process */ + stderr?: string; } export function executeCommand( - command: string, - options?: Partial, + command: string, + options?: Partial, ): Promise; /** * Executes a command and returns the exit code and (if requested) the stdout @@ -38,86 +37,78 @@ export function executeCommand( * @param options (optional) Some options for the command execution */ export function executeCommand( - command: string, - args: string[], - options?: Partial, + command: string, + args: string[], + options?: Partial, ): Promise; export function executeCommand( - command: string, - argsOrOptions?: string[] | Partial, - options?: Partial, + command: string, + argsOrOptions?: string[] | Partial, + options?: Partial, ): Promise { - return new Promise((resolve) => { - let args: string[] | undefined; - if (isArray(argsOrOptions)) { - args = argsOrOptions as string[]; - } else if (isObject(argsOrOptions)) { - // no args were given - options = argsOrOptions; - } - if (options == null) options = {}; - if (args == null) args = []; + return new Promise(resolve => { + let args: string[] | undefined; + if (Array.isArray(argsOrOptions)) { + args = argsOrOptions as string[]; + } else if (argsOrOptions && typeof argsOrOptions === 'object') { + // no args were given + options = argsOrOptions; + } + if (options == null) options = {}; + if (args == null) args = []; - const spawnOptions: SpawnOptions = { - stdio: [ - options.stdin || process.stdin, - options.stdout || process.stdout, - options.stderr || process.stderr, - ], - windowsHide: true, - }; - if (options.cwd != null) spawnOptions.cwd = options.cwd; + const spawnOptions: SpawnOptions = { + stdio: [options.stdin || process.stdin, options.stdout || process.stdout, options.stderr || process.stderr], + windowsHide: true, + }; + if (options.cwd != null) spawnOptions.cwd = options.cwd; - // Fix npm / node executable paths on Windows - if (isWindows) { - if (command === "npm") { - command += ".cmd"; - // Needed since Node.js v18.20.2 and v20.12.2 - // https://github.com/nodejs/node/releases/tag/v18.20.2 - spawnOptions.shell = true; - } else if (command === "node") { - command += ".exe"; - } - } + // Fix npm / node executable paths on Windows + if (isWindows) { + if (command === 'npm') { + command += '.cmd'; + // Needed since Node.js v18.20.2 and v20.12.2 + // https://github.com/nodejs/node/releases/tag/v18.20.2 + spawnOptions.shell = true; + } else if (command === 'node') { + command += '.exe'; + } + } - if (options.logCommandExecution == null) - options.logCommandExecution = false; - if (options.logCommandExecution) { - console.log("executing: " + `${command} ${args.join(" ")}`); - } + if (options.logCommandExecution == null) options.logCommandExecution = false; + if (options.logCommandExecution) { + console.log('executing: ' + `${command} ${args.join(' ')}`); + } - // Now execute the npm process and avoid throwing errors - try { - let bufferedStdout: string | undefined; - let bufferedStderr: string | undefined; - const cmd = spawn(command, args, spawnOptions).on( - "close", - (code, signal) => { - resolve({ - exitCode: code ?? undefined, - signal: signal ?? undefined, - stdout: bufferedStdout, - stderr: bufferedStderr, - }); - }, - ); - // Capture stdout/stderr if requested - if (options.stdout === "pipe") { - bufferedStdout = ""; - cmd.stdout!.on("data", (chunk: Buffer | string) => { - if (Buffer.isBuffer(chunk)) chunk = chunk.toString("utf8"); - bufferedStdout! += chunk; - }); - } - if (options.stderr === "pipe") { - bufferedStderr = ""; - cmd.stderr!.on("data", (chunk: Buffer | string) => { - if (Buffer.isBuffer(chunk)) chunk = chunk.toString("utf8"); - bufferedStderr! += chunk; - }); - } - } catch { - // doesn't matter, we return the exit code in the "close" handler - } - }); + // Now execute the npm process and avoid throwing errors + try { + let bufferedStdout: string | undefined; + let bufferedStderr: string | undefined; + const cmd = spawn(command, args, spawnOptions).on('close', (code, signal) => { + resolve({ + exitCode: code ?? undefined, + signal: signal ?? undefined, + stdout: bufferedStdout, + stderr: bufferedStderr, + }); + }); + // Capture stdout/stderr if requested + if (options.stdout === 'pipe') { + bufferedStdout = ''; + cmd.stdout!.on('data', (chunk: Buffer | string) => { + if (Buffer.isBuffer(chunk)) chunk = chunk.toString('utf8'); + bufferedStdout! += chunk; + }); + } + if (options.stderr === 'pipe') { + bufferedStderr = ''; + cmd.stderr!.on('data', (chunk: Buffer | string) => { + if (Buffer.isBuffer(chunk)) chunk = chunk.toString('utf8'); + bufferedStderr! += chunk; + }); + } + } catch { + // doesn't matter, we return the exit code in the "close" handler + } + }); } diff --git a/src/lib/str2regex.ts b/src/lib/str2regex.ts index a8d1c6be..9426d01c 100644 --- a/src/lib/str2regex.ts +++ b/src/lib/str2regex.ts @@ -1,9 +1,9 @@ export function str2regex(pattern: string): RegExp { - return new RegExp( - pattern - .replace(/\\/g, "\\\\") // Backslashes escapen - .replace(/\./g, "\\.") // Punkte als solche matchen - .replace(/\*/g, ".*") // Wildcard in Regex umsetzen - .replace(/!/g, "?!"), // negative lookahead - ); + return new RegExp( + pattern + .replace(/\\/g, '\\\\') // Backslashes escapen + .replace(/\./g, '\\.') // Punkte als solche matchen + .replace(/\*/g, '.*') // Wildcard in Regex umsetzen + .replace(/!/g, '?!'), // negative lookahead + ); } diff --git a/src/tests/index.ts b/src/tests/index.ts index 58ae3b2e..49057e2c 100644 --- a/src/tests/index.ts +++ b/src/tests/index.ts @@ -1,27 +1,27 @@ -import { testAdapter } from "./integration"; -import { validatePackageFiles } from "./packageFiles"; -import { testAdapterWithMocks } from "./unit"; -import { createMocks } from "./unit/harness/createMocks"; -import { createAsserts } from "./unit/mocks/mockDatabase"; +import { testAdapter } from './integration'; +import { validatePackageFiles } from './packageFiles'; +import { testAdapterWithMocks } from './unit'; +import { createMocks } from './unit/harness/createMocks'; +import { createAsserts } from './unit/mocks/mockDatabase'; -export { TestHarness as IntegrationTestHarness } from "./integration/lib/harness"; -export { MockAdapter } from "./unit/mocks/mockAdapter"; -export { MockDatabase } from "./unit/mocks/mockDatabase"; +export { TestHarness as IntegrationTestHarness } from './integration/lib/harness'; +export { MockAdapter } from './unit/mocks/mockAdapter'; +export { MockDatabase } from './unit/mocks/mockDatabase'; /** Predefined test sets */ export const tests = { - /** @deprecated Adapter startup unit tests are no longer supported */ - unit: testAdapterWithMocks, - integration: testAdapter, - packageFiles: validatePackageFiles, + /** @deprecated Adapter startup unit tests are no longer supported */ + unit: testAdapterWithMocks, + integration: testAdapter, + packageFiles: validatePackageFiles, }; /** Utilities for your own tests */ export const utils = { - unit: { - createMocks, - createAsserts, - /** @deprecated Adapter startup unit tests are no longer supported */ - startMockAdapter: () => ({}), - }, + unit: { + createMocks, + createAsserts, + /** @deprecated Adapter startup unit tests are no longer supported */ + startMockAdapter: () => ({}), + }, }; diff --git a/src/tests/integration/index.ts b/src/tests/integration/index.ts index 3fd10ef7..975cc5ef 100644 --- a/src/tests/integration/index.ts +++ b/src/tests/integration/index.ts @@ -1,291 +1,260 @@ -import { wait } from "alcalzone-shared/async"; -import * as os from "os"; -import * as path from "path"; -import { getAdapterName, getAppName } from "../../lib/adapterTools"; -import { AdapterSetup } from "./lib/adapterSetup"; -import { ControllerSetup } from "./lib/controllerSetup"; -import { DBConnection } from "./lib/dbConnection"; -import { TestHarness } from "./lib/harness"; -import { createLogger } from "./lib/logger"; +import * as os from 'os'; +import * as path from 'path'; +import { getAdapterName, getAppName } from '../../lib/adapterTools'; +import { AdapterSetup } from './lib/adapterSetup'; +import { ControllerSetup } from './lib/controllerSetup'; +import { DBConnection } from './lib/dbConnection'; +import { TestHarness } from './lib/harness'; +import { createLogger } from './lib/logger'; export interface TestAdapterOptions { - allowedExitCodes?: (number | string)[]; - /** The loglevel to use for DB and adapter related logs */ - loglevel?: ioBroker.LogLevel; - /** How long to wait before the adapter startup is considered successful */ - waitBeforeStartupSuccess?: number; - /** - * Which JS-Controller version or dist-tag should be used for the tests. Default: dev - * This should only be changed during active development. - */ - controllerVersion?: string; - /** Allows you to define additional tests */ - defineAdditionalTests?: (args: TestContext) => void; + allowedExitCodes?: (number | string)[]; + /** The loglevel to use for DB and adapter related logs */ + loglevel?: ioBroker.LogLevel; + /** How long to wait before the adapter startup is considered successful */ + waitBeforeStartupSuccess?: number; + /** + * Which JS-Controller version or dist-tag should be used for the tests. Default: dev + * This should only be changed during active development. + */ + controllerVersion?: string; + /** Allows you to define additional tests */ + defineAdditionalTests?: (args: TestContext) => void; } export interface TestSuiteFn { - (name: string, fn: (getHarness: () => TestHarness) => void): void; + (name: string, fn: (getHarness: () => TestHarness) => void): void; } export interface TestSuite extends TestSuiteFn { - /** Only runs the tests inside this `suite` for the current file */ - only: TestSuiteFn; - /** Skips running the tests inside this `suite` for the current file */ - skip: TestSuiteFn; + /** Only runs the tests inside this `suite` for the current file */ + only: TestSuiteFn; + /** Skips running the tests inside this `suite` for the current file */ + skip: TestSuiteFn; } export interface TestContext { - /** - * Defines a test suite. At the start of each suite, the adapter will be started with a fresh environment. - * To define tests in each suite, use describe and it as usual. - * - * Each suite has its own test harness, which can be retrieved using the function that is passed to the suite callback. - */ - suite: TestSuite; - - describe: Mocha.SuiteFunction; - it: Mocha.TestFunction; + /** + * Defines a test suite. At the start of each suite, the adapter will be started with a fresh environment. + * To define tests in each suite, use describe and it as usual. + * + * Each suite has its own test harness, which can be retrieved using the function that is passed to the suite callback. + */ + suite: TestSuite; + + describe: Mocha.SuiteFunction; + it: Mocha.TestFunction; } -export function testAdapter( - adapterDir: string, - options: TestAdapterOptions = {}, -): void { - const appName = getAppName(adapterDir); - const adapterName = getAdapterName(adapterDir); - const testDir = path.join(os.tmpdir(), `test-${appName}.${adapterName}`); - - /** This db connection is only used for the lifetime of a test and then re-created */ - let dbConnection: DBConnection; - let harness: TestHarness; - const controllerSetup = new ControllerSetup(adapterDir, testDir); - - let objectsBackup: Buffer; - let statesBackup: Buffer; - - let isInSuite = false; - - console.log(); - console.log(`Running tests in ${testDir}`); - console.log(); - - async function prepareTests(this: Mocha.Context): Promise { - // Installation may take a while - especially if rsa-compat needs to be installed - const oneMinute = 60000; - this.timeout(30 * oneMinute); - - if (await controllerSetup.isJsControllerRunning()) { - throw new Error( - "JS-Controller is already running! Stop it for the first test run and try again!", - ); - } - - const adapterSetup = new AdapterSetup(adapterDir, testDir); - - // Installation happens in two steps: - // First we need to set up JS Controller, so the databases etc. can be created - - // First we need to copy all files and execute an npm install - await controllerSetup.prepareTestDir(options.controllerVersion); - // Only then we can install the adapter, because some (including VIS) try to access - // the databases if JS Controller is installed - await adapterSetup.installAdapterInTestDir(); - - const dbConnection = new DBConnection( - appName, - testDir, - createLogger(options.loglevel ?? "debug"), - ); - await dbConnection.start(); - controllerSetup.setupSystemConfig(dbConnection); - await controllerSetup.disableAdminInstances(dbConnection); - - await adapterSetup.deleteOldInstances(dbConnection); - await adapterSetup.addAdapterInstance(); - await dbConnection.stop(); - - // Create a copy of the databases that we can restore later - ({ objects: objectsBackup, states: statesBackup } = - await dbConnection.backup()); - } - - async function shutdownTests(this: Mocha.Context): Promise { - // Stopping the processes may take a while - this.timeout(30000); - // Stop the controller again - await harness.stopController(); - - harness.removeAllListeners(); - } - - async function resetDbAndStartHarness(this: Mocha.Context): Promise { - this.timeout(30000); - - dbConnection = new DBConnection( - appName, - testDir, - createLogger(options.loglevel ?? "debug"), - ); - - // Clean up before every single test - await Promise.all([ - controllerSetup.clearDBDir(), - controllerSetup.clearLogDir(), - dbConnection.restore(objectsBackup, statesBackup), - ]); - - // Create a new test harness - await dbConnection.start(); - harness = new TestHarness(adapterDir, testDir, dbConnection); - - // Enable the adapter and set its loglevel to the selected one - await harness.changeAdapterConfig(adapterName, { - common: { - enabled: true, - loglevel: options.loglevel ?? "debug", - }, - }); - - // And enable the sendTo emulation - await harness.enableSendTo(); - } - - describe(`Adapter integration tests`, () => { - before(prepareTests); - - describe("Adapter startup", () => { - beforeEach(resetDbAndStartHarness); - afterEach(shutdownTests); - - it("The adapter starts", function () { - this.timeout(60000); - - const allowedExitCodes = new Set( - options.allowedExitCodes ?? [], - ); - - // Adapters with these modes are allowed to "immediately" exit with code 0 - switch (harness.getAdapterExecutionMode()) { - case "schedule": - case "once": - // @ts-expect-error subscribe was deprecated - case "subscribe": - allowedExitCodes.add(0); - } - - return new Promise((resolve, reject) => { - // Register a handler to check the alive state and exit codes - harness - .on("stateChange", async (id, state) => { - if ( - id === - `system.adapter.${adapterName}.0.alive` && - state && - state.val === true - ) { - // Wait a bit so we can catch errors that do not happen immediately - await wait( - options.waitBeforeStartupSuccess != - undefined - ? options.waitBeforeStartupSuccess - : 5000, - ); - resolve(`The adapter started successfully.`); - } - }) - .on("failed", (code) => { - if (!allowedExitCodes.has(code)) { - reject( - new Error( - `The adapter startup was interrupted unexpectedly with ${ - typeof code === "number" - ? "code" - : "signal" - } ${code}`, - ), - ); - } else { - // This was a valid exit code - resolve( - `The expected ${ - typeof code === "number" - ? "exit code" - : "signal" - } ${code} was received.`, - ); - } - }); - harness.startAdapter(); - }).then((msg) => console.log(msg)); - }); - }); - - // Call the user's tests - if (typeof options.defineAdditionalTests === "function") { - const originalIt = global.it; - - // Ensure no it() gets called outside of a suite() - function assertSuite(): void { - if (!isInSuite) { - throw new Error( - "In user-defined adapter tests, it() must NOT be called outside of a suite()", - ); - } - } - const patchedIt = new Proxy(originalIt, { - apply(target, thisArg, args) { - assertSuite(); - return target.apply(thisArg, args as any); - }, - get(target, propKey) { - assertSuite(); - return (target as any)[propKey]; - }, - }); - - describe("User-defined tests", () => { - // patch the global it() function so nobody can bypass the checks - global.it = patchedIt; - - // a test suite is a special describe which sets up and tears down the test environment before and after ALL tests - const suiteBody = ( - fn: (getHarness: () => TestHarness) => void, - ): void => { - isInSuite = true; - before(resetDbAndStartHarness); - - fn(() => harness); - - after(shutdownTests); - isInSuite = false; - }; - - const suite = (( - name: string, - fn: (getHarness: () => TestHarness) => void, - ): void => { - describe(name, () => suiteBody(fn)); - }) as TestSuite; - - // Support .skip and .only - suite.skip = (name, fn) => { - describe.skip(name, () => suiteBody(fn)); - }; - - suite.only = (name, fn) => { - describe.only(name, () => suiteBody(fn)); - }; - - const args: TestContext = { - suite, - describe, - it: patchedIt, - }; - - options.defineAdditionalTests!(args); - - global.it = originalIt; - }); - } - }); +export function testAdapter(adapterDir: string, options: TestAdapterOptions = {}): void { + const appName = getAppName(adapterDir); + const adapterName = getAdapterName(adapterDir); + const testDir = path.join(os.tmpdir(), `test-${appName}.${adapterName}`); + + /** This db connection is only used for the lifetime of a test and then re-created */ + let dbConnection: DBConnection; + let harness: TestHarness; + const controllerSetup = new ControllerSetup(adapterDir, testDir); + + let objectsBackup: Buffer; + let statesBackup: Buffer; + + let isInSuite = false; + + console.log(); + console.log(`Running tests in ${testDir}`); + console.log(); + + async function prepareTests(this: Mocha.Context): Promise { + // Installation may take a while - especially if rsa-compat needs to be installed + const oneMinute = 60000; + this.timeout(30 * oneMinute); + + if (await controllerSetup.isJsControllerRunning()) { + throw new Error('JS-Controller is already running! Stop it for the first test run and try again!'); + } + + const adapterSetup = new AdapterSetup(adapterDir, testDir); + + // Installation happens in two steps: + // First we need to set up JS Controller, so the databases etc. can be created + + // First we need to copy all files and execute an npm install + await controllerSetup.prepareTestDir(options.controllerVersion); + // Only then we can install the adapter, because some (including VIS) try to access + // the databases if JS Controller is installed + await adapterSetup.installAdapterInTestDir(); + + const dbConnection = new DBConnection(appName, testDir, createLogger(options.loglevel ?? 'debug')); + await dbConnection.start(); + controllerSetup.setupSystemConfig(dbConnection); + await controllerSetup.disableAdminInstances(dbConnection); + + await adapterSetup.deleteOldInstances(dbConnection); + await adapterSetup.addAdapterInstance(); + await dbConnection.stop(); + + // Create a copy of the databases that we can restore later + ({ objects: objectsBackup, states: statesBackup } = await dbConnection.backup()); + } + + async function shutdownTests(this: Mocha.Context): Promise { + // Stopping the processes may take a while + this.timeout(30000); + // Stop the controller again + await harness.stopController(); + + harness.removeAllListeners(); + } + + async function resetDbAndStartHarness(this: Mocha.Context): Promise { + this.timeout(30000); + + dbConnection = new DBConnection(appName, testDir, createLogger(options.loglevel ?? 'debug')); + + // Clean up before every single test + await Promise.all([ + controllerSetup.clearDBDir(), + controllerSetup.clearLogDir(), + dbConnection.restore(objectsBackup, statesBackup), + ]); + + // Create a new test harness + await dbConnection.start(); + harness = new TestHarness(adapterDir, testDir, dbConnection); + + // Enable the adapter and set its loglevel to the selected one + await harness.changeAdapterConfig(adapterName, { + common: { + enabled: true, + loglevel: options.loglevel ?? 'debug', + }, + }); + + // And enable the sendTo emulation + await harness.enableSendTo(); + } + + describe(`Adapter integration tests`, () => { + before(prepareTests); + + describe('Adapter startup', () => { + beforeEach(resetDbAndStartHarness); + afterEach(shutdownTests); + + it('The adapter starts', function () { + this.timeout(60000); + + const allowedExitCodes = new Set(options.allowedExitCodes ?? []); + + // Adapters with these modes are allowed to "immediately" exit with code 0 + switch (harness.getAdapterExecutionMode()) { + case 'schedule': + case 'once': + // @ts-expect-error subscribe was deprecated + case 'subscribe': + allowedExitCodes.add(0); + } + + return new Promise((resolve, reject) => { + // Register a handler to check the alive state and exit codes + harness + .on('stateChange', async (id, state) => { + if (id === `system.adapter.${adapterName}.0.alive` && state && state.val === true) { + // Wait a bit so we can catch errors that do not happen immediately + await new Promise(resolve => + setTimeout( + resolve, + options.waitBeforeStartupSuccess !== undefined + ? options.waitBeforeStartupSuccess + : 5000, + ), + ); + resolve(`The adapter started successfully.`); + } + }) + .on('failed', code => { + if (!allowedExitCodes.has(code)) { + reject( + new Error( + `The adapter startup was interrupted unexpectedly with ${ + typeof code === 'number' ? 'code' : 'signal' + } ${code}`, + ), + ); + } else { + // This was a valid exit code + resolve( + `The expected ${ + typeof code === 'number' ? 'exit code' : 'signal' + } ${code} was received.`, + ); + } + }); + harness.startAdapter(); + }).then(msg => console.log(msg)); + }); + }); + + // Call the user's tests + if (typeof options.defineAdditionalTests === 'function') { + const originalIt = global.it; + + // Ensure no it() gets called outside of a suite() + function assertSuite(): void { + if (!isInSuite) { + throw new Error('In user-defined adapter tests, it() must NOT be called outside of a suite()'); + } + } + const patchedIt = new Proxy(originalIt, { + apply(target, thisArg, args) { + assertSuite(); + return target.apply(thisArg, args as any); + }, + get(target, propKey) { + assertSuite(); + return (target as any)[propKey]; + }, + }); + + describe('User-defined tests', () => { + // patch the global it() function so nobody can bypass the checks + global.it = patchedIt; + + // a test suite is a special describe which sets up and tears down the test environment before and after ALL tests + const suiteBody = (fn: (getHarness: () => TestHarness) => void): void => { + isInSuite = true; + before(resetDbAndStartHarness); + + fn(() => harness); + + after(shutdownTests); + isInSuite = false; + }; + + const suite = ((name: string, fn: (getHarness: () => TestHarness) => void): void => { + describe(name, () => suiteBody(fn)); + }) as TestSuite; + + // Support .skip and .only + suite.skip = (name, fn) => { + describe.skip(name, () => suiteBody(fn)); + }; + + suite.only = (name, fn) => { + describe.only(name, () => suiteBody(fn)); + }; + + const args: TestContext = { + suite, + describe, + it: patchedIt, + }; + + options.defineAdditionalTests!(args); + + global.it = originalIt; + }); + } + }); } diff --git a/src/tests/integration/lib/adapterSetup.ts b/src/tests/integration/lib/adapterSetup.ts index 13ef2527..c13e5611 100644 --- a/src/tests/integration/lib/adapterSetup.ts +++ b/src/tests/integration/lib/adapterSetup.ts @@ -1,186 +1,143 @@ // Add debug logging for tests -import { entries } from "alcalzone-shared/objects"; -import debugModule from "debug"; -import { - copy, - pathExists, - readJSON, - remove, - unlink, - writeJSON, -} from "fs-extra"; -import * as path from "path"; -import { - getAdapterDependencies, - getAdapterFullName, - getAdapterName, - getAppName, -} from "../../../lib/adapterTools"; -import { executeCommand } from "../../../lib/executeCommand"; -import type { DBConnection } from "./dbConnection"; -import { getTestAdapterDir, getTestControllerDir } from "./tools"; -const debug = debugModule("testing:integration:AdapterSetup"); +// @ts-expect-error no types +import { entries } from 'alcalzone-shared/objects'; +import debugModule from 'debug'; +import { copy, pathExists, readJSON, remove, unlink, writeJSON } from 'fs-extra'; +import * as path from 'path'; +import { getAdapterDependencies, getAdapterFullName, getAdapterName, getAppName } from '../../../lib/adapterTools'; +import { executeCommand } from '../../../lib/executeCommand'; +import type { DBConnection } from './dbConnection'; +import { getTestAdapterDir, getTestControllerDir } from './tools'; +const debug = debugModule('testing:integration:AdapterSetup'); export class AdapterSetup { - public constructor( - private adapterDir: string, - private testDir: string, - ) { - debug("Creating AdapterSetup..."); - - this.adapterName = getAdapterName(this.adapterDir); - this.adapterFullName = getAdapterFullName(this.adapterDir); - this.appName = getAppName(this.adapterDir); - this.testAdapterDir = getTestAdapterDir(this.adapterDir, this.testDir); - this.testControllerDir = getTestControllerDir( - this.appName, - this.testDir, - ); - - debug(` directories:`); - debug(` controller: ${this.testControllerDir}`); - debug(` adapter: ${this.testAdapterDir}`); - debug(` appName: ${this.appName}`); - debug(` adapterName: ${this.adapterName}`); - } - - private testAdapterDir: string; - private adapterName: string; - private adapterFullName: string; - private appName: string; - private testControllerDir: string; - - /** - * Tests if the adapter is already installed in the test directory - */ - public async isAdapterInstalled(): Promise { - // We expect the adapter to be installed if the dir in /node_modules exists - return pathExists(this.testAdapterDir); - } - - /** Copies all adapter files (except a few) to the test directory */ - public async installAdapterInTestDir(): Promise { - debug("Copying adapter files to test directory..."); - - // We install the adapter almost like it would be installed in the real world - // Therefore pack it into a tarball and put it in the test dir for installation - const packResult = await executeCommand( - "npm", - ["pack", "--loglevel", "silent"], - { - stdout: "pipe", - }, - ); - if (packResult.exitCode !== 0 || typeof packResult.stdout !== "string") - throw new Error(`Packing the adapter tarball failed!`); - - // The last non-empty line of `npm pack`s STDOUT contains the tarball path - const stdoutLines = packResult.stdout.trim().split(/[\r\n]+/); - const tarballName = stdoutLines[stdoutLines.length - 1].trim(); - const tarballPath = path.resolve(this.adapterDir, tarballName); - await copy(tarballPath, path.resolve(this.testDir, tarballName)); - await unlink(tarballPath); - - // Let npm remove the adapter in the package-lock.json file(s), - // so that the installation in the following step - // won't grab the cached files. - // See https://github.com/ioBroker/testing/issues/612 - debug("Removing the adapter from package-lock.json"); - await executeCommand( - "npm", - [ - "uninstall", - this.adapterFullName, - "--package-lock-only", - "--omit=dev", - ], - { - cwd: this.testDir, - }, - ); - - // Complete the package.json, so npm can do it's magic - debug("Saving the adapter in package.json"); - const packageJsonPath = path.join(this.testDir, "package.json"); - const packageJson = await readJSON(packageJsonPath); - packageJson.dependencies[this.adapterFullName] = - `file:./${tarballName}`; - for (const [dep, version] of entries( - getAdapterDependencies(this.adapterDir), - )) { - // Don't overwrite the js-controller github dependency with a probably lower one - if (dep === "js-controller") continue; - packageJson.dependencies[`${this.appName}.${dep}`] = version; - } - await writeJSON(packageJsonPath, packageJson, { spaces: 2 }); - - debug("Deleting old remains of this adapter"); - if (await pathExists(this.testAdapterDir)) - await remove(this.testAdapterDir); - - debug("Installing adapter"); - // Defer to npm to install the controller (if it wasn't already) - await executeCommand("npm", ["i", "--omit=dev"], { - cwd: this.testDir, - }); - - debug(" => done!"); - } - - /** - * Adds an instance for an already installed adapter in the test directory - */ - public async addAdapterInstance(): Promise { - debug("Adding adapter instance..."); - - // execute iobroker add -- This also installs missing dependencies - const addResult = await executeCommand( - "node", - [ - `${this.appName}.js`, - "add", - this.adapterName, - "--enabled", - "false", - ], - { - cwd: this.testControllerDir, - stdout: "ignore", - }, - ); - if (addResult.exitCode !== 0) - throw new Error(`Adding the adapter instance failed!`); - debug(" => done!"); - } - - public async deleteOldInstances(dbConnection: DBConnection): Promise { - debug("Removing old adapter instances..."); - - const allKeys = new Set([ - ...(await dbConnection.getObjectIDs()), - ...(await dbConnection.getStateIDs()), - ]); - - const instanceRegex = new RegExp( - `^system\\.adapter\\.${this.adapterName}\\.\\d+`, - ); - const instanceObjsRegex = new RegExp(`^${this.adapterName}\\.\\d+\.`); - - const belongsToAdapter = (id: string): boolean => { - return ( - instanceRegex.test(id) || - instanceObjsRegex.test(id) || - id === this.adapterName || - id === `${this.adapterName}.admin` - ); - }; - - const idsToDelete = [...allKeys].filter((id) => belongsToAdapter(id)); - for (const id of idsToDelete) { - await dbConnection.delObject(id).catch(() => {}); - await dbConnection.delState(id).catch(() => {}); - } - - debug(" => done!"); - } + public constructor( + private adapterDir: string, + private testDir: string, + ) { + debug('Creating AdapterSetup...'); + + this.adapterName = getAdapterName(this.adapterDir); + this.adapterFullName = getAdapterFullName(this.adapterDir); + this.appName = getAppName(this.adapterDir); + this.testAdapterDir = getTestAdapterDir(this.adapterDir, this.testDir); + this.testControllerDir = getTestControllerDir(this.appName, this.testDir); + + debug(` directories:`); + debug(` controller: ${this.testControllerDir}`); + debug(` adapter: ${this.testAdapterDir}`); + debug(` appName: ${this.appName}`); + debug(` adapterName: ${this.adapterName}`); + } + + private testAdapterDir: string; + private adapterName: string; + private adapterFullName: string; + private appName: string; + private testControllerDir: string; + + /** + * Tests if the adapter is already installed in the test directory + */ + public async isAdapterInstalled(): Promise { + // We expect the adapter to be installed if the dir in /node_modules exists + return pathExists(this.testAdapterDir); + } + + /** Copies all adapter files (except a few) to the test directory */ + public async installAdapterInTestDir(): Promise { + debug('Copying adapter files to test directory...'); + + // We install the adapter almost like it would be installed in the real world + // Therefore pack it into a tarball and put it in the test dir for installation + const packResult = await executeCommand('npm', ['pack', '--loglevel', 'silent'], { + stdout: 'pipe', + }); + if (packResult.exitCode !== 0 || typeof packResult.stdout !== 'string') + throw new Error(`Packing the adapter tarball failed!`); + + // The last non-empty line of `npm pack`s STDOUT contains the tarball path + const stdoutLines = packResult.stdout.trim().split(/[\r\n]+/); + const tarballName = stdoutLines[stdoutLines.length - 1].trim(); + const tarballPath = path.resolve(this.adapterDir, tarballName); + await copy(tarballPath, path.resolve(this.testDir, tarballName)); + await unlink(tarballPath); + + // Let npm remove the adapter in the package-lock.json file(s), + // so that the installation in the following step + // won't grab the cached files. + // See https://github.com/ioBroker/testing/issues/612 + debug('Removing the adapter from package-lock.json'); + await executeCommand('npm', ['uninstall', this.adapterFullName, '--package-lock-only', '--omit=dev'], { + cwd: this.testDir, + }); + + // Complete the package.json, so npm can do it's magic + debug('Saving the adapter in package.json'); + const packageJsonPath = path.join(this.testDir, 'package.json'); + const packageJson = await readJSON(packageJsonPath); + packageJson.dependencies[this.adapterFullName] = `file:./${tarballName}`; + for (const [dep, version] of entries(getAdapterDependencies(this.adapterDir))) { + // Don't overwrite the js-controller GitHub dependency with a probably lower one + if (dep === 'js-controller') continue; + packageJson.dependencies[`${this.appName}.${dep}`] = version; + } + await writeJSON(packageJsonPath, packageJson, { spaces: 2 }); + + debug('Deleting old remains of this adapter'); + if (await pathExists(this.testAdapterDir)) await remove(this.testAdapterDir); + + debug('Installing adapter'); + // Defer to npm to install the controller (if it wasn't already) + await executeCommand('npm', ['i', '--omit=dev'], { + cwd: this.testDir, + }); + + debug(' => done!'); + } + + /** + * Adds an instance for an already installed adapter in the test directory + */ + public async addAdapterInstance(): Promise { + debug('Adding adapter instance...'); + + // execute iobroker add -- This also installs missing dependencies + const addResult = await executeCommand( + 'node', + [`${this.appName}.js`, 'add', this.adapterName, '--enabled', 'false'], + { + cwd: this.testControllerDir, + stdout: 'ignore', + }, + ); + if (addResult.exitCode !== 0) throw new Error(`Adding the adapter instance failed!`); + debug(' => done!'); + } + + public async deleteOldInstances(dbConnection: DBConnection): Promise { + debug('Removing old adapter instances...'); + + const allKeys = new Set([...(await dbConnection.getObjectIDs()), ...(await dbConnection.getStateIDs())]); + + const instanceRegex = new RegExp(`^system\\.adapter\\.${this.adapterName}\\.\\d+`); + const instanceObjsRegex = new RegExp(`^${this.adapterName}\\.\\d+\.`); + + const belongsToAdapter = (id: string): boolean => { + return ( + instanceRegex.test(id) || + instanceObjsRegex.test(id) || + id === this.adapterName || + id === `${this.adapterName}.admin` + ); + }; + + const idsToDelete = [...allKeys].filter(id => belongsToAdapter(id)); + for (const id of idsToDelete) { + await dbConnection.delObject(id).catch(() => {}); + await dbConnection.delState(id).catch(() => {}); + } + + debug(' => done!'); + } } diff --git a/src/tests/integration/lib/controllerSetup.ts b/src/tests/integration/lib/controllerSetup.ts index 5c8a8068..fdef99ff 100644 --- a/src/tests/integration/lib/controllerSetup.ts +++ b/src/tests/integration/lib/controllerSetup.ts @@ -1,271 +1,231 @@ // Add debug logging for tests -import debugModule from "debug"; -import { - emptyDir, - ensureDir, - pathExists, - unlink, - writeFile, - writeJSON, -} from "fs-extra"; -import { Socket } from "net"; -import * as path from "path"; -import { getAdapterName, getAppName } from "../../../lib/adapterTools"; -import { executeCommand } from "../../../lib/executeCommand"; -import type { DBConnection } from "./dbConnection"; -import { - getTestAdapterDir, - getTestControllerDir, - getTestDBDir, - getTestDataDir, - getTestLogDir, -} from "./tools"; +import debugModule from 'debug'; +import { emptyDir, ensureDir, pathExists, unlink, writeFile, writeJSON } from 'fs-extra'; +import { Socket } from 'net'; +import * as path from 'path'; +import { getAdapterName, getAppName } from '../../../lib/adapterTools'; +import { executeCommand } from '../../../lib/executeCommand'; +import type { DBConnection } from './dbConnection'; +import { getTestAdapterDir, getTestControllerDir, getTestDBDir, getTestDataDir, getTestLogDir } from './tools'; -const debug = debugModule("testing:integration:ControllerSetup"); +const debug = debugModule('testing:integration:ControllerSetup'); export class ControllerSetup { - public constructor( - private adapterDir: string, - private testDir: string, - ) { - debug("Creating ControllerSetup..."); - - this.adapterName = getAdapterName(this.adapterDir); - this.appName = getAppName(this.adapterDir); - this.testAdapterDir = getTestAdapterDir(this.adapterDir, this.testDir); - this.testControllerDir = getTestControllerDir( - this.appName, - this.testDir, - ); - this.testDataDir = getTestDataDir(this.appName, this.testDir); - - debug(` directories:`); - debug(` controller: ${this.testControllerDir}`); - debug(` adapter: ${this.testAdapterDir}`); - debug(` data: ${this.testDataDir}`); - debug(` appName: ${this.appName}`); - debug(` adapterName: ${this.adapterName}`); - } - - private appName: string; - private adapterName: string; - private testAdapterDir: string; - private testControllerDir: string; - private testDataDir: string; - - public async prepareTestDir( - controllerVersion: string = "dev", - ): Promise { - debug( - `Preparing the test directory. JS-Controller version: "${controllerVersion}"...`, - ); - // Make sure the test dir exists - await ensureDir(this.testDir); - - // Write the package.json - const packageJson = { - name: path.basename(this.testDir), - version: "1.0.0", - main: "index.js", - scripts: { - test: 'echo "Error: no test specified" && exit 1', - }, - keywords: [], - author: "", - license: "ISC", - dependencies: { - [`${this.appName}.js-controller`]: controllerVersion, - }, - description: "", - }; - await writeJSON(path.join(this.testDir, "package.json"), packageJson, { - spaces: 2, - }); - - // Delete a possible package-lock.json as it can mess with future installations - const pckLockPath = path.join(this.testDir, "package-lock.json"); - if (await pathExists(pckLockPath)) await unlink(pckLockPath); - - // Set the engineStrict flag on new Node.js versions to be in line with newer ioBroker installations - const nodeMajorVersion = parseInt( - process.versions.node.split(".")[0], - 10, - ); - if (nodeMajorVersion >= 10) { - await writeFile( - path.join(this.testDir, ".npmrc"), - "engine-strict=true", - "utf8", - ); - } - - // Remember if JS-Controller is installed already. If so, we need to call `setup first` afterwards - const wasJsControllerInstalled = await this.isJsControllerInstalled(); - // Defer to npm to install the controller (if it wasn't already) - debug("(Re-)installing JS Controller..."); - await executeCommand("npm", ["i", "--omit=dev"], { - cwd: this.testDir, - }); - // Prepare/clean the databases and config - if (wasJsControllerInstalled) await this.setupJsController(); - - debug(" => done!"); - } - - /** - * Tests if JS-Controller is already installed - * @param appName The branded name of "iobroker" - * @param testDir The directory the integration tests are executed in - */ - async isJsControllerInstalled(): Promise { - debug("Testing if JS-Controller is installed..."); - // We expect js-controller to be installed if the dir in /node_modules and the data directory exist - const isInstalled = - (await pathExists(this.testControllerDir)) && - (await pathExists(this.testDataDir)); - debug(` => ${isInstalled}`); - return isInstalled; - } - - /** - * Tests if an instance of JS-Controller is already running by attempting to connect to the Objects DB - */ - public isJsControllerRunning(): Promise { - debug("Testing if JS-Controller is running..."); - return new Promise((resolve) => { - const client = new Socket(); - - const timeout = setTimeout(() => { - // Assume the connection failed after 1 s - client.destroy(); - debug(` => false`); - resolve(false); - }, 1000); - - // Try to connect to an existing ObjectsDB - client - .connect({ - port: 9000, - host: "127.0.0.1", - }) - .on("connect", () => { - // The connection succeeded - client.destroy(); - debug(` => true`); - clearTimeout(timeout); - resolve(true); - }) - .on("error", () => { - client.destroy(); - debug(` => false`); - clearTimeout(timeout); - resolve(false); - }); - }); - } - - // /** - // * Installs a new instance of JS-Controller into the test directory - // * @param appName The branded name of "iobroker" - // * @param testDir The directory the integration tests are executed in - // */ - // public async installJsController(): Promise { - // debug("Installing newest JS-Controller from github..."); - // // First npm install the JS-Controller into the correct directory - // const installUrl = `${this.appName}/${this.appName}.js-controller`; - // const installResult = await executeCommand( - // "npm", - // ["i", installUrl, "--save"], - // { - // cwd: this.testDir, - // }, - // ); - // if (installResult.exitCode !== 0) - // throw new Error("JS-Controller could not be installed!"); - // debug(" => done!"); - // } - - /** - * Sets up an existing JS-Controller instance for testing by executing "iobroker setup first" - */ - async setupJsController(): Promise { - debug("Initializing JS-Controller installation..."); - // Stop the controller before calling setup first - await executeCommand("node", [`${this.appName}.js`, "stop"], { - cwd: this.testControllerDir, - stdout: "ignore", - }); - - const setupResult = await executeCommand( - "node", - [`${this.appName}.js`, "setup", "first", "--console"], - { - cwd: this.testControllerDir, - stdout: "ignore", - }, - ); - if (setupResult.exitCode !== 0) - throw new Error(`${this.appName} setup first failed!`); - debug(" => done!"); - } - - /** - * Changes the objects and states db to use alternative ports - * @param appName The branded name of "iobroker" - * @param testDir The directory the integration tests are executed in - */ - public setupSystemConfig(dbConnection: DBConnection): void { - debug(`Moving databases to different ports...`); - - const systemConfig = dbConnection.getSystemConfig(); - systemConfig.objects.port = 19001; - systemConfig.states.port = 19000; - dbConnection.setSystemConfig(systemConfig); - debug(" => done!"); - } - - /** - * Clears the log dir for integration tests (and creates it if it doesn't exist) - * @param appName The branded name of "iobroker" - * @param testDir The directory the integration tests are executed in - */ - public clearLogDir(): Promise { - debug("Cleaning log directory..."); - return emptyDir(getTestLogDir(this.appName, this.testDir)); - } - - /** - * Clears the sqlite DB dir for integration tests (and creates it if it doesn't exist) - * @param appName The branded name of "iobroker" - * @param testDir The directory the integration tests are executed in - */ - public clearDBDir(): Promise { - debug("Cleaning SQLite directory..."); - return emptyDir(getTestDBDir(this.appName, this.testDir)); - } - - /** - * Disables all admin instances in the objects DB - * @param objects The contents of objects.json - */ - public async disableAdminInstances( - dbConnection: DBConnection, - ): Promise { - debug("Disabling admin instances..."); - const instanceObjects = await dbConnection.getObjectViewAsync( - "system", - "instance", - { - startkey: "system.adapter.admin.", - endkey: "system.adapter.admin.\u9999", - }, - ); - for (const { id, value: obj } of instanceObjects.rows) { - if (obj && obj.common) { - obj.common.enabled = false; - await dbConnection.setObject(id, obj); - } - } - debug(" => done!"); - } + public constructor( + private adapterDir: string, + private testDir: string, + ) { + debug('Creating ControllerSetup...'); + + this.adapterName = getAdapterName(this.adapterDir); + this.appName = getAppName(this.adapterDir); + this.testAdapterDir = getTestAdapterDir(this.adapterDir, this.testDir); + this.testControllerDir = getTestControllerDir(this.appName, this.testDir); + this.testDataDir = getTestDataDir(this.appName, this.testDir); + + debug(` directories:`); + debug(` controller: ${this.testControllerDir}`); + debug(` adapter: ${this.testAdapterDir}`); + debug(` data: ${this.testDataDir}`); + debug(` appName: ${this.appName}`); + debug(` adapterName: ${this.adapterName}`); + } + + private appName: string; + private adapterName: string; + private testAdapterDir: string; + private testControllerDir: string; + private testDataDir: string; + + public async prepareTestDir(controllerVersion: string = 'dev'): Promise { + debug(`Preparing the test directory. JS-Controller version: "${controllerVersion}"...`); + // Make sure the test dir exists + await ensureDir(this.testDir); + + // Write the package.json + const packageJson = { + name: path.basename(this.testDir), + version: '1.0.0', + main: 'index.js', + scripts: { + test: 'echo "Error: no test specified" && exit 1', + }, + keywords: [], + author: '', + license: 'ISC', + dependencies: { + [`${this.appName}.js-controller`]: controllerVersion, + }, + description: '', + }; + await writeJSON(path.join(this.testDir, 'package.json'), packageJson, { + spaces: 2, + }); + + // Delete a possible package-lock.json as it can mess with future installations + const pckLockPath = path.join(this.testDir, 'package-lock.json'); + if (await pathExists(pckLockPath)) await unlink(pckLockPath); + + // Set the engineStrict flag on new Node.js versions to be in line with newer ioBroker installations + const nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10); + if (nodeMajorVersion >= 10) { + await writeFile(path.join(this.testDir, '.npmrc'), 'engine-strict=true', 'utf8'); + } + + // Remember if JS-Controller is installed already. If so, we need to call `setup first` afterwards + const wasJsControllerInstalled = await this.isJsControllerInstalled(); + // Defer to npm to install the controller (if it wasn't already) + debug('(Re-)installing JS Controller...'); + await executeCommand('npm', ['i', '--omit=dev'], { + cwd: this.testDir, + }); + // Prepare/clean the databases and config + if (wasJsControllerInstalled) await this.setupJsController(); + + debug(' => done!'); + } + + /** + * Tests if JS-Controller is already installed + * @param appName The branded name of "iobroker" + * @param testDir The directory the integration tests are executed in + */ + async isJsControllerInstalled(): Promise { + debug('Testing if JS-Controller is installed...'); + // We expect js-controller to be installed if the dir in /node_modules and the data directory exist + const isInstalled = (await pathExists(this.testControllerDir)) && (await pathExists(this.testDataDir)); + debug(` => ${isInstalled}`); + return isInstalled; + } + + /** + * Tests if an instance of JS-Controller is already running by attempting to connect to the Objects DB + */ + public isJsControllerRunning(): Promise { + debug('Testing if JS-Controller is running...'); + return new Promise(resolve => { + const client = new Socket(); + + const timeout = setTimeout(() => { + // Assume the connection failed after 1 s + client.destroy(); + debug(` => false`); + resolve(false); + }, 1000); + + // Try to connect to an existing ObjectsDB + client + .connect({ + port: 9000, + host: '127.0.0.1', + }) + .on('connect', () => { + // The connection succeeded + client.destroy(); + debug(` => true`); + clearTimeout(timeout); + resolve(true); + }) + .on('error', () => { + client.destroy(); + debug(` => false`); + clearTimeout(timeout); + resolve(false); + }); + }); + } + + // /** + // * Installs a new instance of JS-Controller into the test directory + // * @param appName The branded name of "iobroker" + // * @param testDir The directory the integration tests are executed in + // */ + // public async installJsController(): Promise { + // debug("Installing newest JS-Controller from github..."); + // // First npm install the JS-Controller into the correct directory + // const installUrl = `${this.appName}/${this.appName}.js-controller`; + // const installResult = await executeCommand( + // "npm", + // ["i", installUrl, "--save"], + // { + // cwd: this.testDir, + // }, + // ); + // if (installResult.exitCode !== 0) + // throw new Error("JS-Controller could not be installed!"); + // debug(" => done!"); + // } + + /** + * Sets up an existing JS-Controller instance for testing by executing "iobroker setup first" + */ + async setupJsController(): Promise { + debug('Initializing JS-Controller installation...'); + // Stop the controller before calling setup first + await executeCommand('node', [`${this.appName}.js`, 'stop'], { + cwd: this.testControllerDir, + stdout: 'ignore', + }); + + const setupResult = await executeCommand('node', [`${this.appName}.js`, 'setup', 'first', '--console'], { + cwd: this.testControllerDir, + stdout: 'ignore', + }); + if (setupResult.exitCode !== 0) throw new Error(`${this.appName} setup first failed!`); + debug(' => done!'); + } + + /** + * Changes the objects and states db to use alternative ports + * @param appName The branded name of "iobroker" + * @param testDir The directory the integration tests are executed in + */ + public setupSystemConfig(dbConnection: DBConnection): void { + debug(`Moving databases to different ports...`); + + const systemConfig = dbConnection.getSystemConfig(); + systemConfig.objects.port = 19001; + systemConfig.states.port = 19000; + dbConnection.setSystemConfig(systemConfig); + debug(' => done!'); + } + + /** + * Clears the log dir for integration tests (and creates it if it doesn't exist) + * @param appName The branded name of "iobroker" + * @param testDir The directory the integration tests are executed in + */ + public clearLogDir(): Promise { + debug('Cleaning log directory...'); + return emptyDir(getTestLogDir(this.appName, this.testDir)); + } + + /** + * Clears the sqlite DB dir for integration tests (and creates it if it doesn't exist) + * @param appName The branded name of "iobroker" + * @param testDir The directory the integration tests are executed in + */ + public clearDBDir(): Promise { + debug('Cleaning SQLite directory...'); + return emptyDir(getTestDBDir(this.appName, this.testDir)); + } + + /** + * Disables all admin instances in the objects DB + * @param objects The contents of objects.json + */ + public async disableAdminInstances(dbConnection: DBConnection): Promise { + debug('Disabling admin instances...'); + const instanceObjects = await dbConnection.getObjectViewAsync('system', 'instance', { + startkey: 'system.adapter.admin.', + endkey: 'system.adapter.admin.\u9999', + }); + for (const { id, value: obj } of instanceObjects.rows) { + if (obj && obj.common) { + obj.common.enabled = false; + await dbConnection.setObject(id, obj); + } + } + debug(' => done!'); + } } diff --git a/src/tests/integration/lib/dbConnection.ts b/src/tests/integration/lib/dbConnection.ts index adae3092..485f006a 100644 --- a/src/tests/integration/lib/dbConnection.ts +++ b/src/tests/integration/lib/dbConnection.ts @@ -1,364 +1,319 @@ -import debugModule from "debug"; -import EventEmitter from "events"; -import { readFile, readJSONSync, writeFile, writeJSONSync } from "fs-extra"; -import * as path from "path"; -import { getTestControllerDir, getTestDataDir } from "./tools"; +import debugModule from 'debug'; +import EventEmitter from 'events'; +import { readFile, readJSONSync, writeFile, writeJSONSync } from 'fs-extra'; +import * as path from 'path'; +import { getTestControllerDir, getTestDataDir } from './tools'; -const debug = debugModule("testing:integration:DBConnection"); +const debug = debugModule('testing:integration:DBConnection'); export type ObjectsDB = Record; export type StatesDB = Record; export interface DBConnection { - on(event: "objectChange", handler: ioBroker.ObjectChangeHandler): this; - on(event: "stateChange", handler: ioBroker.StateChangeHandler): this; + on(event: 'objectChange', handler: ioBroker.ObjectChangeHandler): this; + on(event: 'stateChange', handler: ioBroker.StateChangeHandler): this; } /** The DB connection capsules access to the states and objects DB */ export class DBConnection extends EventEmitter { - /** - * @param appName The branded name of "iobroker" - * @param testDir The directory the integration tests are executed in - */ - public constructor( - private appName: string, - private testDir: string, - private logger: ioBroker.Logger, - ) { - super(); - this.testControllerDir = getTestControllerDir(this.appName, testDir); - this.testDataDir = getTestDataDir(appName, testDir); - } - - private testDataDir: string; - private testControllerDir: string; - - // TODO: These could use some better type definitions - private _objectsServer: any; - private _statesServer: any; - - private _objectsClient: any; - /** The underlying objects client instance that can be used to access the objects DB */ - public get objectsClient(): any { - return this._objectsClient; - } - - private _statesClient: any; - /** The underlying states client instance that can be used to access the states DB */ - public get statesClient(): any { - return this._statesClient; - } - - public get objectsType(): "file" | "jsonl" { - return this.getSystemConfig().objects.type; - } - public get objectsPath(): string { - return path.join( - this.testDataDir, - this.objectsType === "file" ? "objects.json" : "objects.jsonl", - ); - } - - public get statesType(): "file" | "jsonl" { - return this.getSystemConfig().states.type; - } - public get statesPath(): string { - return path.join( - this.testDataDir, - this.statesType === "file" ? "states.json" : "states.jsonl", - ); - } - - public getSystemConfig(): any { - const systemFilename = path.join( - this.testDataDir, - `${this.appName}.json`, - ); - return readJSONSync(systemFilename); - } - - public async backup(): Promise<{ objects: Buffer; states: Buffer }> { - debug("Creating DB backup..."); - const wasRunning = this._isRunning; - await this.stop(); - - const objects = await readFile(this.objectsPath); - const states = await readFile(this.statesPath); - - if (wasRunning) await this.start(); - - return { objects, states }; - } - - public async restore(objects: Buffer, states: Buffer): Promise { - debug("Restoring DB backup..."); - const wasRunning = this._isRunning; - await this.stop(); - - await writeFile(this.objectsPath, objects); - await writeFile(this.statesPath, states); - - if (wasRunning) await this.start(); - } - - public setSystemConfig(systemConfig: any): void { - const systemFilename = path.join( - this.testDataDir, - `${this.appName}.json`, - ); - writeJSONSync(systemFilename, systemConfig, { spaces: 2 }); - } - - private _isRunning = false; - public get isRunning(): boolean { - return this._isRunning; - } - - public async start(): Promise { - if (this._isRunning) { - debug( - "At least one DB instance is already running, not starting again...", - ); - return; - } - - debug("starting DB instances..."); - await this.createObjectsDB(); - await this.createStatesDB(); - this._isRunning = true; - debug("DB instances started"); - } - - public async stop(): Promise { - if (!this._isRunning) { - debug("No DB instance is running, nothing to stop..."); - return; - } - debug("Stopping DB instances..."); - // Stop clients before servers - await this._objectsClient?.destroy(); - await this._objectsServer?.destroy(); - await this._statesClient?.destroy(); - await this._statesServer?.destroy(); - - this._objectsClient = null; - this._objectsServer = null; - this._statesClient = null; - this._statesServer = null; - - this._isRunning = false; - debug("DB instances stopped"); - } - - /** Creates the objects DB and sets up listeners for it */ - private async createObjectsDB(): Promise { - debug("creating objects DB"); - - const objectsType = this.objectsType; - debug(` => objects DB type: ${objectsType}`); - - const settings = { - connection: { - type: objectsType, - host: "127.0.0.1", - port: 19001, - user: "", - pass: "", - noFileCache: false, - connectTimeout: 2000, - }, - logger: this.logger, - }; - - const objectsDbPath = require.resolve( - `@iobroker/db-objects-${objectsType}`, - { - paths: [ - path.join(this.testDir, "node_modules"), - path.join(this.testControllerDir, "node_modules"), - ], - }, - ); - debug(` => objects DB lib found at ${objectsDbPath}`); - // eslint-disable-next-line @typescript-eslint/no-require-imports - const { Server, Client } = require(objectsDbPath); - - // First create the server - await new Promise((resolve) => { - this._objectsServer = new Server({ - ...settings, - connected: () => { - resolve(); - }, - }); - }); - - // Then the client - await new Promise((resolve) => { - this._objectsClient = new Client({ - ...settings, - connected: () => { - this._objectsClient.subscribe("*"); - resolve(); - }, - change: this.emit.bind(this, "objectChange"), - }); - }); - - debug(" => done!"); - } - - /** Creates the states DB and sets up listeners for it */ - private async createStatesDB(): Promise { - debug(`creating states DB`); - - const statesType = this.statesType; - debug(` => states DB type: ${statesType}`); - - const settings = { - connection: { - type: statesType, - host: "127.0.0.1", - port: 19000, - options: { - auth_pass: null, - retry_max_delay: 15000, - }, - }, - logger: this.logger, - }; - - const statesDbPath = require.resolve( - `@iobroker/db-states-${statesType}`, - { - paths: [ - path.join(this.testDir, "node_modules"), - path.join(this.testControllerDir, "node_modules"), - ], - }, - ); - debug(` => states DB lib found at ${statesDbPath}`); - // eslint-disable-next-line @typescript-eslint/no-require-imports - const { Server, Client } = require(statesDbPath); - - // First create the server - await new Promise((resolve) => { - this._statesServer = new Server({ - ...settings, - connected: () => { - resolve(); - }, - }); - }); - - // Then the client - await new Promise((resolve) => { - this._statesClient = new Client({ - ...settings, - connected: () => { - this._statesClient.subscribe("*"); - resolve(); - }, - change: this.emit.bind(this, "stateChange"), - }); - }); - - debug(" => done!"); - } - - public readonly getObject: ioBroker.Adapter["getForeignObjectAsync"] = - async (id) => { - if (!this._objectsClient) { - throw new Error("Objects DB is not running"); - } - return this._objectsClient.getObjectAsync(id); - }; - - public readonly setObject: ioBroker.Adapter["setForeignObjectAsync"] = - async (...args) => { - if (!this._objectsClient) { - throw new Error("Objects DB is not running"); - } - return this._objectsClient.setObjectAsync(...args); - }; - - public readonly delObject: ioBroker.Adapter["delForeignObjectAsync"] = - async (...args) => { - if (!this._objectsClient) { - throw new Error("Objects DB is not running"); - } - return this._objectsClient.delObjectAsync(...args); - }; - - public readonly getState: ioBroker.Adapter["getForeignStateAsync"] = async ( - id, - ) => { - if (!this._statesClient) { - throw new Error("States DB is not running"); - } - return this._statesClient.getStateAsync(id); - }; - - public readonly setState: ioBroker.Adapter["setForeignStateAsync"] = - (async (...args: any[]) => { - if (!this._statesClient) { - throw new Error("States DB is not running"); - } - return this._statesClient.setStateAsync(...args); - }) as any; - - public readonly delState: ioBroker.Adapter["delForeignStateAsync"] = async ( - ...args - ) => { - if (!this._statesClient) { - throw new Error("States DB is not running"); - } - return this._statesClient.delStateAsync(...args); - }; - - public subscribeMessage(id: string): void { - if (!this._statesClient) { - throw new Error("States DB is not running"); - } - return this._statesClient.subscribeMessage(id); - } - - public pushMessage( - instanceId: string, - msg: any, - callback: (err: Error | null, id: any) => void, - ): void { - if (!this._statesClient) { - throw new Error("States DB is not running"); - } - this._statesClient.pushMessage(instanceId, msg, callback); - } - - public readonly getObjectViewAsync: ioBroker.Adapter["getObjectViewAsync"] = - async (...args) => { - if (!this._objectsClient) { - throw new Error("Objects DB is not running"); - } - return this._objectsClient.getObjectViewAsync(...args); - }; - - public async getStateIDs(pattern = "*"): Promise { - if (!this._statesClient) { - throw new Error("States DB is not running"); - } - return ( - this._statesClient.getKeysAsync?.(pattern) || - this._statesClient.getKeys?.(pattern) - ); - } - - public async getObjectIDs(pattern = "*"): Promise { - if (!this._objectsClient) { - throw new Error("Objects DB is not running"); - } - return ( - this._objectsClient.getKeysAsync?.(pattern) || - this._objectsClient.getKeys?.(pattern) - ); - } + /** + * @param appName The branded name of "iobroker" + * @param testDir The directory the integration tests are executed in + */ + public constructor( + private appName: string, + private testDir: string, + private logger: ioBroker.Logger, + ) { + super(); + this.testControllerDir = getTestControllerDir(this.appName, testDir); + this.testDataDir = getTestDataDir(appName, testDir); + } + + private testDataDir: string; + private testControllerDir: string; + + // TODO: These could use some better type definitions + private _objectsServer: any; + private _statesServer: any; + + private _objectsClient: any; + /** The underlying objects client instance that can be used to access the objects DB */ + public get objectsClient(): any { + return this._objectsClient; + } + + private _statesClient: any; + /** The underlying states client instance that can be used to access the states DB */ + public get statesClient(): any { + return this._statesClient; + } + + public get objectsType(): 'file' | 'jsonl' { + return this.getSystemConfig().objects.type; + } + public get objectsPath(): string { + return path.join(this.testDataDir, this.objectsType === 'file' ? 'objects.json' : 'objects.jsonl'); + } + + public get statesType(): 'file' | 'jsonl' { + return this.getSystemConfig().states.type; + } + public get statesPath(): string { + return path.join(this.testDataDir, this.statesType === 'file' ? 'states.json' : 'states.jsonl'); + } + + public getSystemConfig(): any { + const systemFilename = path.join(this.testDataDir, `${this.appName}.json`); + return readJSONSync(systemFilename); + } + + public async backup(): Promise<{ objects: Buffer; states: Buffer }> { + debug('Creating DB backup...'); + const wasRunning = this._isRunning; + await this.stop(); + + const objects = await readFile(this.objectsPath); + const states = await readFile(this.statesPath); + + if (wasRunning) await this.start(); + + return { objects, states }; + } + + public async restore(objects: Buffer, states: Buffer): Promise { + debug('Restoring DB backup...'); + const wasRunning = this._isRunning; + await this.stop(); + + await writeFile(this.objectsPath, objects); + await writeFile(this.statesPath, states); + + if (wasRunning) await this.start(); + } + + public setSystemConfig(systemConfig: any): void { + const systemFilename = path.join(this.testDataDir, `${this.appName}.json`); + writeJSONSync(systemFilename, systemConfig, { spaces: 2 }); + } + + private _isRunning = false; + public get isRunning(): boolean { + return this._isRunning; + } + + public async start(): Promise { + if (this._isRunning) { + debug('At least one DB instance is already running, not starting again...'); + return; + } + + debug('starting DB instances...'); + await this.createObjectsDB(); + await this.createStatesDB(); + this._isRunning = true; + debug('DB instances started'); + } + + public async stop(): Promise { + if (!this._isRunning) { + debug('No DB instance is running, nothing to stop...'); + return; + } + debug('Stopping DB instances...'); + // Stop clients before servers + await this._objectsClient?.destroy(); + await this._objectsServer?.destroy(); + await this._statesClient?.destroy(); + await this._statesServer?.destroy(); + + this._objectsClient = null; + this._objectsServer = null; + this._statesClient = null; + this._statesServer = null; + + this._isRunning = false; + debug('DB instances stopped'); + } + + /** Creates the objects DB and sets up listeners for it */ + private async createObjectsDB(): Promise { + debug('creating objects DB'); + + const objectsType = this.objectsType; + debug(` => objects DB type: ${objectsType}`); + + const settings = { + connection: { + type: objectsType, + host: '127.0.0.1', + port: 19001, + user: '', + pass: '', + noFileCache: false, + connectTimeout: 2000, + }, + logger: this.logger, + }; + + const objectsDbPath = require.resolve(`@iobroker/db-objects-${objectsType}`, { + paths: [path.join(this.testDir, 'node_modules'), path.join(this.testControllerDir, 'node_modules')], + }); + debug(` => objects DB lib found at ${objectsDbPath}`); + // eslint-disable-next-line @typescript-eslint/no-require-imports + const { Server, Client } = require(objectsDbPath); + + // First create the server + await new Promise(resolve => { + this._objectsServer = new Server({ + ...settings, + connected: () => { + resolve(); + }, + }); + }); + + // Then the client + await new Promise(resolve => { + this._objectsClient = new Client({ + ...settings, + connected: () => { + this._objectsClient.subscribe('*'); + resolve(); + }, + change: this.emit.bind(this, 'objectChange'), + }); + }); + + debug(' => done!'); + } + + /** Creates the states DB and sets up listeners for it */ + private async createStatesDB(): Promise { + debug(`creating states DB`); + + const statesType = this.statesType; + debug(` => states DB type: ${statesType}`); + + const settings = { + connection: { + type: statesType, + host: '127.0.0.1', + port: 19000, + options: { + auth_pass: null, + retry_max_delay: 15000, + }, + }, + logger: this.logger, + }; + + const statesDbPath = require.resolve(`@iobroker/db-states-${statesType}`, { + paths: [path.join(this.testDir, 'node_modules'), path.join(this.testControllerDir, 'node_modules')], + }); + debug(` => states DB lib found at ${statesDbPath}`); + // eslint-disable-next-line @typescript-eslint/no-require-imports + const { Server, Client } = require(statesDbPath); + + // First create the server + await new Promise(resolve => { + this._statesServer = new Server({ + ...settings, + connected: () => { + resolve(); + }, + }); + }); + + // Then the client + await new Promise(resolve => { + this._statesClient = new Client({ + ...settings, + connected: () => { + this._statesClient.subscribe('*'); + resolve(); + }, + change: this.emit.bind(this, 'stateChange'), + }); + }); + + debug(' => done!'); + } + + public readonly getObject: ioBroker.Adapter['getForeignObjectAsync'] = async id => { + if (!this._objectsClient) { + throw new Error('Objects DB is not running'); + } + return this._objectsClient.getObjectAsync(id); + }; + + public readonly setObject: ioBroker.Adapter['setForeignObjectAsync'] = async (...args) => { + if (!this._objectsClient) { + throw new Error('Objects DB is not running'); + } + return this._objectsClient.setObjectAsync(...args); + }; + + public readonly delObject: ioBroker.Adapter['delForeignObjectAsync'] = async (...args) => { + if (!this._objectsClient) { + throw new Error('Objects DB is not running'); + } + return this._objectsClient.delObjectAsync(...args); + }; + + public readonly getState: ioBroker.Adapter['getForeignStateAsync'] = async id => { + if (!this._statesClient) { + throw new Error('States DB is not running'); + } + return this._statesClient.getStateAsync(id); + }; + + public readonly setState: ioBroker.Adapter['setForeignStateAsync'] = (async (...args: any[]) => { + if (!this._statesClient) { + throw new Error('States DB is not running'); + } + return this._statesClient.setStateAsync(...args); + }) as any; + + public readonly delState: ioBroker.Adapter['delForeignStateAsync'] = async (...args) => { + if (!this._statesClient) { + throw new Error('States DB is not running'); + } + return this._statesClient.delStateAsync(...args); + }; + + public subscribeMessage(id: string): void { + if (!this._statesClient) { + throw new Error('States DB is not running'); + } + return this._statesClient.subscribeMessage(id); + } + + public pushMessage(instanceId: string, msg: any, callback: (err: Error | null, id: any) => void): void { + if (!this._statesClient) { + throw new Error('States DB is not running'); + } + this._statesClient.pushMessage(instanceId, msg, callback); + } + + public readonly getObjectViewAsync: ioBroker.Adapter['getObjectViewAsync'] = async (...args) => { + if (!this._objectsClient) { + throw new Error('Objects DB is not running'); + } + return this._objectsClient.getObjectViewAsync(...args); + }; + + public async getStateIDs(pattern = '*'): Promise { + if (!this._statesClient) { + throw new Error('States DB is not running'); + } + return this._statesClient.getKeysAsync?.(pattern) || this._statesClient.getKeys?.(pattern); + } + + public async getObjectIDs(pattern = '*'): Promise { + if (!this._objectsClient) { + throw new Error('Objects DB is not running'); + } + return this._objectsClient.getKeysAsync?.(pattern) || this._objectsClient.getKeys?.(pattern); + } } diff --git a/src/tests/integration/lib/harness.ts b/src/tests/integration/lib/harness.ts index 55bf64a9..1c3c6643 100644 --- a/src/tests/integration/lib/harness.ts +++ b/src/tests/integration/lib/harness.ts @@ -1,331 +1,291 @@ -import { wait } from "alcalzone-shared/async"; -import { extend } from "alcalzone-shared/objects"; -import { ChildProcess, spawn } from "child_process"; -import debugModule from "debug"; -import { EventEmitter } from "events"; -import * as path from "path"; -import { - getAdapterExecutionMode, - getAdapterName, - getAppName, - locateAdapterMainFile, -} from "../../../lib/adapterTools"; -import type { DBConnection } from "./dbConnection"; -import { getTestAdapterDir, getTestControllerDir } from "./tools"; - -const debug = debugModule("testing:integration:TestHarness"); +// @ts-expect-error no types +import { wait } from 'alcalzone-shared/async'; +// @ts-expect-error no types +import { extend } from 'alcalzone-shared/objects'; +import { ChildProcess, spawn } from 'child_process'; +import debugModule from 'debug'; +import { EventEmitter } from 'events'; +import * as path from 'path'; +import { getAdapterExecutionMode, getAdapterName, getAppName, locateAdapterMainFile } from '../../../lib/adapterTools'; +import type { DBConnection } from './dbConnection'; +import { getTestAdapterDir, getTestControllerDir } from './tools'; + +const debug = debugModule('testing:integration:TestHarness'); const isWindows = /^win/.test(process.platform); export interface TestHarness { - on(event: "objectChange", handler: ioBroker.ObjectChangeHandler): this; - on(event: "stateChange", handler: ioBroker.StateChangeHandler): this; - on(event: "failed", handler: (codeOrSignal: number | string) => void): this; + on(event: 'objectChange', handler: ioBroker.ObjectChangeHandler): this; + on(event: 'stateChange', handler: ioBroker.StateChangeHandler): this; + on(event: 'failed', handler: (codeOrSignal: number | string) => void): this; } -const fromAdapterID = "system.adapter.test.0"; +const fromAdapterID = 'system.adapter.test.0'; /** * The test harness capsules the execution of the JS-Controller and the adapter instance and monitors their status. * Use it in every test to start a fresh adapter instance */ export class TestHarness extends EventEmitter { - /** - * @param adapterDir The root directory of the adapter - * @param testDir The directory the integration tests are executed in - */ - public constructor( - private adapterDir: string, - private testDir: string, - private dbConnection: DBConnection, - ) { - super(); - - debug("Creating instance"); - this.adapterName = getAdapterName(this.adapterDir); - this.appName = getAppName(adapterDir); - - this.testControllerDir = getTestControllerDir(this.appName, testDir); - this.testAdapterDir = getTestAdapterDir(this.adapterDir, testDir); - - debug(` directories:`); - debug(` controller: ${this.testControllerDir}`); - debug(` adapter: ${this.testAdapterDir}`); - debug(` appName: ${this.appName}`); - debug(` adapterName: ${this.adapterName}`); - - dbConnection.on("objectChange", (id, obj) => { - this.emit("objectChange", id, obj); - }); - dbConnection.on("stateChange", (id, state) => { - this.emit("stateChange", id, state); - }); - } - - public readonly adapterName: string; - private appName: string; - private testControllerDir: string; - private testAdapterDir: string; - - /** Gives direct access to the Objects DB */ - public get objects(): any { - if (!this.dbConnection.objectsClient) { - throw new Error("Objects DB is not running"); - } - return this.dbConnection.objectsClient; - } - - /** Gives direct access to the States DB */ - public get states(): any { - if (!this.dbConnection.statesClient) { - throw new Error("States DB is not running"); - } - return this.dbConnection.statesClient; - } - - private _adapterProcess: ChildProcess | undefined; - /** The process the adapter is running in */ - public get adapterProcess(): ChildProcess | undefined { - return this._adapterProcess; - } - - private _adapterExit: number | string | undefined; - /** Contains the adapter exit code or signal if it was terminated unexpectedly */ - public get adapterExit(): number | string | undefined { - return this._adapterExit; - } - - /** Checks if the controller instance is running */ - public isControllerRunning(): boolean { - // The "controller instance" is just the databases, so if they are running, - // the "controller" is. - return this.dbConnection.isRunning; - } - - /** Starts the controller instance by creating the databases */ - public async startController(): Promise { - await this.dbConnection.start(); - } - - /** Stops the controller instance (and the adapter if it is running) */ - public async stopController(): Promise { - if (!this.isControllerRunning()) return; - - if (!this.didAdapterStop()) { - debug("Stopping adapter instance..."); - // Give the adapter time to stop (as long as configured in the io-package.json) - let stopTimeout: number; - try { - stopTimeout = ( - (await this.dbConnection.getObject( - `system.adapter.${this.adapterName}.0`, - )) as any - ).common.stopTimeout; - stopTimeout += 1000; - } catch {} - stopTimeout ||= 5000; // default 5s - debug(` => giving it ${stopTimeout}ms to terminate`); - await Promise.race([this.stopAdapter(), wait(stopTimeout)]); - - if (this.isAdapterRunning()) { - debug("Adapter did not terminate, killing it"); - this._adapterProcess!.kill("SIGKILL"); - } else { - debug("Adapter terminated"); - } - } else { - debug("Adapter failed to start - no need to terminate!"); - } - - await this.dbConnection.stop(); - } - - /** - * Starts the adapter in a separate process and monitors its status - * @param env Additional environment variables to set - */ - public async startAdapter(env: NodeJS.ProcessEnv = {}): Promise { - if (this.isAdapterRunning()) - throw new Error("The adapter is already running!"); - else if (this.didAdapterStop()) - throw new Error( - "This test harness has already been used. Please create a new one for each test!", - ); - - const mainFileAbsolute = await locateAdapterMainFile( - this.testAdapterDir, - ); - const mainFileRelative = path.relative( - this.testAdapterDir, - mainFileAbsolute, - ); - - const onClose = (code: number | undefined, signal: string): void => { - this._adapterProcess!.removeAllListeners(); - this._adapterExit = code != undefined ? code : signal; - this.emit("failed", this._adapterExit); - }; - - this._adapterProcess = spawn( - isWindows ? "node.exe" : "node", - [mainFileRelative, "--console"], - { - cwd: this.testAdapterDir, - stdio: ["inherit", "inherit", "inherit"], - env: { ...process.env, ...env }, - }, - ) - .on("close", onClose) - .on("exit", onClose); - } - - /** - * Starts the adapter in a separate process and resolves after it has started - * @param waitForConnection By default, the test will wait for the adapter's `alive` state to become true. Set this to `true` to wait for the `info.connection` state instead. - * @param env Additional environment variables to set - */ - public async startAdapterAndWait( - waitForConnection: boolean = false, - env: NodeJS.ProcessEnv = {}, - ): Promise { - return new Promise((resolve, reject) => { - const waitForStateId = waitForConnection - ? `${this.adapterName}.0.info.connection` - : `system.adapter.${this.adapterName}.0.alive`; - this.on("stateChange", async (id, state) => { - if (id === waitForStateId && state && state.val === true) { - resolve(); - } - }) - .on("failed", (code) => { - reject( - new Error( - `The adapter startup was interrupted unexpectedly with ${ - typeof code === "number" ? "code" : "signal" - } ${code}`, - ), - ); - }) - .startAdapter(env); - }); - } - - /** Tests if the adapter process is still running */ - public isAdapterRunning(): boolean { - return !!this._adapterProcess; - } - - /** Tests if the adapter process has already exited */ - public didAdapterStop(): boolean { - return this._adapterExit != undefined; - } - - /** Stops the adapter process */ - public stopAdapter(): Promise | undefined { - if (!this.isAdapterRunning()) return; - - return new Promise(async (resolve) => { - const onClose = ( - code: number | undefined, - signal: string, - ): void => { - if (!this._adapterProcess) return; - this._adapterProcess.removeAllListeners(); - - this._adapterExit = code != undefined ? code : signal; - this._adapterProcess = undefined; - debug("Adapter process terminated:"); - debug(` Code: ${code}`); - debug(` Signal: ${signal}`); - resolve(); - }; - - this._adapterProcess!.removeAllListeners() - .on("close", onClose) - .on("exit", onClose); - - // Tell adapter to stop - try { - await this.dbConnection.setState( - `system.adapter.${this.adapterName}.0.sigKill`, - { - val: -1, - from: "system.host.testing", - }, - ); - } catch { - // DB connection may be closed already, kill the process - this._adapterProcess?.kill("SIGTERM"); - } - }); - } - - /** - * Updates the adapter config. The changes can be a subset of the target object - */ - public async changeAdapterConfig( - adapterName: string, - changes: Record, - ): Promise { - const adapterInstanceId = `system.adapter.${adapterName}.0`; - const obj = await this.dbConnection.getObject(adapterInstanceId); - if (obj) { - extend(obj, changes); - await this.dbConnection.setObject(adapterInstanceId, obj); - } - } - - public getAdapterExecutionMode(): ioBroker.AdapterCommon["mode"] { - return getAdapterExecutionMode(this.testAdapterDir); - } - - /** Enables the sendTo method */ - public async enableSendTo(): Promise { - await this.dbConnection.setObject(fromAdapterID, { - type: "instance", - common: {} as ioBroker.InstanceCommon, - native: {}, - instanceObjects: [], - objects: [], - }); - - this.dbConnection.subscribeMessage(fromAdapterID); - } - - private sendToID = 1; - - /** Sends a message to an adapter instance */ - public sendTo( - target: string, - command: string, - message: any, - callback: ioBroker.MessageCallback, - ): void { - const stateChangedHandler: ioBroker.StateChangeHandler = ( - id, - state, - ) => { - if (id === `messagebox.${fromAdapterID}`) { - callback((state as any).message); - this.removeListener("stateChange", stateChangedHandler); - } - }; - this.addListener("stateChange", stateChangedHandler); - - this.dbConnection.pushMessage( - `system.adapter.${target}`, - { - command: command, - message: message, - from: fromAdapterID, - callback: { - message: message, - id: this.sendToID++, - ack: false, - time: Date.now(), - }, - }, - (err: any, id: any) => console.log("published message " + id), - ); - } + /** + * @param adapterDir The root directory of the adapter + * @param testDir The directory the integration tests are executed in + */ + public constructor( + private adapterDir: string, + private testDir: string, + private dbConnection: DBConnection, + ) { + super(); + + debug('Creating instance'); + this.adapterName = getAdapterName(this.adapterDir); + this.appName = getAppName(adapterDir); + + this.testControllerDir = getTestControllerDir(this.appName, testDir); + this.testAdapterDir = getTestAdapterDir(this.adapterDir, testDir); + + debug(` directories:`); + debug(` controller: ${this.testControllerDir}`); + debug(` adapter: ${this.testAdapterDir}`); + debug(` appName: ${this.appName}`); + debug(` adapterName: ${this.adapterName}`); + + dbConnection.on('objectChange', (id, obj) => { + this.emit('objectChange', id, obj); + }); + dbConnection.on('stateChange', (id, state) => { + this.emit('stateChange', id, state); + }); + } + + public readonly adapterName: string; + private appName: string; + private testControllerDir: string; + private testAdapterDir: string; + + /** Gives direct access to the Objects DB */ + public get objects(): any { + if (!this.dbConnection.objectsClient) { + throw new Error('Objects DB is not running'); + } + return this.dbConnection.objectsClient; + } + + /** Gives direct access to the States DB */ + public get states(): any { + if (!this.dbConnection.statesClient) { + throw new Error('States DB is not running'); + } + return this.dbConnection.statesClient; + } + + private _adapterProcess: ChildProcess | undefined; + /** The process the adapter is running in */ + public get adapterProcess(): ChildProcess | undefined { + return this._adapterProcess; + } + + private _adapterExit: number | string | undefined; + /** Contains the adapter exit code or signal if it was terminated unexpectedly */ + public get adapterExit(): number | string | undefined { + return this._adapterExit; + } + + /** Checks if the controller instance is running */ + public isControllerRunning(): boolean { + // The "controller instance" is just the databases, so if they are running, + // the "controller" is. + return this.dbConnection.isRunning; + } + + /** Starts the controller instance by creating the databases */ + public async startController(): Promise { + await this.dbConnection.start(); + } + + /** Stops the controller instance (and the adapter if it is running) */ + public async stopController(): Promise { + if (!this.isControllerRunning()) return; + + if (!this.didAdapterStop()) { + debug('Stopping adapter instance...'); + // Give the adapter time to stop (as long as configured in the io-package.json) + let stopTimeout: number; + try { + stopTimeout = ((await this.dbConnection.getObject(`system.adapter.${this.adapterName}.0`)) as any) + .common.stopTimeout; + stopTimeout += 1000; + } catch {} + stopTimeout ||= 5000; // default 5s + debug(` => giving it ${stopTimeout}ms to terminate`); + await Promise.race([this.stopAdapter(), wait(stopTimeout)]); + + if (this.isAdapterRunning()) { + debug('Adapter did not terminate, killing it'); + this._adapterProcess!.kill('SIGKILL'); + } else { + debug('Adapter terminated'); + } + } else { + debug('Adapter failed to start - no need to terminate!'); + } + + await this.dbConnection.stop(); + } + + /** + * Starts the adapter in a separate process and monitors its status + * @param env Additional environment variables to set + */ + public async startAdapter(env: NodeJS.ProcessEnv = {}): Promise { + if (this.isAdapterRunning()) throw new Error('The adapter is already running!'); + else if (this.didAdapterStop()) + throw new Error('This test harness has already been used. Please create a new one for each test!'); + + const mainFileAbsolute = await locateAdapterMainFile(this.testAdapterDir); + const mainFileRelative = path.relative(this.testAdapterDir, mainFileAbsolute); + + const onClose = (code: number | undefined, signal: string): void => { + this._adapterProcess!.removeAllListeners(); + this._adapterExit = code != undefined ? code : signal; + this.emit('failed', this._adapterExit); + }; + + this._adapterProcess = spawn(isWindows ? 'node.exe' : 'node', [mainFileRelative, '--console'], { + cwd: this.testAdapterDir, + stdio: ['inherit', 'inherit', 'inherit'], + env: { ...process.env, ...env }, + }) + .on('close', onClose) + .on('exit', onClose); + } + + /** + * Starts the adapter in a separate process and resolves after it has started + * @param waitForConnection By default, the test will wait for the adapter's `alive` state to become true. Set this to `true` to wait for the `info.connection` state instead. + * @param env Additional environment variables to set + */ + public async startAdapterAndWait(waitForConnection: boolean = false, env: NodeJS.ProcessEnv = {}): Promise { + return new Promise((resolve, reject) => { + const waitForStateId = waitForConnection + ? `${this.adapterName}.0.info.connection` + : `system.adapter.${this.adapterName}.0.alive`; + this.on('stateChange', async (id, state) => { + if (id === waitForStateId && state && state.val === true) { + resolve(); + } + }) + .on('failed', code => { + reject( + new Error( + `The adapter startup was interrupted unexpectedly with ${ + typeof code === 'number' ? 'code' : 'signal' + } ${code}`, + ), + ); + }) + .startAdapter(env); + }); + } + + /** Tests if the adapter process is still running */ + public isAdapterRunning(): boolean { + return !!this._adapterProcess; + } + + /** Tests if the adapter process has already exited */ + public didAdapterStop(): boolean { + return this._adapterExit != undefined; + } + + /** Stops the adapter process */ + public stopAdapter(): Promise | undefined { + if (!this.isAdapterRunning()) return; + + return new Promise(async resolve => { + const onClose = (code: number | undefined, signal: string): void => { + if (!this._adapterProcess) return; + this._adapterProcess.removeAllListeners(); + + this._adapterExit = code != undefined ? code : signal; + this._adapterProcess = undefined; + debug('Adapter process terminated:'); + debug(` Code: ${code}`); + debug(` Signal: ${signal}`); + resolve(); + }; + + this._adapterProcess!.removeAllListeners().on('close', onClose).on('exit', onClose); + + // Tell adapter to stop + try { + await this.dbConnection.setState(`system.adapter.${this.adapterName}.0.sigKill`, { + val: -1, + from: 'system.host.testing', + }); + } catch { + // DB connection may be closed already, kill the process + this._adapterProcess?.kill('SIGTERM'); + } + }); + } + + /** + * Updates the adapter config. The changes can be a subset of the target object + */ + public async changeAdapterConfig(adapterName: string, changes: Record): Promise { + const adapterInstanceId = `system.adapter.${adapterName}.0`; + const obj = await this.dbConnection.getObject(adapterInstanceId); + if (obj) { + extend(obj, changes); + await this.dbConnection.setObject(adapterInstanceId, obj); + } + } + + public getAdapterExecutionMode(): ioBroker.AdapterCommon['mode'] { + return getAdapterExecutionMode(this.testAdapterDir); + } + + /** Enables the sendTo method */ + public async enableSendTo(): Promise { + await this.dbConnection.setObject(fromAdapterID, { + type: 'instance', + common: {} as ioBroker.InstanceCommon, + native: {}, + instanceObjects: [], + objects: [], + }); + + this.dbConnection.subscribeMessage(fromAdapterID); + } + + private sendToID = 1; + + /** Sends a message to an adapter instance */ + public sendTo(target: string, command: string, message: any, callback: ioBroker.MessageCallback): void { + const stateChangedHandler: ioBroker.StateChangeHandler = (id, state) => { + if (id === `messagebox.${fromAdapterID}`) { + callback((state as any).message); + this.removeListener('stateChange', stateChangedHandler); + } + }; + this.addListener('stateChange', stateChangedHandler); + + this.dbConnection.pushMessage( + `system.adapter.${target}`, + { + command: command, + message: message, + from: fromAdapterID, + callback: { + message: message, + id: this.sendToID++, + ack: false, + time: Date.now(), + }, + }, + (err: any, id: any) => console.log('published message ' + id), + ); + } } diff --git a/src/tests/integration/lib/logger.ts b/src/tests/integration/lib/logger.ts index 2bafcfe2..16b33a3a 100644 --- a/src/tests/integration/lib/logger.ts +++ b/src/tests/integration/lib/logger.ts @@ -1,22 +1,21 @@ enum LoglevelOrder { - "error", - "warn", - "info", - "debug", - "silly", + 'error', + 'warn', + 'info', + 'debug', + 'silly', } export function createLogger(loglevel: ioBroker.LogLevel): ioBroker.Logger { - const loglevelNumeric = - LoglevelOrder[loglevel ?? "debug"] ?? LoglevelOrder.debug; + const loglevelNumeric = LoglevelOrder[loglevel ?? 'debug'] ?? LoglevelOrder.debug; - const ignore = (): void => {}; - return { - error: loglevelNumeric >= LoglevelOrder.error ? console.error : ignore, - warn: loglevelNumeric >= LoglevelOrder.warn ? console.warn : ignore, - info: loglevelNumeric >= LoglevelOrder.info ? console.log : ignore, - debug: loglevelNumeric >= LoglevelOrder.debug ? console.log : ignore, - silly: loglevelNumeric >= LoglevelOrder.silly ? console.log : ignore, - level: loglevel, - }; + const ignore = (): void => {}; + return { + error: loglevelNumeric >= LoglevelOrder.error ? console.error : ignore, + warn: loglevelNumeric >= LoglevelOrder.warn ? console.warn : ignore, + info: loglevelNumeric >= LoglevelOrder.info ? console.log : ignore, + debug: loglevelNumeric >= LoglevelOrder.debug ? console.log : ignore, + silly: loglevelNumeric >= LoglevelOrder.silly ? console.log : ignore, + level: loglevel, + }; } diff --git a/src/tests/integration/lib/tools.ts b/src/tests/integration/lib/tools.ts index 192f6aab..632196d0 100644 --- a/src/tests/integration/lib/tools.ts +++ b/src/tests/integration/lib/tools.ts @@ -1,5 +1,5 @@ -import * as path from "path"; -import { getAdapterFullName } from "../../../lib/adapterTools"; +import * as path from 'path'; +import { getAdapterFullName } from '../../../lib/adapterTools'; /** * Locates the directory where JS-Controller is installed for integration tests @@ -7,7 +7,7 @@ import { getAdapterFullName } from "../../../lib/adapterTools"; * @param testDir The directory the integration tests are executed in */ export function getTestControllerDir(appName: string, testDir: string): string { - return path.resolve(testDir, "node_modules", `${appName}.js-controller`); + return path.resolve(testDir, 'node_modules', `${appName}.js-controller`); } /** @@ -16,7 +16,7 @@ export function getTestControllerDir(appName: string, testDir: string): string { * @param testDir The directory the integration tests are executed in */ export function getTestDataDir(appName: string, testDir: string): string { - return path.resolve(testDir, `${appName}-data`); + return path.resolve(testDir, `${appName}-data`); } /** @@ -25,7 +25,7 @@ export function getTestDataDir(appName: string, testDir: string): string { * @param testDir The directory the integration tests are executed in */ export function getTestLogDir(appName: string, testDir: string): string { - return path.resolve(testDir, "log"); + return path.resolve(testDir, 'log'); } /** @@ -34,7 +34,7 @@ export function getTestLogDir(appName: string, testDir: string): string { * @param testDir The directory the integration tests are executed in */ export function getTestDBDir(appName: string, testDir: string): string { - return path.resolve(getTestDataDir(appName, testDir), "sqlite"); + return path.resolve(getTestDataDir(appName, testDir), 'sqlite'); } /** @@ -43,6 +43,6 @@ export function getTestDBDir(appName: string, testDir: string): string { * @param testDir The directory the integration tests are executed in */ export function getTestAdapterDir(adapterDir: string, testDir: string): string { - const adapterName = getAdapterFullName(adapterDir); - return path.resolve(testDir, "node_modules", adapterName); + const adapterName = getAdapterFullName(adapterDir); + return path.resolve(testDir, 'node_modules', adapterName); } diff --git a/src/tests/packageFiles/index.ts b/src/tests/packageFiles/index.ts index dde518a4..b41516de 100644 --- a/src/tests/packageFiles/index.ts +++ b/src/tests/packageFiles/index.ts @@ -1,341 +1,298 @@ -import { isArray, isObject } from "alcalzone-shared/typeguards"; -import { AssertionError, expect } from "chai"; -import * as fs from "fs"; -import * as path from "path"; +// @ts-expect-error no types +import { isArray, isObject } from 'alcalzone-shared/typeguards'; +import { AssertionError, expect } from 'chai'; +import * as fs from 'fs'; +import * as path from 'path'; /** * Tests if the adapter files are valid. * This is meant to be executed in a mocha context. */ export function validatePackageFiles(adapterDir: string): void { - const packageJsonPath = path.join(adapterDir, "package.json"); - const ioPackageJsonPath = path.join(adapterDir, "io-package.json"); - - // This allows us to skip tests that require valid JSON files - const invalidFiles: Record = { - "package.json": false, - "io-package.json": false, - }; - function skipIfInvalid( - this: Mocha.Context, - ...filenames: string[] - ): void | never { - if (filenames.some((f) => invalidFiles[f])) return this.skip(); - } - function markAsInvalid(this: Mocha.Context, filename: string): void { - if ( - this.currentTest!.state === "failed" && - invalidFiles[filename] === false - ) { - invalidFiles[filename] = true; - console.error( - `Skipping subsequent tests including "${filename}" because they require valid JSON files!`, - ); - } - } - - /** Ensures that a given property exists on the target object */ - function ensurePropertyExists(propertyPath: string, targetObj: any): void { - const propertyParts = propertyPath.split("."); - it(`The property "${propertyPath}" exists`, () => { - let prev = targetObj; - for (const part of propertyParts) { - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - expect(prev[part]).to.not.be.undefined; - prev = prev[part]; - } - }); - } - - describe(`Validate the package files`, () => { - describe(`Ensure they are readable`, () => { - for (const filename of ["package.json", "io-package.json"]) { - const packagePath = path.join(adapterDir, filename); - - describe(`${filename}`, () => { - afterEach(function () { - markAsInvalid.call(this, filename); - }); - beforeEach(function () { - skipIfInvalid.call(this, filename); - }); - - it("exists", () => { - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - expect( - fs.existsSync(packagePath), - `${filename} is missing in the adapter dir. Please create it!`, - ).to.be.true; - }); - - it("contains valid JSON", () => { - expect(() => { - JSON.parse(fs.readFileSync(packagePath, "utf8")); - }, `${filename} contains invalid JSON!`).not.to.throw(); - }); - - it("is an object", () => { - expect( - // eslint-disable-next-line @typescript-eslint/no-require-imports - require(packagePath), - `${filename} must contain an object!`, - ).to.be.an("object"); - }); - }); - } - }); - - describe(`Check contents of package.json`, () => { - beforeEach(function () { - skipIfInvalid.call(this, "package.json"); - }); - - // eslint-disable-next-line @typescript-eslint/no-require-imports - const packageContent = require(packageJsonPath); - // eslint-disable-next-line @typescript-eslint/no-require-imports - const iopackContent = require(ioPackageJsonPath); - - const requiredProperties = [ - "name", - "version", - "description", - "author", - "license", - "repository", - "repository.type", - ]; - requiredProperties.forEach((prop) => - ensurePropertyExists(prop, packageContent), - ); - - it("The package name is correct", () => { - let name: string = packageContent.name; - expect(name).to.match( - /^iobroker\./, - `The npm package name must start with lowercase "iobroker."!`, - ); - name = name.replace(/^iobroker\./, ""); - - expect(name).to.match( - /[a-z0-9_\-]+/, - `The adapter name must only contain lowercase letters, numbers, "-" and "_"!`, - ); - expect(name).to.match( - /^[a-z]/, - `The adapter name must start with a letter!`, - ); - expect(name).to.match( - /[a-z0-9]$/, - `The adapter name must end with a letter or number!`, - ); - }); - - if (!iopackContent.common.onlyWWW) { - it(`property main is defined for non onlyWWW adapters`, () => { - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - expect(packageContent.main).to.not.be.undefined; - }); - } - - it(`The repository type is "git"`, () => { - expect(packageContent.repository.type).to.equal("git"); - }); - - it("npm is not listed as a dependency", () => { - for (const depType of [ - "dependencies", - "devDependencies", - "optionalDependencies", - "peerDependencies", - ] as const) { - if ( - isObject(packageContent[depType]) && - "npm" in packageContent[depType] - ) { - throw new AssertionError( - `npm must not be listed in ${depType}, found "${packageContent[depType].npm}"!`, - ); - } - } - }); - }); - - describe(`Check contents of io-package.json`, () => { - beforeEach(function () { - skipIfInvalid.call(this, "io-package.json"); - }); - - // eslint-disable-next-line @typescript-eslint/no-require-imports - const iopackContent = require(ioPackageJsonPath); - - const requiredProperties = [ - "common.name", - "common.titleLang", - "common.version", - "common.news", - "common.desc", - "common.icon", - "common.extIcon", - "common.type", - "common.authors", - "native", - ]; - requiredProperties.forEach((prop) => - ensurePropertyExists(prop, iopackContent), - ); - - it(`The title does not contain "adapter" or "iobroker"`, () => { - if (!iopackContent.title) return; - expect(iopackContent.common.title).not.to.match( - /iobroker|adapter/i, - ); - }); - it(`titleLang is an object to support multiple languages`, () => { - expect(iopackContent.common.titleLang).to.be.an("object"); - }); - it(`titleLang does not contain "adapter" or "iobroker"`, () => { - for (const title of Object.values( - iopackContent.common.titleLang, - )) { - expect(title).not.to.match(/iobroker|adapter/i); - } - }); - it(`The description is an object to support multiple languages`, () => { - expect(iopackContent.common.desc).to.be.an("object"); - }); - it(`common.authors is an array that is not empty`, () => { - const authors = iopackContent.common.authors; - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - expect(isArray(authors)).to.be.true; - expect(authors.length).to.be.at.least(1); - }); - - it(`common.news is an object that contains maximum 20 entries`, () => { - const news = iopackContent.common.news; - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - expect(isObject(news)).to.be.true; - expect(Object.keys(news).length).to.be.at.most(20); - }); - - if (iopackContent.common.licenseInformation) { - it(`if common.licenseInformation exists, it is an object with required properties`, () => { - expect(iopackContent.common.licenseInformation).to.be.an( - "object", - ); - expect( - iopackContent.common.licenseInformation.type, - ).to.be.oneOf(["free", "commercial", "paid", "limited"]); - - if ( - iopackContent.common.licenseInformation.type !== "free" - ) { - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - expect( - iopackContent.common.licenseInformation.link, - "License link is missing", - ).to.not.be.undefined; - } - }); - - it(`common.license should not exist together with common.licenseInformation`, () => { - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - expect( - iopackContent.common.license, - "common.license must be removed", - ).to.be.undefined; - }); - } else { - it(`common.license must exist without common.licenseInformation`, () => { - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - expect( - iopackContent.common.license, - "common.licenseInformation (preferred) or common.license (deprecated) must exist", - ).to.not.be.undefined; - }); - } - - if (iopackContent.common.tier != undefined) { - it(`common.tier must be 1, 2 or 3`, () => { - expect(iopackContent.common.tier).to.be.at.least(1); - expect(iopackContent.common.tier).to.be.at.most(3); - }); - } - - // If the adapter has a configuration page, check that a supported admin UI is used - const hasNoConfigPage = - iopackContent.common.noConfig === true || - iopackContent.common.noConfig === "true" || - iopackContent.common.adminUI?.config === "none"; - if (!hasNoConfigPage) { - it("The adapter uses a supported admin UI", () => { - const hasSupportedUI = - !!iopackContent.common.materialize || - iopackContent.common.adminUI?.config === "html" || - iopackContent.common.adminUI?.config === "json" || - iopackContent.common.adminUI?.config === "materialize"; - - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - expect( - hasSupportedUI, - "Unsupported Admin UI, must be html, materialize or JSON config!", - ).to.be.true; - }); - } - }); - - describe(`Compare contents of package.json and io-package.json`, () => { - beforeEach(function () { - skipIfInvalid.call(this, "package.json", "io-package.json"); - }); - - // eslint-disable-next-line @typescript-eslint/no-require-imports - const packageContent = require(packageJsonPath); - // eslint-disable-next-line @typescript-eslint/no-require-imports - const iopackContent = require(ioPackageJsonPath); - - it("The name matches", () => { - expect("iobroker." + iopackContent.common.name).to.equal( - packageContent.name, - ); - }); - - it("The version matches", () => { - expect(iopackContent.common.version).to.equal( - packageContent.version, - ); - }); - - it("The license matches", () => { - if (iopackContent.common.licenseInformation) { - expect( - iopackContent.common.licenseInformation.license, - ).to.equal(packageContent.license); - } else { - expect(iopackContent.common.license).to.equal( - packageContent.license, - ); - } - }); - }); - }); - - // describe(`Check additional files`, () => { - // it("README.md exists", () => { - // expect( - // fs.existsSync(path.join(adapterDir, "README.md")), - // `README.md is missing in the adapter dir. Please create it!`, - // ).to.be.true; - // }); - - // it("LICENSE exists or is present in the README.md", () => { - // const licenseExists = fs.existsSync(path.join(adapterDir, "LICENSE")); - // if (licenseExists) return; - - // const readmeContent = fs.readFileSync(path.join(adapterDir, "README.md"), "utf8"); - // expect(readmeContent).to.match( - // /## LICENSE/i, - // `The license should be in a file "LICENSE" or be included in "README.md" as a 2nd level headline!`, - // ); - // }); - // }); + const packageJsonPath = path.join(adapterDir, 'package.json'); + const ioPackageJsonPath = path.join(adapterDir, 'io-package.json'); + + // This allows us to skip tests that require valid JSON files + const invalidFiles: Record = { + 'package.json': false, + 'io-package.json': false, + }; + function skipIfInvalid(this: Mocha.Context, ...filenames: string[]): void | never { + if (filenames.some(f => invalidFiles[f])) return this.skip(); + } + function markAsInvalid(this: Mocha.Context, filename: string): void { + if (this.currentTest!.state === 'failed' && invalidFiles[filename] === false) { + invalidFiles[filename] = true; + console.error(`Skipping subsequent tests including "${filename}" because they require valid JSON files!`); + } + } + + /** Ensures that a given property exists on the target object */ + function ensurePropertyExists(propertyPath: string, targetObj: any): void { + const propertyParts = propertyPath.split('.'); + it(`The property "${propertyPath}" exists`, () => { + let prev = targetObj; + for (const part of propertyParts) { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(prev[part]).to.not.be.undefined; + prev = prev[part]; + } + }); + } + + describe(`Validate the package files`, () => { + describe(`Ensure they are readable`, () => { + for (const filename of ['package.json', 'io-package.json']) { + const packagePath = path.join(adapterDir, filename); + + describe(`${filename}`, () => { + afterEach(function () { + markAsInvalid.call(this, filename); + }); + beforeEach(function () { + skipIfInvalid.call(this, filename); + }); + + it('exists', () => { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect( + fs.existsSync(packagePath), + `${filename} is missing in the adapter dir. Please create it!`, + ).to.be.true; + }); + + it('contains valid JSON', () => { + expect(() => { + JSON.parse(fs.readFileSync(packagePath, 'utf8')); + }, `${filename} contains invalid JSON!`).not.to.throw(); + }); + + it('is an object', () => { + expect( + // eslint-disable-next-line @typescript-eslint/no-require-imports + require(packagePath), + `${filename} must contain an object!`, + ).to.be.an('object'); + }); + }); + } + }); + + describe(`Check contents of package.json`, () => { + beforeEach(function () { + skipIfInvalid.call(this, 'package.json'); + }); + + // eslint-disable-next-line @typescript-eslint/no-require-imports + const packageContent = require(packageJsonPath); + // eslint-disable-next-line @typescript-eslint/no-require-imports + const iopackContent = require(ioPackageJsonPath); + + const requiredProperties = [ + 'name', + 'version', + 'description', + 'author', + 'license', + 'repository', + 'repository.type', + ]; + requiredProperties.forEach(prop => ensurePropertyExists(prop, packageContent)); + + it('The package name is correct', () => { + let name: string = packageContent.name; + expect(name).to.match(/^iobroker\./, `The npm package name must start with lowercase "iobroker."!`); + name = name.replace(/^iobroker\./, ''); + + expect(name).to.match( + /[a-z0-9_\-]+/, + `The adapter name must only contain lowercase letters, numbers, "-" and "_"!`, + ); + expect(name).to.match(/^[a-z]/, `The adapter name must start with a letter!`); + expect(name).to.match(/[a-z0-9]$/, `The adapter name must end with a letter or number!`); + }); + + if (!iopackContent.common.onlyWWW) { + it(`property main is defined for non onlyWWW adapters`, () => { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(packageContent.main).to.not.be.undefined; + }); + } + + it(`The repository type is "git"`, () => { + expect(packageContent.repository.type).to.equal('git'); + }); + + it('npm is not listed as a dependency', () => { + for (const depType of [ + 'dependencies', + 'devDependencies', + 'optionalDependencies', + 'peerDependencies', + ] as const) { + if (isObject(packageContent[depType]) && 'npm' in packageContent[depType]) { + throw new AssertionError( + `npm must not be listed in ${depType}, found "${packageContent[depType].npm}"!`, + ); + } + } + }); + }); + + describe(`Check contents of io-package.json`, () => { + beforeEach(function () { + skipIfInvalid.call(this, 'io-package.json'); + }); + + // eslint-disable-next-line @typescript-eslint/no-require-imports + const iopackContent = require(ioPackageJsonPath); + + const requiredProperties = [ + 'common.name', + 'common.titleLang', + 'common.version', + 'common.news', + 'common.desc', + 'common.icon', + 'common.extIcon', + 'common.type', + 'common.authors', + 'native', + ]; + requiredProperties.forEach(prop => ensurePropertyExists(prop, iopackContent)); + + it(`The title does not contain "adapter" or "iobroker"`, () => { + if (!iopackContent.title) return; + expect(iopackContent.common.title).not.to.match(/iobroker|adapter/i); + }); + it(`titleLang is an object to support multiple languages`, () => { + expect(iopackContent.common.titleLang).to.be.an('object'); + }); + it(`titleLang does not contain "adapter" or "iobroker"`, () => { + for (const title of Object.values(iopackContent.common.titleLang)) { + expect(title).not.to.match(/iobroker|adapter/i); + } + }); + it(`The description is an object to support multiple languages`, () => { + expect(iopackContent.common.desc).to.be.an('object'); + }); + it(`common.authors is an array that is not empty`, () => { + const authors = iopackContent.common.authors; + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(isArray(authors)).to.be.true; + expect(authors.length).to.be.at.least(1); + }); + + it(`common.news is an object that contains maximum 20 entries`, () => { + const news = iopackContent.common.news; + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(isObject(news)).to.be.true; + expect(Object.keys(news).length).to.be.at.most(20); + }); + + if (iopackContent.common.licenseInformation) { + it(`if common.licenseInformation exists, it is an object with required properties`, () => { + expect(iopackContent.common.licenseInformation).to.be.an('object'); + expect(iopackContent.common.licenseInformation.type).to.be.oneOf([ + 'free', + 'commercial', + 'paid', + 'limited', + ]); + + if (iopackContent.common.licenseInformation.type !== 'free') { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(iopackContent.common.licenseInformation.link, 'License link is missing').to.not.be + .undefined; + } + }); + + it(`common.license should not exist together with common.licenseInformation`, () => { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(iopackContent.common.license, 'common.license must be removed').to.be.undefined; + }); + } else { + it(`common.license must exist without common.licenseInformation`, () => { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect( + iopackContent.common.license, + 'common.licenseInformation (preferred) or common.license (deprecated) must exist', + ).to.not.be.undefined; + }); + } + + if (iopackContent.common.tier != undefined) { + it(`common.tier must be 1, 2 or 3`, () => { + expect(iopackContent.common.tier).to.be.at.least(1); + expect(iopackContent.common.tier).to.be.at.most(3); + }); + } + + // If the adapter has a configuration page, check that a supported admin UI is used + const hasNoConfigPage = + iopackContent.common.noConfig === true || + iopackContent.common.noConfig === 'true' || + iopackContent.common.adminUI?.config === 'none'; + if (!hasNoConfigPage) { + it('The adapter uses a supported admin UI', () => { + const hasSupportedUI = + !!iopackContent.common.materialize || + iopackContent.common.adminUI?.config === 'html' || + iopackContent.common.adminUI?.config === 'json' || + iopackContent.common.adminUI?.config === 'materialize'; + + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + expect(hasSupportedUI, 'Unsupported Admin UI, must be html, materialize or JSON config!').to.be + .true; + }); + } + }); + + describe(`Compare contents of package.json and io-package.json`, () => { + beforeEach(function () { + skipIfInvalid.call(this, 'package.json', 'io-package.json'); + }); + + // eslint-disable-next-line @typescript-eslint/no-require-imports + const packageContent = require(packageJsonPath); + // eslint-disable-next-line @typescript-eslint/no-require-imports + const iopackContent = require(ioPackageJsonPath); + + it('The name matches', () => { + expect('iobroker.' + iopackContent.common.name).to.equal(packageContent.name); + }); + + it('The version matches', () => { + expect(iopackContent.common.version).to.equal(packageContent.version); + }); + + it('The license matches', () => { + if (iopackContent.common.licenseInformation) { + expect(iopackContent.common.licenseInformation.license).to.equal(packageContent.license); + } else { + expect(iopackContent.common.license).to.equal(packageContent.license); + } + }); + }); + }); + + // describe(`Check additional files`, () => { + // it("README.md exists", () => { + // expect( + // fs.existsSync(path.join(adapterDir, "README.md")), + // `README.md is missing in the adapter dir. Please create it!`, + // ).to.be.true; + // }); + + // it("LICENSE exists or is present in the README.md", () => { + // const licenseExists = fs.existsSync(path.join(adapterDir, "LICENSE")); + // if (licenseExists) return; + + // const readmeContent = fs.readFileSync(path.join(adapterDir, "README.md"), "utf8"); + // expect(readmeContent).to.match( + // /## LICENSE/i, + // `The license should be in a file "LICENSE" or be included in "README.md" as a 2nd level headline!`, + // ); + // }); + // }); } diff --git a/src/tests/unit/harness/createMocks.ts b/src/tests/unit/harness/createMocks.ts index 48af3308..b8b15af2 100644 --- a/src/tests/unit/harness/createMocks.ts +++ b/src/tests/unit/harness/createMocks.ts @@ -1,5 +1,5 @@ -import { createAdapterMock } from "../mocks/mockAdapter"; -import { MockDatabase } from "../mocks/mockDatabase"; +import { createAdapterMock } from '../mocks/mockAdapter'; +import { MockDatabase } from '../mocks/mockDatabase'; /** * Creates a new set of mocks, including a mock database and a mock adapter. @@ -7,13 +7,10 @@ import { MockDatabase } from "../mocks/mockDatabase"; */ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function createMocks(adapterOptions: Partial) { - const databaseMock = new MockDatabase(); - const adapterMock = createAdapterMock.bind(undefined)( - databaseMock, - adapterOptions, - ); - return { - database: databaseMock, - adapter: adapterMock, - }; + const databaseMock = new MockDatabase(); + const adapterMock = createAdapterMock.bind(undefined)(databaseMock, adapterOptions); + return { + database: databaseMock, + adapter: adapterMock, + }; } diff --git a/src/tests/unit/index.test.ts b/src/tests/unit/index.test.ts index b864f539..11028301 100644 --- a/src/tests/unit/index.test.ts +++ b/src/tests/unit/index.test.ts @@ -1,14 +1,14 @@ -import { expect } from "chai"; -import { createMocks } from "./harness/createMocks"; +import { expect } from 'chai'; +import { createMocks } from './harness/createMocks'; -describe("Regression tests", () => { - it("The function createMocks() can be called multiple times", () => { - expect(() => { - createMocks({}); - }).not.to.throw(); +describe('Regression tests', () => { + it('The function createMocks() can be called multiple times', () => { + expect(() => { + createMocks({}); + }).not.to.throw(); - expect(() => { - createMocks({}); - }).not.to.throw(); - }); + expect(() => { + createMocks({}); + }).not.to.throw(); + }); }); diff --git a/src/tests/unit/index.ts b/src/tests/unit/index.ts index 52f3ac6f..83ae6783 100644 --- a/src/tests/unit/index.ts +++ b/src/tests/unit/index.ts @@ -1,20 +1,20 @@ export interface TestAdapterOptions { - // allowedExitCodes?: number[]; - // additionalMockedModules?: StartMockAdapterOptions["additionalMockedModules"]; - // /** Change the default test timeout of 15000ms for the startup tests */ - // startTimeout?: number; - // /** Allows you to overwrite the default adapter config */ - // overwriteAdapterConfig?: ( - // config: Record, - // ) => Record; - // /** An array of objects that should be populated before starting the adapter */ - // predefinedObjects?: ioBroker.Object[]; - // /** A dictionary of states that should be populated before starting the adapter */ - // predefinedStates?: Record; - // /** Allows you to modifiy the behavior of predefined mocks in the predefined methods */ - // defineMockBehavior?: (database: MockDatabase, adapter: MockAdapter) => void; - /** Allows you to define additional tests */ - defineAdditionalTests?: () => void; + // allowedExitCodes?: number[]; + // additionalMockedModules?: StartMockAdapterOptions["additionalMockedModules"]; + // /** Change the default test timeout of 15000ms for the startup tests */ + // startTimeout?: number; + // /** Allows you to overwrite the default adapter config */ + // overwriteAdapterConfig?: ( + // config: Record, + // ) => Record; + // /** An array of objects that should be populated before starting the adapter */ + // predefinedObjects?: ioBroker.Object[]; + // /** A dictionary of states that should be populated before starting the adapter */ + // predefinedStates?: Record; + // /** Allows you to modify the behavior of predefined mocks in the predefined methods */ + // defineMockBehavior?: (database: MockDatabase, adapter: MockAdapter) => void; + /** Allows you to define additional tests */ + defineAdditionalTests?: () => void; } /** @@ -22,26 +22,17 @@ export interface TestAdapterOptions { * Tests the adapter startup in offline mode (with mocks, no JS-Controller) * This is meant to be executed in a mocha context. */ -export function testAdapterWithMocks( - _adapterDir: string, - options: TestAdapterOptions = {}, -): void { - describe(`Unit tests`, async () => { - // Call the user's tests - if (typeof options.defineAdditionalTests === "function") { - options.defineAdditionalTests(); - } else { - it("DEPRECATED!", () => { - console.warn( - "\u001b[33mUnit tests for adapter startup are deprecated!", - ); - console.warn( - `If you do not define your own tests, you can remove the "test:unit" script`, - ); - console.warn( - `from package.json and from your Travis/Github Actions workflow.\u001b[0m`, - ); - }); - } - }); +export function testAdapterWithMocks(_adapterDir: string, options: TestAdapterOptions = {}): void { + describe(`Unit tests`, async () => { + // Call the user's tests + if (typeof options.defineAdditionalTests === 'function') { + options.defineAdditionalTests(); + } else { + it('DEPRECATED!', () => { + console.warn('\u001b[33mUnit tests for adapter startup are deprecated!'); + console.warn(`If you do not define your own tests, you can remove the "test:unit" script`); + console.warn(`from package.json and from your Travis/Github Actions workflow.\u001b[0m`); + }); + } + }); } diff --git a/src/tests/unit/mocks/mockAdapter.ts b/src/tests/unit/mocks/mockAdapter.ts index aafc9174..758c144b 100644 --- a/src/tests/unit/mocks/mockAdapter.ts +++ b/src/tests/unit/mocks/mockAdapter.ts @@ -1,623 +1,582 @@ -import { extend, values } from "alcalzone-shared/objects"; -import { stub } from "sinon"; -import type { MockDatabase } from "./mockDatabase"; -import { createLoggerMock, MockLogger } from "./mockLogger"; +// @ts-expect-error no types +import { extend, values } from 'alcalzone-shared/objects'; +import { stub } from 'sinon'; +import type { MockDatabase } from './mockDatabase'; +import { createLoggerMock, MockLogger } from './mockLogger'; import { - doResetBehavior, - doResetHistory, - ImplementedMethodDictionary, - Mock, - stubAndPromisifyImplementedMethods, -} from "./tools"; + doResetBehavior, + doResetHistory, + ImplementedMethodDictionary, + Mock, + stubAndPromisifyImplementedMethods, +} from './tools'; // The mocked adapter interface has all the usual properties, but all methods are replaced with stubs export type MockAdapter = Mock & { - readyHandler: ioBroker.ReadyHandler | undefined; - objectChangeHandler: ioBroker.ObjectChangeHandler | undefined; - stateChangeHandler: ioBroker.StateChangeHandler | undefined; - messageHandler: ioBroker.MessageHandler | undefined; - unloadHandler: ioBroker.UnloadHandler | undefined; + readyHandler: ioBroker.ReadyHandler | undefined; + objectChangeHandler: ioBroker.ObjectChangeHandler | undefined; + stateChangeHandler: ioBroker.StateChangeHandler | undefined; + messageHandler: ioBroker.MessageHandler | undefined; + unloadHandler: ioBroker.UnloadHandler | undefined; - log: MockLogger; + log: MockLogger; - resetMock(): void; - resetMockHistory(): void; - resetMockBehavior(): void; + resetMock(): void; + resetMockHistory(): void; + resetMockBehavior(): void; }; // Define here which methods were implemented manually, so we can hook them up with a real stub // The value describes if and how the async version of the callback is constructed const implementedMethods: ImplementedMethodDictionary = { - getObject: "normal", - setObject: "normal", - setObjectNotExists: "normal", - extendObject: "normal", - getForeignObject: "normal", - getForeignObjects: "normal", - setForeignObject: "normal", - setForeignObjectNotExists: "normal", - extendForeignObject: "normal", - getState: "normal", - getStates: "normal", - setState: "normal", - setStateChanged: "normal", - delState: "normal", - getForeignState: "normal", - setForeignState: "normal", - setForeignStateChanged: "normal", - subscribeStates: "normal", - subscribeForeignStates: "normal", - subscribeObjects: "normal", - subscribeForeignObjects: "normal", - getAdapterObjects: "no error", - getObjectView: "normal", - getObjectList: "normal", - on: "none", - removeListener: "none", - removeAllListeners: "none", - terminate: "none", - getPort: "no error", - checkPassword: "no error", - setPassword: "normal", - checkGroup: "no error", - calculatePermissions: "no error", - getCertificates: "normal", - sendTo: "no error", - sendToHost: "no error", - getHistory: "normal", - // @ts-expect-error This method was deprecated - setBinaryState: "normal", - getBinaryState: "normal", - getEnum: "normal", - getEnums: "normal", - addChannelToEnum: "normal", - deleteChannelFromEnum: "normal", - addStateToEnum: "normal", - deleteStateFromEnum: "normal", - createDevice: "normal", - deleteDevice: "normal", - createChannel: "normal", - deleteChannel: "normal", - createState: "normal", - deleteState: "normal", - getDevices: "normal", - getChannelsOf: "normal", - getStatesOf: "normal", - readDir: "normal", - mkDir: "normal", - readFile: "normal", - writeFile: "normal", - delFile: "normal", - unlink: "normal", - rename: "normal", - chmodFile: "normal", + getObject: 'normal', + setObject: 'normal', + setObjectNotExists: 'normal', + extendObject: 'normal', + getForeignObject: 'normal', + getForeignObjects: 'normal', + setForeignObject: 'normal', + setForeignObjectNotExists: 'normal', + extendForeignObject: 'normal', + getState: 'normal', + getStates: 'normal', + setState: 'normal', + setStateChanged: 'normal', + delState: 'normal', + getForeignState: 'normal', + setForeignState: 'normal', + setForeignStateChanged: 'normal', + subscribeStates: 'normal', + subscribeForeignStates: 'normal', + subscribeObjects: 'normal', + subscribeForeignObjects: 'normal', + getAdapterObjects: 'no error', + getObjectView: 'normal', + getObjectList: 'normal', + on: 'none', + removeListener: 'none', + removeAllListeners: 'none', + terminate: 'none', + getPort: 'no error', + checkPassword: 'no error', + setPassword: 'normal', + checkGroup: 'no error', + calculatePermissions: 'no error', + getCertificates: 'normal', + sendTo: 'no error', + sendToHost: 'no error', + getHistory: 'normal', + // @ts-expect-error This method was deprecated + setBinaryState: 'normal', + getBinaryState: 'normal', + getEnum: 'normal', + getEnums: 'normal', + addChannelToEnum: 'normal', + deleteChannelFromEnum: 'normal', + addStateToEnum: 'normal', + deleteStateFromEnum: 'normal', + createDevice: 'normal', + deleteDevice: 'normal', + createChannel: 'normal', + deleteChannel: 'normal', + createState: 'normal', + deleteState: 'normal', + getDevices: 'normal', + getChannelsOf: 'normal', + getStatesOf: 'normal', + readDir: 'normal', + mkDir: 'normal', + readFile: 'normal', + writeFile: 'normal', + delFile: 'normal', + unlink: 'normal', + rename: 'normal', + chmodFile: 'normal', }; -function getCallback any>( - ...args: any[] -): T | undefined { - const lastArg = args[args.length - 1]; - if (typeof lastArg === "function") return lastArg as T; +function getCallback any>(...args: any[]): T | undefined { + const lastArg = args[args.length - 1]; + if (typeof lastArg === 'function') return lastArg as T; } /** Stub implementation which can be promisified */ const asyncEnabledStub = ((...args: any[]) => { - const callback = getCallback(...args); - if (typeof callback === "function") callback(); + const callback = getCallback(...args); + if (typeof callback === 'function') callback(); }) as sinon.SinonStub; /** * Creates an adapter mock that is connected to a given database mock */ export function createAdapterMock( - this: MockAdapter | void, - db: MockDatabase, - options: Partial = {}, + this: MockAdapter | void, + db: MockDatabase, + options: Partial = {}, ): MockAdapter { - // In order to support ES6-style adapters with inheritance, we need to work on the instance directly - const ret: MockAdapter = this || ({} as any); - Object.assign(ret, { - name: options.name || "test", - host: "testhost", - instance: options.instance || 0, - namespace: `${options.name || "test"}.${options.instance || 0}`, - config: options.config || {}, - common: {}, - systemConfig: null, - adapterDir: "", - ioPack: {}, - pack: {}, - log: createLoggerMock(), - version: "any", - connected: true, - - getPort: asyncEnabledStub, - stop: stub(), - - checkPassword: asyncEnabledStub, - setPassword: asyncEnabledStub, - checkGroup: asyncEnabledStub, - calculatePermissions: asyncEnabledStub, - getCertificates: asyncEnabledStub, - - sendTo: asyncEnabledStub, - sendToHost: asyncEnabledStub, - - idToDCS: stub(), - - getObject: ((id: string, ...args: any[]) => { - if (!id.startsWith(ret.namespace)) id = ret.namespace + "." + id; - const callback = getCallback(...args); - if (callback) callback(null, db.getObject(id)); - }) as sinon.SinonStub, - setObject: ((id: string, obj: ioBroker.Object, ...args: any[]) => { - if (!id.startsWith(ret.namespace)) id = ret.namespace + "." + id; - obj._id = id; - db.publishObject(obj); - const callback = getCallback(...args); - if (callback) callback(null, { id }); - }) as sinon.SinonStub, - setObjectNotExists: (( - id: string, - obj: ioBroker.Object, - ...args: any[] - ) => { - if (!id.startsWith(ret.namespace)) id = ret.namespace + "." + id; - const callback = getCallback(...args); - if (db.hasObject(id)) { - if (callback) callback(null, { id }); - } else { - ret.setObject(id, obj, callback); - } - }) as sinon.SinonStub, - getAdapterObjects: (( - callback: (objects: Record) => void, - ) => { - callback(db.getObjects(`${ret.namespace}.*`)); - }) as sinon.SinonStub, - - getObjectView: (( - design: string, - search: string, - { startkey, endkey }: { startkey?: string; endkey?: string }, - ...args: any[] - ) => { - if (design !== "system") { - throw new Error( - "If you want to use a custom design for getObjectView, you need to mock it yourself!", - ); - } - const callback = getCallback< - ioBroker.GetObjectViewCallback - >(...args); - if (typeof callback === "function") { - let objects = values(db.getObjects("*")); - objects = objects.filter((obj) => obj.type === search); - if (startkey) - objects = objects.filter((obj) => obj._id >= startkey); - if (endkey) - objects = objects.filter((obj) => obj._id <= endkey); - callback(null, { - rows: objects.map((obj) => ({ - id: obj._id, - value: obj, - })), - }); - } - }) as sinon.SinonStub, - - getObjectList: (( - { - startkey, - endkey, - include_docs, - }: { - startkey?: string; - endkey?: string; - include_docs?: boolean; - }, - ...args: any[] - ) => { - const callback = getCallback< - ioBroker.GetObjectListCallback - >(...args); - - if (typeof callback === "function") { - let objects = values(db.getObjects("*")); - if (startkey) - objects = objects.filter((obj) => obj._id >= startkey); - if (endkey) - objects = objects.filter((obj) => obj._id <= endkey); - if (!include_docs) - objects = objects.filter((obj) => !obj._id.startsWith("_")); - callback(null, { - rows: objects.map((obj) => ({ - id: obj._id, - value: obj, - doc: obj, - })), - }); - } - }) as sinon.SinonStub, - - extendObject: (( - id: string, - obj: ioBroker.PartialObject, - ...args: any[] - ) => { - if (!id.startsWith(ret.namespace)) id = ret.namespace + "." + id; - const existing = db.getObject(id) || {}; - const target = extend({}, existing, obj) as ioBroker.Object; - target._id = id; - db.publishObject(target); - const callback = getCallback( - ...args, - ); - if (callback) callback(null, { id: target._id, value: target }, id); - }) as sinon.SinonStub, - delObject: ((id: string, ...args: any[]) => { - if (!id.startsWith(ret.namespace)) id = ret.namespace + "." + id; - db.deleteObject(id); - const callback = getCallback(...args); - if (callback) callback(undefined); - }) as sinon.SinonStub, - - getForeignObject: ((id: string, ...args: any[]) => { - const callback = getCallback(...args); - if (callback) callback(null, db.getObject(id)); - }) as sinon.SinonStub, - getForeignObjects: ((pattern: string, ...args: any[]) => { - const type: ioBroker.ObjectType | undefined = - typeof args[0] === "string" - ? (args[0] as ioBroker.ObjectType) - : undefined; - const callback = getCallback(...args); - if (callback) callback(null, db.getObjects(pattern, type)); - }) as sinon.SinonStub, - setForeignObject: (( - id: string, - obj: ioBroker.Object, - ...args: any[] - ) => { - obj._id = id; - db.publishObject(obj); - const callback = getCallback(...args); - if (callback) callback(null, { id }); - }) as sinon.SinonStub, - setForeignObjectNotExists: (( - id: string, - obj: ioBroker.Object, - ...args: any[] - ) => { - const callback = getCallback(...args); - if (db.hasObject(id)) { - if (callback) callback(null, { id }); - } else { - ret.setObject(id, obj, callback); - } - }) as sinon.SinonStub, - extendForeignObject: (( - id: string, - obj: ioBroker.PartialObject, - ...args: any[] - ) => { - const target = db.getObject(id) || ({} as ioBroker.Object); - Object.assign(target, obj); - target._id = id; - db.publishObject(target); - const callback = getCallback( - ...args, - ); - if (callback) callback(null, { id: target._id, value: target }, id); - }) as sinon.SinonStub, - findForeignObject: stub(), - delForeignObject: ((id: string, ...args: any[]) => { - db.deleteObject(id); - const callback = getCallback(...args); - if (callback) callback(undefined); - }) as sinon.SinonStub, - - setState: ((id: string, state: any, ...args: any[]) => { - const callback = getCallback(...args); - - if (!id.startsWith(ret.namespace)) id = ret.namespace + "." + id; - - let ack: boolean; - if (state != null && typeof state === "object") { - ack = !!state.ack; - state = state.val; - } else { - ack = typeof args[0] === "boolean" ? args[0] : false; - } - - db.publishState(id, { val: state, ack }); - if (callback) callback(null, id); - }) as sinon.SinonStub, - setStateChanged: ((id: string, state: any, ...args: any[]) => { - const callback = getCallback(...args); - - let ack: boolean; - if (state != null && typeof state === "object") { - ack = !!state.ack; - state = state.val; - } else { - ack = typeof args[0] === "boolean" ? args[0] : false; - } - - if (!id.startsWith(ret.namespace)) id = ret.namespace + "." + id; - if (!db.hasState(id) || db.getState(id)!.val !== state) { - db.publishState(id, { val: state, ack }); - } - if (callback) callback(null, id); - }) as sinon.SinonStub, - setForeignState: ((id: string, state: any, ...args: any[]) => { - const callback = getCallback(...args); - - let ack: boolean; - if (state != null && typeof state === "object") { - ack = !!state.ack; - state = state.val; - } else { - ack = typeof args[0] === "boolean" ? args[0] : false; - } - - db.publishState(id, { val: state, ack }); - if (callback) callback(null, id); - }) as sinon.SinonStub, - setForeignStateChanged: ((id: string, state: any, ...args: any[]) => { - const callback = getCallback(...args); - - let ack: boolean; - if (state != null && typeof state === "object") { - ack = !!state.ack; - state = state.val; - } else { - ack = typeof args[0] === "boolean" ? args[0] : false; - } - - if (!db.hasState(id) || db.getState(id)!.val !== state) { - db.publishState(id, { val: state, ack }); - } - if (callback) callback(null, id); - }) as sinon.SinonStub, - - getState: ((id: string, ...args: any[]) => { - if (!id.startsWith(ret.namespace)) id = ret.namespace + "." + id; - const callback = getCallback(...args); - if (callback) callback(null, db.getState(id)); - }) as sinon.SinonStub, - getForeignState: ((id: string, ...args: any[]) => { - const callback = getCallback(...args); - if (callback) callback(null, db.getState(id)); - }) as sinon.SinonStub, - getStates: ((pattern: string, ...args: any[]) => { - if (!pattern.startsWith(ret.namespace)) - pattern = ret.namespace + "." + pattern; - const callback = getCallback(...args); - if (callback) callback(null, db.getStates(pattern)); - }) as sinon.SinonStub, - getForeignStates: ((pattern: string, ...args: any[]) => { - const callback = getCallback(...args); - if (callback) callback(null, db.getStates(pattern)); - }) as sinon.SinonStub, - - delState: ((id: string, ...args: any[]) => { - if (!id.startsWith(ret.namespace)) id = ret.namespace + "." + id; - db.deleteState(id); - const callback = getCallback(...args); - if (callback) callback(undefined); - }) as sinon.SinonStub, - delForeignState: ((id: string, ...args: any[]) => { - db.deleteState(id); - const callback = getCallback(...args); - if (callback) callback(undefined); - }) as sinon.SinonStub, - - getHistory: asyncEnabledStub, - - setBinaryState: asyncEnabledStub, - getBinaryState: asyncEnabledStub, - - getEnum: asyncEnabledStub, - getEnums: asyncEnabledStub, - - addChannelToEnum: asyncEnabledStub, - deleteChannelFromEnum: asyncEnabledStub, - - addStateToEnum: asyncEnabledStub, - deleteStateFromEnum: asyncEnabledStub, - - subscribeObjects: asyncEnabledStub, - subscribeForeignObjects: asyncEnabledStub, - unsubscribeObjects: asyncEnabledStub, - unsubscribeForeignObjects: asyncEnabledStub, - - subscribeStates: asyncEnabledStub, - subscribeForeignStates: asyncEnabledStub, - unsubscribeStates: asyncEnabledStub, - unsubscribeForeignStates: asyncEnabledStub, - - createDevice: asyncEnabledStub, - deleteDevice: asyncEnabledStub, - createChannel: asyncEnabledStub, - deleteChannel: asyncEnabledStub, - createState: asyncEnabledStub, - deleteState: asyncEnabledStub, - - getDevices: asyncEnabledStub, - getChannels: stub(), - getChannelsOf: asyncEnabledStub, - getStatesOf: asyncEnabledStub, - - readDir: asyncEnabledStub, - mkDir: asyncEnabledStub, - - readFile: asyncEnabledStub, - writeFile: asyncEnabledStub, - - delFile: asyncEnabledStub, - unlink: asyncEnabledStub, - - rename: asyncEnabledStub, - - chmodFile: asyncEnabledStub, - - formatValue: stub(), - formatDate: stub(), - - terminate: ((reason?: string | number, exitCode?: number) => { - if (typeof reason === "number") { - // Only the exit code was passed - exitCode = reason; - reason = undefined; - } - - const errorMessage = `Adapter.terminate was called${ - typeof exitCode === "number" ? ` (exit code ${exitCode})` : "" - }: ${reason ? reason : "Without reason"}`; - // Terminates execution by - const err = new Error(errorMessage); - // @ts-expect-error I'm too lazy to add terminateReason to the error type - err.terminateReason = reason || "no reason given!"; - throw err; - }) as any as sinon.SinonStub, - - supportsFeature: stub(), - getPluginInstance: stub(), - getPluginConfig: stub(), - - // EventEmitter methods - on: ((event: string, handler: (...args: any[]) => void) => { - // Remember the event handlers so we can call them on demand - switch (event) { - case "ready": - ret.readyHandler = handler; - break; - case "message": - ret.messageHandler = handler; - break; - case "objectChange": - ret.objectChangeHandler = handler; - break; - case "stateChange": - ret.stateChangeHandler = handler; - break; - case "unload": - ret.unloadHandler = handler; - break; - } - return ret; - }) as sinon.SinonStub, - - removeListener: (( - event: string, - _listener: (...args: any[]) => void, - ) => { - // TODO This is not entirely correct - switch (event) { - case "ready": - ret.readyHandler = undefined; - break; - case "message": - ret.messageHandler = undefined; - break; - case "objectChange": - ret.objectChangeHandler = undefined; - break; - case "stateChange": - ret.stateChangeHandler = undefined; - break; - case "unload": - ret.unloadHandler = undefined; - break; - } - return ret; - }) as sinon.SinonStub, - - removeAllListeners: ((event?: string) => { - if (!event || event === "ready") { - ret.readyHandler = undefined; - } - if (!event || event === "message") { - ret.messageHandler = undefined; - } - if (!event || event === "objectChange") { - ret.objectChangeHandler = undefined; - } - if (!event || event === "stateChange") { - ret.stateChangeHandler = undefined; - } - if (!event || event === "unload") { - ret.unloadHandler = undefined; - } - return ret; - }) as sinon.SinonStub, - - // Mock-specific methods - resetMockHistory() { - // reset Adapter - doResetHistory(ret); - (ret.log as MockLogger).resetMockHistory(); - }, - resetMockBehavior() { - // reset Adapter - doResetBehavior(ret, implementedMethods); - (ret.log as MockLogger).resetMockBehavior(); - }, - resetMock() { - ret.resetMockHistory(); - ret.resetMockBehavior(); - }, - } as unknown as MockAdapter); - - stubAndPromisifyImplementedMethods(ret, implementedMethods, [ - "getObjectView", - "getObjectList", - ]); - - // Access the options object directly, so we can react to later changes - Object.defineProperties(ret, { - readyHandler: { - get(): typeof options.ready { - return options.ready; - }, - set(handler: typeof options.ready) { - options.ready = handler; - }, - }, - messageHandler: { - get(): typeof options.message { - return options.message; - }, - set(handler: typeof options.message) { - options.message = handler; - }, - }, - objectChangeHandler: { - get(): typeof options.objectChange { - return options.objectChange; - }, - set(handler: typeof options.objectChange) { - options.objectChange = handler; - }, - }, - stateChangeHandler: { - get(): typeof options.stateChange { - return options.stateChange; - }, - set(handler: typeof options.stateChange) { - options.stateChange = handler; - }, - }, - unloadHandler: { - get(): typeof options.unload { - return options.unload; - }, - set(handler: typeof options.unload) { - options.unload = handler; - }, - }, - }); - - return ret; + // In order to support ES6-style adapters with inheritance, we need to work on the instance directly + const ret: MockAdapter = this || ({} as any); + Object.assign(ret, { + name: options.name || 'test', + host: 'testhost', + instance: options.instance || 0, + namespace: `${options.name || 'test'}.${options.instance || 0}`, + config: options.config || {}, + common: {}, + systemConfig: null, + adapterDir: '', + ioPack: {}, + pack: {}, + log: createLoggerMock(), + version: 'any', + connected: true, + + getPort: asyncEnabledStub, + stop: stub(), + + checkPassword: asyncEnabledStub, + setPassword: asyncEnabledStub, + checkGroup: asyncEnabledStub, + calculatePermissions: asyncEnabledStub, + getCertificates: asyncEnabledStub, + + sendTo: asyncEnabledStub, + sendToHost: asyncEnabledStub, + + idToDCS: stub(), + + getObject: ((id: string, ...args: any[]) => { + if (!id.startsWith(ret.namespace)) id = ret.namespace + '.' + id; + const callback = getCallback(...args); + if (callback) callback(null, db.getObject(id)); + }) as sinon.SinonStub, + setObject: ((id: string, obj: ioBroker.Object, ...args: any[]) => { + if (!id.startsWith(ret.namespace)) id = ret.namespace + '.' + id; + obj._id = id; + db.publishObject(obj); + const callback = getCallback(...args); + if (callback) callback(null, { id }); + }) as sinon.SinonStub, + setObjectNotExists: ((id: string, obj: ioBroker.Object, ...args: any[]) => { + if (!id.startsWith(ret.namespace)) id = ret.namespace + '.' + id; + const callback = getCallback(...args); + if (db.hasObject(id)) { + if (callback) callback(null, { id }); + } else { + ret.setObject(id, obj, callback); + } + }) as sinon.SinonStub, + getAdapterObjects: ((callback: (objects: Record) => void) => { + callback(db.getObjects(`${ret.namespace}.*`)); + }) as sinon.SinonStub, + + getObjectView: (( + design: string, + search: string, + { startkey, endkey }: { startkey?: string; endkey?: string }, + ...args: any[] + ) => { + if (design !== 'system') { + throw new Error('If you want to use a custom design for getObjectView, you need to mock it yourself!'); + } + const callback = getCallback>(...args); + if (typeof callback === 'function') { + let objects: ioBroker.Object[] = values(db.getObjects('*')); + objects = objects.filter(obj => obj.type === search); + if (startkey) objects = objects.filter(obj => obj._id >= startkey); + if (endkey) objects = objects.filter(obj => obj._id <= endkey); + callback(null, { + rows: objects.map(obj => ({ + id: obj._id, + value: obj, + })), + }); + } + }) as sinon.SinonStub, + + getObjectList: (( + { + startkey, + endkey, + include_docs, + }: { + startkey?: string; + endkey?: string; + include_docs?: boolean; + }, + ...args: any[] + ) => { + const callback = getCallback>(...args); + + if (typeof callback === 'function') { + let objects: ioBroker.Object[] = values(db.getObjects('*')); + if (startkey) { + objects = objects.filter(obj => obj._id >= startkey); + } + if (endkey) { + objects = objects.filter(obj => obj._id <= endkey); + } + if (!include_docs) { + objects = objects.filter(obj => !obj._id.startsWith('_')); + } + callback(null, { + rows: objects.map(obj => ({ + id: obj._id, + value: obj, + doc: obj, + })), + }); + } + }) as sinon.SinonStub, + + extendObject: ((id: string, obj: ioBroker.PartialObject, ...args: any[]) => { + if (!id.startsWith(ret.namespace)) id = ret.namespace + '.' + id; + const existing = db.getObject(id) || {}; + const target = extend({}, existing, obj) as ioBroker.Object; + target._id = id; + db.publishObject(target); + const callback = getCallback(...args); + if (callback) callback(null, { id: target._id, value: target }, id); + }) as sinon.SinonStub, + delObject: ((id: string, ...args: any[]) => { + if (!id.startsWith(ret.namespace)) id = ret.namespace + '.' + id; + db.deleteObject(id); + const callback = getCallback(...args); + if (callback) callback(undefined); + }) as sinon.SinonStub, + + getForeignObject: ((id: string, ...args: any[]) => { + const callback = getCallback(...args); + if (callback) callback(null, db.getObject(id)); + }) as sinon.SinonStub, + getForeignObjects: ((pattern: string, ...args: any[]) => { + const type: ioBroker.ObjectType | undefined = + typeof args[0] === 'string' ? (args[0] as ioBroker.ObjectType) : undefined; + const callback = getCallback(...args); + if (callback) callback(null, db.getObjects(pattern, type)); + }) as sinon.SinonStub, + setForeignObject: ((id: string, obj: ioBroker.Object, ...args: any[]) => { + obj._id = id; + db.publishObject(obj); + const callback = getCallback(...args); + if (callback) callback(null, { id }); + }) as sinon.SinonStub, + setForeignObjectNotExists: ((id: string, obj: ioBroker.Object, ...args: any[]) => { + const callback = getCallback(...args); + if (db.hasObject(id)) { + if (callback) callback(null, { id }); + } else { + ret.setObject(id, obj, callback); + } + }) as sinon.SinonStub, + extendForeignObject: ((id: string, obj: ioBroker.PartialObject, ...args: any[]) => { + const target = db.getObject(id) || ({} as ioBroker.Object); + Object.assign(target, obj); + target._id = id; + db.publishObject(target); + const callback = getCallback(...args); + if (callback) callback(null, { id: target._id, value: target }, id); + }) as sinon.SinonStub, + findForeignObject: stub(), + delForeignObject: ((id: string, ...args: any[]) => { + db.deleteObject(id); + const callback = getCallback(...args); + if (callback) callback(undefined); + }) as sinon.SinonStub, + + setState: ((id: string, state: any, ...args: any[]) => { + const callback = getCallback(...args); + + if (!id.startsWith(ret.namespace)) id = ret.namespace + '.' + id; + + let ack: boolean; + if (state != null && typeof state === 'object') { + ack = !!state.ack; + state = state.val; + } else { + ack = typeof args[0] === 'boolean' ? args[0] : false; + } + + db.publishState(id, { val: state, ack }); + if (callback) callback(null, id); + }) as sinon.SinonStub, + setStateChanged: ((id: string, state: any, ...args: any[]) => { + const callback = getCallback(...args); + + let ack: boolean; + if (state != null && typeof state === 'object') { + ack = !!state.ack; + state = state.val; + } else { + ack = typeof args[0] === 'boolean' ? args[0] : false; + } + + if (!id.startsWith(ret.namespace)) id = ret.namespace + '.' + id; + if (!db.hasState(id) || db.getState(id)!.val !== state) { + db.publishState(id, { val: state, ack }); + } + if (callback) callback(null, id); + }) as sinon.SinonStub, + setForeignState: ((id: string, state: any, ...args: any[]) => { + const callback = getCallback(...args); + + let ack: boolean; + if (state != null && typeof state === 'object') { + ack = !!state.ack; + state = state.val; + } else { + ack = typeof args[0] === 'boolean' ? args[0] : false; + } + + db.publishState(id, { val: state, ack }); + if (callback) callback(null, id); + }) as sinon.SinonStub, + setForeignStateChanged: ((id: string, state: any, ...args: any[]) => { + const callback = getCallback(...args); + + let ack: boolean; + if (state != null && typeof state === 'object') { + ack = !!state.ack; + state = state.val; + } else { + ack = typeof args[0] === 'boolean' ? args[0] : false; + } + + if (!db.hasState(id) || db.getState(id)!.val !== state) { + db.publishState(id, { val: state, ack }); + } + if (callback) callback(null, id); + }) as sinon.SinonStub, + + getState: ((id: string, ...args: any[]) => { + if (!id.startsWith(ret.namespace)) id = ret.namespace + '.' + id; + const callback = getCallback(...args); + if (callback) callback(null, db.getState(id)); + }) as sinon.SinonStub, + getForeignState: ((id: string, ...args: any[]) => { + const callback = getCallback(...args); + if (callback) callback(null, db.getState(id)); + }) as sinon.SinonStub, + getStates: ((pattern: string, ...args: any[]) => { + if (!pattern.startsWith(ret.namespace)) pattern = ret.namespace + '.' + pattern; + const callback = getCallback(...args); + if (callback) callback(null, db.getStates(pattern)); + }) as sinon.SinonStub, + getForeignStates: ((pattern: string, ...args: any[]) => { + const callback = getCallback(...args); + if (callback) callback(null, db.getStates(pattern)); + }) as sinon.SinonStub, + + delState: ((id: string, ...args: any[]) => { + if (!id.startsWith(ret.namespace)) id = ret.namespace + '.' + id; + db.deleteState(id); + const callback = getCallback(...args); + if (callback) callback(undefined); + }) as sinon.SinonStub, + delForeignState: ((id: string, ...args: any[]) => { + db.deleteState(id); + const callback = getCallback(...args); + if (callback) callback(undefined); + }) as sinon.SinonStub, + + getHistory: asyncEnabledStub, + + setBinaryState: asyncEnabledStub, + getBinaryState: asyncEnabledStub, + + getEnum: asyncEnabledStub, + getEnums: asyncEnabledStub, + + addChannelToEnum: asyncEnabledStub, + deleteChannelFromEnum: asyncEnabledStub, + + addStateToEnum: asyncEnabledStub, + deleteStateFromEnum: asyncEnabledStub, + + subscribeObjects: asyncEnabledStub, + subscribeForeignObjects: asyncEnabledStub, + unsubscribeObjects: asyncEnabledStub, + unsubscribeForeignObjects: asyncEnabledStub, + + subscribeStates: asyncEnabledStub, + subscribeForeignStates: asyncEnabledStub, + unsubscribeStates: asyncEnabledStub, + unsubscribeForeignStates: asyncEnabledStub, + + createDevice: asyncEnabledStub, + deleteDevice: asyncEnabledStub, + createChannel: asyncEnabledStub, + deleteChannel: asyncEnabledStub, + createState: asyncEnabledStub, + deleteState: asyncEnabledStub, + + getDevices: asyncEnabledStub, + getChannels: stub(), + getChannelsOf: asyncEnabledStub, + getStatesOf: asyncEnabledStub, + + readDir: asyncEnabledStub, + mkDir: asyncEnabledStub, + + readFile: asyncEnabledStub, + writeFile: asyncEnabledStub, + + delFile: asyncEnabledStub, + unlink: asyncEnabledStub, + + rename: asyncEnabledStub, + + chmodFile: asyncEnabledStub, + + formatValue: stub(), + formatDate: stub(), + + terminate: ((reason?: string | number, exitCode?: number) => { + if (typeof reason === 'number') { + // Only the exit code was passed + exitCode = reason; + reason = undefined; + } + + const errorMessage = `Adapter.terminate was called${ + typeof exitCode === 'number' ? ` (exit code ${exitCode})` : '' + }: ${reason ? reason : 'Without reason'}`; + // Terminates execution by + const err = new Error(errorMessage); + // @ts-expect-error I'm too lazy to add terminateReason to the error type + err.terminateReason = reason || 'no reason given!'; + throw err; + }) as any as sinon.SinonStub, + + supportsFeature: stub(), + getPluginInstance: stub(), + getPluginConfig: stub(), + + // EventEmitter methods + on: ((event: string, handler: (...args: any[]) => void) => { + // Remember the event handlers so we can call them on demand + switch (event) { + case 'ready': + ret.readyHandler = handler; + break; + case 'message': + ret.messageHandler = handler; + break; + case 'objectChange': + ret.objectChangeHandler = handler; + break; + case 'stateChange': + ret.stateChangeHandler = handler; + break; + case 'unload': + ret.unloadHandler = handler; + break; + } + return ret; + }) as sinon.SinonStub, + + removeListener: ((event: string, _listener: (...args: any[]) => void) => { + // TODO This is not entirely correct + switch (event) { + case 'ready': + ret.readyHandler = undefined; + break; + case 'message': + ret.messageHandler = undefined; + break; + case 'objectChange': + ret.objectChangeHandler = undefined; + break; + case 'stateChange': + ret.stateChangeHandler = undefined; + break; + case 'unload': + ret.unloadHandler = undefined; + break; + } + return ret; + }) as sinon.SinonStub, + + removeAllListeners: ((event?: string) => { + if (!event || event === 'ready') { + ret.readyHandler = undefined; + } + if (!event || event === 'message') { + ret.messageHandler = undefined; + } + if (!event || event === 'objectChange') { + ret.objectChangeHandler = undefined; + } + if (!event || event === 'stateChange') { + ret.stateChangeHandler = undefined; + } + if (!event || event === 'unload') { + ret.unloadHandler = undefined; + } + return ret; + }) as sinon.SinonStub, + + // Mock-specific methods + resetMockHistory() { + // reset Adapter + doResetHistory(ret); + (ret.log as MockLogger).resetMockHistory(); + }, + resetMockBehavior() { + // reset Adapter + doResetBehavior(ret, implementedMethods); + (ret.log as MockLogger).resetMockBehavior(); + }, + resetMock() { + ret.resetMockHistory(); + ret.resetMockBehavior(); + }, + } as unknown as MockAdapter); + + stubAndPromisifyImplementedMethods(ret, implementedMethods, ['getObjectView', 'getObjectList']); + + // Access the options object directly, so we can react to later changes + Object.defineProperties(ret, { + readyHandler: { + get(): typeof options.ready { + return options.ready; + }, + set(handler: typeof options.ready) { + options.ready = handler; + }, + }, + messageHandler: { + get(): typeof options.message { + return options.message; + }, + set(handler: typeof options.message) { + options.message = handler; + }, + }, + objectChangeHandler: { + get(): typeof options.objectChange { + return options.objectChange; + }, + set(handler: typeof options.objectChange) { + options.objectChange = handler; + }, + }, + stateChangeHandler: { + get(): typeof options.stateChange { + return options.stateChange; + }, + set(handler: typeof options.stateChange) { + options.stateChange = handler; + }, + }, + unloadHandler: { + get(): typeof options.unload { + return options.unload; + }, + set(handler: typeof options.unload) { + options.unload = handler; + }, + }, + }); + + return ret; } diff --git a/src/tests/unit/mocks/mockAdapterCore.ts b/src/tests/unit/mocks/mockAdapterCore.ts index 31fc2f28..62733dc5 100644 --- a/src/tests/unit/mocks/mockAdapterCore.ts +++ b/src/tests/unit/mocks/mockAdapterCore.ts @@ -1,79 +1,64 @@ -import * as os from "os"; -import * as path from "path"; -import { createAdapterMock, MockAdapter } from "./mockAdapter"; -import type { MockDatabase } from "./mockDatabase"; +import * as os from 'os'; +import * as path from 'path'; +import { createAdapterMock, MockAdapter } from './mockAdapter'; +import type { MockDatabase } from './mockDatabase'; interface MockAdapterConstructor { - new (nameOrOptions: string | ioBroker.AdapterOptions): MockAdapter; - (nameOrOptions: string | ioBroker.AdapterOptions): MockAdapter; + new (nameOrOptions: string | ioBroker.AdapterOptions): MockAdapter; + (nameOrOptions: string | ioBroker.AdapterOptions): MockAdapter; } export interface MockAdapterCoreOptions { - onAdapterCreated?: (adapter: MockAdapter) => void; - adapterDir?: string; + onAdapterCreated?: (adapter: MockAdapter) => void; + adapterDir?: string; } // eslint-disable-next-line @typescript-eslint/explicit-function-return-type -export function mockAdapterCore( - database: MockDatabase, - options: MockAdapterCoreOptions = {}, -) { - /** - * The root directory of JS-Controller - * If this has to exist in the test, the user/tester has to take care of it! - */ - const controllerDir = path.join( - options.adapterDir || "", - "..", - "iobroker.js-controller", - ); +export function mockAdapterCore(database: MockDatabase, options: MockAdapterCoreOptions = {}) { + /** + * The root directory of JS-Controller + * If this has to exist in the test, the user/tester has to take care of it! + */ + const controllerDir = path.join(options.adapterDir || '', '..', 'iobroker.js-controller'); - const dataDir = path.join(os.tmpdir(), `test-iobroker-data`); - /** - * The test location for iobroker-data - * If this has to exist in the test, the user/tester has to take care of it! - */ - function getAbsoluteDefaultDataDir(): string { - return dataDir; - } + const dataDir = path.join(os.tmpdir(), `test-iobroker-data`); + /** + * The test location for iobroker-data + * If this has to exist in the test, the user/tester has to take care of it! + */ + function getAbsoluteDefaultDataDir(): string { + return dataDir; + } - /** - * The test location for adapter-specific data - * If this has to exist in the test, the user/tester has to take care of it! - */ - function getAbsoluteInstanceDataDir(adapterObject: MockAdapter): string { - return path.join(getAbsoluteDefaultDataDir(), adapterObject.namespace); - } + /** + * The test location for adapter-specific data + * If this has to exist in the test, the user/tester has to take care of it! + */ + function getAbsoluteInstanceDataDir(adapterObject: MockAdapter): string { + return path.join(getAbsoluteDefaultDataDir(), adapterObject.namespace); + } - /** Reads the configuration file of JS-Controller */ - function getConfig(): Record { - return {}; - } + /** Reads the configuration file of JS-Controller */ + function getConfig(): Record { + return {}; + } - const AdapterConstructor = function ( - this: MockAdapter | void, - nameOrOptions: string | ioBroker.AdapterOptions, - ) { - // This needs to be a class with the correct `this` context or the ES6 tests won't work - if (!(this instanceof AdapterConstructor)) - return new AdapterConstructor(nameOrOptions); + const AdapterConstructor = function (this: MockAdapter | void, nameOrOptions: string | ioBroker.AdapterOptions) { + // This needs to be a class with the correct `this` context or the ES6 tests won't work + if (!(this instanceof AdapterConstructor)) return new AdapterConstructor(nameOrOptions); - const createAdapterMockOptions = - typeof nameOrOptions === "string" - ? { name: nameOrOptions } - : nameOrOptions; - createAdapterMock.bind(this)(database, createAdapterMockOptions); - if (typeof options.onAdapterCreated === "function") - options.onAdapterCreated(this); - return this; - } as MockAdapterConstructor; + const createAdapterMockOptions = typeof nameOrOptions === 'string' ? { name: nameOrOptions } : nameOrOptions; + createAdapterMock.bind(this)(database, createAdapterMockOptions); + if (typeof options.onAdapterCreated === 'function') options.onAdapterCreated(this); + return this; + } as MockAdapterConstructor; - return { - controllerDir, - getConfig, - Adapter: AdapterConstructor, - adapter: AdapterConstructor, - getAbsoluteDefaultDataDir, - getAbsoluteInstanceDataDir, - }; + return { + controllerDir, + getConfig, + Adapter: AdapterConstructor, + adapter: AdapterConstructor, + getAbsoluteDefaultDataDir, + getAbsoluteInstanceDataDir, + }; } diff --git a/src/tests/unit/mocks/mockDatabase.ts b/src/tests/unit/mocks/mockDatabase.ts index 8e8f124b..a09d6bfc 100644 --- a/src/tests/unit/mocks/mockDatabase.ts +++ b/src/tests/unit/mocks/mockDatabase.ts @@ -1,187 +1,150 @@ -import { composeObject, extend } from "alcalzone-shared/objects"; -import { isArray } from "alcalzone-shared/typeguards"; -import { str2regex } from "../../../lib/str2regex"; -import type { MockAdapter } from "./mockAdapter"; +// @ts-expect-error no types +import { composeObject, extend } from 'alcalzone-shared/objects'; +import { str2regex } from '../../../lib/str2regex'; +import type { MockAdapter } from './mockAdapter'; const objectTemplate = Object.freeze({ - type: "state", - common: { name: "an object" }, - native: {}, + type: 'state', + common: { name: 'an object' }, + native: {}, } as ioBroker.Object); const stateTemplate = Object.freeze({ - ack: false, - val: 0, + ack: false, + val: 0, } as ioBroker.State); /** * A minimalistic version of ioBroker's Objects and States DB that just operates on a Map */ export class MockDatabase { - public objects = new Map(); - public states = new Map(); - - public clearObjects(): void { - this.objects.clear(); - } - public clearStates(): void { - this.states.clear(); - } - public clear(): void { - this.clearObjects(); - this.clearStates(); - } - - public publishObject(obj: ioBroker.PartialObject): void { - if (obj._id == null) throw new Error("An object must have an ID"); - if (obj.type == null) throw new Error("An object must have a type"); - - const completeObject = extend( - {}, - objectTemplate, - obj, - ) as ioBroker.Object; - this.objects.set(obj._id, completeObject); - } - public publishObjects(...objects: ioBroker.PartialObject[]): void { - objects.forEach(this.publishObject.bind(this)); - } - public publishStateObjects(...objects: ioBroker.PartialObject[]): void { - objects - .map((obj) => extend({}, obj, { type: "state" })) - .forEach(this.publishObject.bind(this)); - } - public publishChannelObjects(...objects: ioBroker.PartialObject[]): void { - objects - .map((obj) => extend({}, obj, { type: "channel" })) - .forEach(this.publishObject.bind(this)); - } - public publishDeviceObjects(...objects: ioBroker.PartialObject[]): void { - objects - .map((obj) => extend({}, obj, { type: "device" })) - .forEach(this.publishObject.bind(this)); - } - - public deleteObject(obj: ioBroker.PartialObject): void; - public deleteObject(objID: string): void; - public deleteObject(objOrID: string | ioBroker.PartialObject): void { - this.objects.delete( - typeof objOrID === "string" ? objOrID : objOrID._id!, - ); - } - - public publishState( - id: string, - state: Partial | null | undefined, - ): void { - // if (typeof id !== "string") throw new Error("The id must be given!"); - if (state == null) { - this.deleteState(id); - return; - } - const completeState = extend( - {}, - stateTemplate, - state, - ) as ioBroker.State; - this.states.set(id, completeState); - } - public deleteState(id: string): void { - this.states.delete(id); - } - public publishStates( - states: Record | null | undefined>, - ): void { - for (const id of Object.keys(states)) { - this.publishState(id, states[id]); - } - } - - public hasObject(id: string): boolean; - public hasObject(namespace: string, id: string): boolean; - public hasObject(namespaceOrId: string, id?: string): boolean { - id = namespaceOrId + (id ? "." + id : ""); - return this.objects.has(id); - } - - public getObject(id: string): ioBroker.Object | undefined; - public getObject( - namespace: string, - id: string, - ): ioBroker.Object | undefined; - public getObject( - namespaceOrId: string, - id?: string, - ): ioBroker.Object | undefined { - // combines getObject and getForeignObject into one - id = namespaceOrId + (id ? "." + id : ""); - return this.objects.get(id); - } - - public hasState(id: string): boolean; - public hasState(namespace: string, id: string): boolean; - public hasState(namespaceOrId: string, id?: string): boolean { - id = namespaceOrId + (id ? "." + id : ""); - return this.states.has(id); - } - - public getState(id: string): ioBroker.State | undefined; - public getState(namespace: string, id: string): ioBroker.State | undefined; - public getState( - namespaceOrId: string, - id?: string, - ): ioBroker.State | undefined { - // combines getObject and getForeignObject into one - id = namespaceOrId + (id ? "." + id : ""); - return this.states.get(id); - } - - public getObjects( - pattern: string, - type?: ioBroker.ObjectType, - ): Record; - public getObjects( - namespace: string, - pattern: string, - type?: ioBroker.ObjectType, - ): Record; - public getObjects( - namespaceOrPattern: string, - patternOrType?: string | ioBroker.ObjectType, - type?: ioBroker.ObjectType, - ): Record { - // combines getObjects and getForeignObjects into one - let pattern: string; - if (type != null) { - pattern = - namespaceOrPattern + (patternOrType ? "." + patternOrType : ""); - } else if (patternOrType != null) { - if (["state", "channel", "device"].indexOf(patternOrType) > -1) { - type = patternOrType as ioBroker.ObjectType; - pattern = namespaceOrPattern; - } else { - pattern = namespaceOrPattern + "." + patternOrType; - } - } else { - pattern = namespaceOrPattern; - } - - const idRegExp = str2regex(pattern); - - return composeObject( - [...this.objects.entries()] - .filter(([id]) => idRegExp.test(id)) - .filter(([, obj]) => type == null || obj.type === type), - ); - } - - public getStates(pattern: string): Record { - // combines getStates and getForeignStates into one - const idRegExp = str2regex(pattern); - return composeObject( - [...this.states.entries()].filter(([id]) => idRegExp.test(id)), - ) as Record; - } + public objects = new Map(); + public states = new Map(); + + public clearObjects(): void { + this.objects.clear(); + } + public clearStates(): void { + this.states.clear(); + } + public clear(): void { + this.clearObjects(); + this.clearStates(); + } + + public publishObject(obj: ioBroker.PartialObject): void { + if (obj._id == null) throw new Error('An object must have an ID'); + if (obj.type == null) throw new Error('An object must have a type'); + + const completeObject = extend({}, objectTemplate, obj) as ioBroker.Object; + this.objects.set(obj._id, completeObject); + } + public publishObjects(...objects: ioBroker.PartialObject[]): void { + objects.forEach(this.publishObject.bind(this)); + } + public publishStateObjects(...objects: ioBroker.PartialObject[]): void { + objects.map(obj => extend({}, obj, { type: 'state' })).forEach(this.publishObject.bind(this)); + } + public publishChannelObjects(...objects: ioBroker.PartialObject[]): void { + objects.map(obj => extend({}, obj, { type: 'channel' })).forEach(this.publishObject.bind(this)); + } + public publishDeviceObjects(...objects: ioBroker.PartialObject[]): void { + objects.map(obj => extend({}, obj, { type: 'device' })).forEach(this.publishObject.bind(this)); + } + + public deleteObject(obj: ioBroker.PartialObject): void; + public deleteObject(objID: string): void; + public deleteObject(objOrID: string | ioBroker.PartialObject): void { + this.objects.delete(typeof objOrID === 'string' ? objOrID : objOrID._id!); + } + + public publishState(id: string, state: Partial | null | undefined): void { + // if (typeof id !== "string") throw new Error("The id must be given!"); + if (state == null) { + this.deleteState(id); + return; + } + const completeState = extend({}, stateTemplate, state) as ioBroker.State; + this.states.set(id, completeState); + } + public deleteState(id: string): void { + this.states.delete(id); + } + public publishStates(states: Record | null | undefined>): void { + for (const id of Object.keys(states)) { + this.publishState(id, states[id]); + } + } + + public hasObject(id: string): boolean; + public hasObject(namespace: string, id: string): boolean; + public hasObject(namespaceOrId: string, id?: string): boolean { + id = namespaceOrId + (id ? '.' + id : ''); + return this.objects.has(id); + } + + public getObject(id: string): ioBroker.Object | undefined; + public getObject(namespace: string, id: string): ioBroker.Object | undefined; + public getObject(namespaceOrId: string, id?: string): ioBroker.Object | undefined { + // combines getObject and getForeignObject into one + id = namespaceOrId + (id ? '.' + id : ''); + return this.objects.get(id); + } + + public hasState(id: string): boolean; + public hasState(namespace: string, id: string): boolean; + public hasState(namespaceOrId: string, id?: string): boolean { + id = namespaceOrId + (id ? '.' + id : ''); + return this.states.has(id); + } + + public getState(id: string): ioBroker.State | undefined; + public getState(namespace: string, id: string): ioBroker.State | undefined; + public getState(namespaceOrId: string, id?: string): ioBroker.State | undefined { + // combines getObject and getForeignObject into one + id = namespaceOrId + (id ? '.' + id : ''); + return this.states.get(id); + } + + public getObjects(pattern: string, type?: ioBroker.ObjectType): Record; + public getObjects(namespace: string, pattern: string, type?: ioBroker.ObjectType): Record; + public getObjects( + namespaceOrPattern: string, + patternOrType?: string | ioBroker.ObjectType, + type?: ioBroker.ObjectType, + ): Record { + // combines getObjects and getForeignObjects into one + let pattern: string; + if (type != null) { + pattern = namespaceOrPattern + (patternOrType ? '.' + patternOrType : ''); + } else if (patternOrType != null) { + if (['state', 'channel', 'device'].indexOf(patternOrType) > -1) { + type = patternOrType as ioBroker.ObjectType; + pattern = namespaceOrPattern; + } else { + pattern = namespaceOrPattern + '.' + patternOrType; + } + } else { + pattern = namespaceOrPattern; + } + + const idRegExp = str2regex(pattern); + + return composeObject( + [...this.objects.entries()] + .filter(([id]) => idRegExp.test(id)) + .filter(([, obj]) => type == null || obj.type === type), + ); + } + + public getStates(pattern: string): Record { + // combines getStates and getForeignStates into one + const idRegExp = str2regex(pattern); + return composeObject([...this.states.entries()].filter(([id]) => idRegExp.test(id))) as Record< + string, + ioBroker.State + >; + } } /** @@ -196,63 +159,56 @@ export class MockDatabase { */ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function createAsserts(db: MockDatabase, adapter: MockAdapter) { - function normalizeID(id: string | string[]): string { - if (isArray(id)) id = id.join("."); - // Test if this ID is fully qualified - if (!/^[a-z0-9\-_]+\.\d+\./.test(id)) { - id = adapter.namespace + "." + id; - } - return id; - } - const ret = { - assertObjectExists(id: string | string[]) { - id = normalizeID(id); - db.hasObject(id).should.equal( - true, - `The object "${adapter.namespace}.${id}" does not exist but it was expected to!`, - ); - }, - assertStateExists(id: string | string[]) { - id = normalizeID(id); - db.hasState(id).should.equal( - true, - `The state "${adapter.namespace}.${id}" does not exist but it was expected to!`, - ); - }, - assertStateHasValue(id: string | string[], value: any) { - ret.assertStateProperty(id, "val", value); - }, - assertStateIsAcked(id: string | string[], ack = true) { - ret.assertStateProperty(id, "ack", ack); - }, - assertStateProperty( - id: string | string[], - property: string, - value: any, - ) { - id = normalizeID(id); - ret.assertStateExists(id); - db.getState(id)! - .should.be.an("object") - .that.has.property(property, value); - }, - assertObjectCommon( - id: string | string[], - common: ioBroker.ObjectCommon, - ) { - id = normalizeID(id); - ret.assertObjectExists(id); - const dbObj = db.getObject(id)!; - dbObj.should.be.an("object").that.has.property("common"); - dbObj.common.should.be.an("object").that.nested.include(common); - }, - assertObjectNative(id: string | string[], native: Record) { - id = normalizeID(id); - ret.assertObjectExists(id); - const dbObj = db.getObject(id)!; - dbObj.should.be.an("object").that.has.property("native"); - dbObj.native.should.be.an("object").that.nested.include(native); - }, - }; - return ret; + function normalizeID(id: string | string[]): string { + if (Array.isArray(id)) { + id = id.join('.'); + } + // Test if this ID is fully qualified + if (!/^[a-z0-9\-_]+\.\d+\./.test(id)) { + id = `${adapter.namespace}.${id}`; + } + return id; + } + const ret = { + assertObjectExists(id: string | string[]) { + id = normalizeID(id); + db.hasObject(id).should.equal( + true, + `The object "${adapter.namespace}.${id}" does not exist but it was expected to!`, + ); + }, + assertStateExists(id: string | string[]) { + id = normalizeID(id); + db.hasState(id).should.equal( + true, + `The state "${adapter.namespace}.${id}" does not exist but it was expected to!`, + ); + }, + assertStateHasValue(id: string | string[], value: any) { + ret.assertStateProperty(id, 'val', value); + }, + assertStateIsAcked(id: string | string[], ack = true) { + ret.assertStateProperty(id, 'ack', ack); + }, + assertStateProperty(id: string | string[], property: string, value: any) { + id = normalizeID(id); + ret.assertStateExists(id); + db.getState(id)!.should.be.an('object').that.has.property(property, value); + }, + assertObjectCommon(id: string | string[], common: ioBroker.ObjectCommon) { + id = normalizeID(id); + ret.assertObjectExists(id); + const dbObj = db.getObject(id)!; + dbObj.should.be.an('object').that.has.property('common'); + dbObj.common.should.be.an('object').that.nested.include(common); + }, + assertObjectNative(id: string | string[], native: Record) { + id = normalizeID(id); + ret.assertObjectExists(id); + const dbObj = db.getObject(id)!; + dbObj.should.be.an('object').that.has.property('native'); + dbObj.native.should.be.an('object').that.nested.include(native); + }, + }; + return ret; } diff --git a/src/tests/unit/mocks/mockLogger.ts b/src/tests/unit/mocks/mockLogger.ts index 988d2297..489659f9 100644 --- a/src/tests/unit/mocks/mockLogger.ts +++ b/src/tests/unit/mocks/mockLogger.ts @@ -1,17 +1,17 @@ -import { stub } from "sinon"; +import { stub } from 'sinon'; import { - doResetBehavior, - doResetHistory, - ImplementedMethodDictionary, - Mock, - stubAndPromisifyImplementedMethods, -} from "./tools"; + doResetBehavior, + doResetHistory, + ImplementedMethodDictionary, + Mock, + stubAndPromisifyImplementedMethods, +} from './tools'; // The mocked objects interface has all the usual properties, but all methods are replaced with stubs export type MockLogger = Mock & { - resetMock(): void; - resetMockHistory(): void; - resetMockBehavior(): void; + resetMock(): void; + resetMockHistory(): void; + resetMockBehavior(): void; }; // Define here which methods were implemented manually, so we can hook them up with a real stub @@ -22,30 +22,30 @@ const implementedMethods: ImplementedMethodDictionary = {}; * Creates an adapter mock that is connected to a given database mock */ export function createLoggerMock(): MockLogger { - const ret = { - info: stub(), - warn: stub(), - error: stub(), - debug: stub(), - silly: stub(), - level: "info", + const ret = { + info: stub(), + warn: stub(), + error: stub(), + debug: stub(), + silly: stub(), + level: 'info', - // Mock-specific methods - resetMockHistory() { - // reset Logger - doResetHistory(ret); - }, - resetMockBehavior() { - // reset Logger - doResetBehavior(ret, implementedMethods); - }, - resetMock() { - ret.resetMockHistory(); - ret.resetMockBehavior(); - }, - } as MockLogger; + // Mock-specific methods + resetMockHistory() { + // reset Logger + doResetHistory(ret); + }, + resetMockBehavior() { + // reset Logger + doResetBehavior(ret, implementedMethods); + }, + resetMock() { + ret.resetMockHistory(); + ret.resetMockBehavior(); + }, + } as MockLogger; - stubAndPromisifyImplementedMethods(ret, implementedMethods); + stubAndPromisifyImplementedMethods(ret, implementedMethods); - return ret; + return ret; } diff --git a/src/tests/unit/mocks/tools.ts b/src/tests/unit/mocks/tools.ts index 05037a53..d9ca9899 100644 --- a/src/tests/unit/mocks/tools.ts +++ b/src/tests/unit/mocks/tools.ts @@ -1,6 +1,8 @@ -import { promisify, promisifyNoError } from "alcalzone-shared/async"; -import type { Equals, Overwrite } from "alcalzone-shared/types"; -import { stub } from "sinon"; +// @ts-expect-error no types +import { promisify, promisifyNoError } from 'alcalzone-shared/async'; +// @ts-expect-error no types +import type { Equals, Overwrite } from 'alcalzone-shared/types'; +import { stub } from 'sinon'; // IsAny exploits the fact that `any` may or may not be assignable to `never`, whereas all other types are export type IsAny = Equals; @@ -8,88 +10,69 @@ export type IsAny = Equals; // This rather complicated type extracts all functions from the given interface without including the properties that are `any` // It basically is `getObject | setObject | ...` export type MockableMethods< - T, - All = Required, - NoAny = { - [K in keyof All]: IsAny extends true - ? never - : All[K] extends (...args: any[]) => void - ? K - : never; - }, + T, + All = Required, + NoAny = { + [K in keyof All]: IsAny extends true ? never : All[K] extends (...args: any[]) => void ? K : never; + }, > = NoAny[keyof NoAny]; // eslint-disable-next-line @typescript-eslint/no-empty-object-type -export type Mock = Overwrite< - T, - { [K in MockableMethods]: sinon.SinonStub } ->; +export type Mock = Overwrite]: sinon.SinonStub }>; export function doResetHistory(parent: Record): void { - for (const prop of Object.keys(parent)) { - const val = parent[prop]; - if (val && typeof val.resetHistory === "function") val.resetHistory(); - } + for (const prop of Object.keys(parent)) { + const val = parent[prop]; + if (val && typeof val.resetHistory === 'function') val.resetHistory(); + } } -export function doResetBehavior( - parent: Record, - implementedMethods: Record, -): void { - for (const prop of Object.keys(parent)) { - if ( - prop in implementedMethods || - (prop.endsWith("Async") && prop.slice(0, -5) in implementedMethods) - ) - continue; - const val = parent[prop]; - if (val && typeof val.resetBehavior === "function") val.resetBehavior(); - } +export function doResetBehavior(parent: Record, implementedMethods: Record): void { + for (const prop of Object.keys(parent)) { + if (prop in implementedMethods || (prop.endsWith('Async') && prop.slice(0, -5) in implementedMethods)) continue; + const val = parent[prop]; + if (val && typeof val.resetBehavior === 'function') val.resetBehavior(); + } } function dontOverwriteThis(): never { - throw new Error("You must not overwrite the behavior of this stub!"); + throw new Error('You must not overwrite the behavior of this stub!'); } export function stubAndPromisifyImplementedMethods( - parent: Record, - implementedMethods: Partial>, - allowUserOverrides: T[] = [], + parent: Record, + implementedMethods: Partial>, + allowUserOverrides: T[] = [], ): void { - // The methods implemented above are no stubs, but we claimed they are - // Therefore hook them up with a real stub - for (const methodName of Object.keys(implementedMethods) as T[]) { - if (methodName.endsWith("Async")) continue; + // The methods implemented above are no stubs, but we claimed they are + // Therefore hook them up with a real stub + for (const methodName of Object.keys(implementedMethods) as T[]) { + if (methodName.endsWith('Async')) continue; - const originalMethod = parent[methodName]; - const callbackFake = (parent[methodName] = stub()); - callbackFake.callsFake(originalMethod); - // Prevent the user from changing the stub's behavior - if (allowUserOverrides.indexOf(methodName) === -1) { - callbackFake.returns = dontOverwriteThis; - callbackFake.callsFake = dontOverwriteThis; - } + const originalMethod = parent[methodName]; + const callbackFake = (parent[methodName] = stub()); + callbackFake.callsFake(originalMethod); + // Prevent the user from changing the stub's behavior + if (allowUserOverrides.indexOf(methodName) === -1) { + callbackFake.returns = dontOverwriteThis; + callbackFake.callsFake = dontOverwriteThis; + } - // Construct the async fake if there's any - const asyncType = implementedMethods[methodName]; - if (asyncType === "none") continue; - const promisifyMethod = - asyncType === "no error" ? promisifyNoError : promisify; - const asyncFake = stub().callsFake( - promisifyMethod(originalMethod, parent), - ); - parent[`${methodName}Async` as T] = asyncFake; - // Prevent the user from changing the stub's behavior - if ( - allowUserOverrides.indexOf(methodName) === -1 || - allowUserOverrides.indexOf((methodName + "Async") as T) === -1 - ) { - asyncFake.returns = dontOverwriteThis; - asyncFake.callsFake = dontOverwriteThis; - } - } + // Construct the async fake if there's any + const asyncType = implementedMethods[methodName]; + if (asyncType === 'none') continue; + const promisifyMethod = asyncType === 'no error' ? promisifyNoError : promisify; + const asyncFake = stub().callsFake(promisifyMethod(originalMethod, parent)); + parent[`${methodName}Async` as T] = asyncFake; + // Prevent the user from changing the stub's behavior + if ( + allowUserOverrides.indexOf(methodName) === -1 || + allowUserOverrides.indexOf((methodName + 'Async') as T) === -1 + ) { + asyncFake.returns = dontOverwriteThis; + asyncFake.callsFake = dontOverwriteThis; + } + } } -export type ImplementedMethodDictionary = Partial< - Record, "none" | "normal" | "no error"> ->; +export type ImplementedMethodDictionary = Partial, 'none' | 'normal' | 'no error'>>; diff --git a/test/mocha.setup.js b/test/mocha.setup.js index 8752b34f..994e27ba 100644 --- a/test/mocha.setup.js +++ b/test/mocha.setup.js @@ -1,20 +1,20 @@ -"use strict"; +'use strict'; // Makes ts-node ignore warnings, so mocha --watch does work -process.env.TS_NODE_IGNORE_WARNINGS = "TRUE"; +process.env.TS_NODE_IGNORE_WARNINGS = 'TRUE'; // Sets the correct tsconfig for testing -process.env.TS_NODE_PROJECT = "tsconfig.json"; +process.env.TS_NODE_PROJECT = 'tsconfig.json'; // Don't silently swallow unhandled rejections -process.on("unhandledRejection", (e) => { - throw e; +process.on('unhandledRejection', e => { + throw e; }); // enable the should interface with sinon // and load chai-as-promised and sinon-chai by default -const sinonChai = require("sinon-chai"); -const chaiAsPromised = require("chai-as-promised"); -const { should, use } = require("chai"); +const sinonChai = require('sinon-chai'); +const chaiAsPromised = require('chai-as-promised'); +const { should, use } = require('chai'); should(); use(sinonChai); diff --git a/test/unit/loader/adapter/main.js b/test/unit/loader/adapter/main.js index 5b2295c3..423f02c4 100644 --- a/test/unit/loader/adapter/main.js +++ b/test/unit/loader/adapter/main.js @@ -1,41 +1,41 @@ -"use strict"; +'use strict'; // The adapter-core module gives you access to the core ioBroker functions // you need to create an adapter -const utils = require("@iobroker/adapter-core"); +const utils = require('@iobroker/adapter-core'); /** * Starts the adapter instance * @param {Partial} [options] */ function startAdapter(options) { - // Create the adapter and define its methods - return utils.adapter( - Object.assign({}, options, { - name: "test-adapter", + // Create the adapter and define its methods + return utils.adapter( + Object.assign({}, options, { + name: 'test-adapter', - // The ready callback is called when databases are connected and adapter received configuration. - // start here! - ready: () => {}, + // The ready callback is called when databases are connected and adapter received configuration. + // start here! + ready: () => {}, - // is called when adapter shuts down - callback has to be called under any circumstances! - unload: (callback) => { - callback(); - }, + // is called when adapter shuts down - callback has to be called under any circumstances! + unload: callback => { + callback(); + }, - // is called if a subscribed object changes - objectChange: (id, obj) => {}, + // is called if a subscribed object changes + objectChange: (id, obj) => {}, - // is called if a subscribed state changes - stateChange: (id, state) => {}, - }), - ); + // is called if a subscribed state changes + stateChange: (id, state) => {}, + }), + ); } if (module.parent) { - // Export startAdapter in compact mode - module.exports = startAdapter; + // Export startAdapter in compact mode + module.exports = startAdapter; } else { - // otherwise start the instance directly - startAdapter(); + // otherwise start the instance directly + startAdapter(); } diff --git a/test/unit/loader/adapter/main_es6_assign.js b/test/unit/loader/adapter/main_es6_assign.js index 9b660099..908f36f3 100644 --- a/test/unit/loader/adapter/main_es6_assign.js +++ b/test/unit/loader/adapter/main_es6_assign.js @@ -1,80 +1,80 @@ // This file is used to test the unit test harness -"use strict"; +'use strict'; -const utils = require("@iobroker/adapter-core"); +const utils = require('@iobroker/adapter-core'); class TestAdapter extends utils.Adapter { - /** - * @param {Partial} [options={}] - */ - constructor(options = {}) { - /** @type {ioBroker.AdapterOptions} */ - const adapterOptions = options; - Object.assign(adapterOptions, { name: "test-adapter" }); - super(adapterOptions); - // After the super call, overwrite the methods on the options object - Object.assign(adapterOptions, { - ready: this.onReady.bind(this), - objectChange: this.onObjectChange.bind(this), - stateChange: this.onStateChange.bind(this), - // message: this.onMessage.bind(this), - unload: this.onUnload.bind(this), - }); - } + /** + * @param {Partial} [options={}] + */ + constructor(options = {}) { + /** @type {ioBroker.AdapterOptions} */ + const adapterOptions = options; + Object.assign(adapterOptions, { name: 'test-adapter' }); + super(adapterOptions); + // After the super call, overwrite the methods on the options object + Object.assign(adapterOptions, { + ready: this.onReady.bind(this), + objectChange: this.onObjectChange.bind(this), + stateChange: this.onStateChange.bind(this), + // message: this.onMessage.bind(this), + unload: this.onUnload.bind(this), + }); + } - /** - * Is called when databases are connected and adapter received configuration. - */ - onReady() {} + /** + * Is called when databases are connected and adapter received configuration. + */ + onReady() {} - /** - * Is called when adapter shuts down - callback has to be called under any circumstances! - * @param {() => void} callback - */ - onUnload(callback) { - callback(); - } + /** + * Is called when adapter shuts down - callback has to be called under any circumstances! + * @param {() => void} callback + */ + onUnload(callback) { + callback(); + } - /** - * Is called if a subscribed object changes - * @param {string} id - * @param {ioBroker.Object | null | undefined} obj - */ - onObjectChange(id, obj) {} + /** + * Is called if a subscribed object changes + * @param {string} id + * @param {ioBroker.Object | null | undefined} obj + */ + onObjectChange(id, obj) {} - /** - * Is called if a subscribed state changes - * @param {string} id - * @param {ioBroker.State | null | undefined} state - */ - onStateChange(id, state) {} + /** + * Is called if a subscribed state changes + * @param {string} id + * @param {ioBroker.State | null | undefined} state + */ + onStateChange(id, state) {} - // /** - // * Some message was sent to this instance over message box. Used by email, pushover, text2speech, ... - // * Using this method requires "common.message" property to be set to true in io-package.json - // * @param {ioBroker.Message} obj - // */ - // onMessage(obj) { - // if (typeof obj === "object" && obj.message) { - // if (obj.command === "send") { - // // e.g. send email or pushover or whatever - // this.log.info("send command"); + // /** + // * Some message was sent to this instance over message box. Used by email, pushover, text2speech, ... + // * Using this method requires "common.message" property to be set to true in io-package.json + // * @param {ioBroker.Message} obj + // */ + // onMessage(obj) { + // if (typeof obj === "object" && obj.message) { + // if (obj.command === "send") { + // // e.g. send email or pushover or whatever + // this.log.info("send command"); - // // Send response in callback if required - // if (obj.callback) this.sendTo(obj.from, obj.command, "Message received", obj.callback); - // } - // } - // } + // // Send response in callback if required + // if (obj.callback) this.sendTo(obj.from, obj.command, "Message received", obj.callback); + // } + // } + // } } if (module.parent) { - // Export the constructor in compact mode - /** - * @param {Partial} [options={}] - */ - module.exports = (options) => new TestAdapter(options); + // Export the constructor in compact mode + /** + * @param {Partial} [options={}] + */ + module.exports = options => new TestAdapter(options); } else { - // otherwise start the instance directly - new TestAdapter(); + // otherwise start the instance directly + new TestAdapter(); } diff --git a/test/unit/loader/adapter/main_es6_events.js b/test/unit/loader/adapter/main_es6_events.js index f01a2333..004b305d 100644 --- a/test/unit/loader/adapter/main_es6_events.js +++ b/test/unit/loader/adapter/main_es6_events.js @@ -1,77 +1,77 @@ // This file is used to test the unit test harness -"use strict"; +'use strict'; -const utils = require("@iobroker/adapter-core"); +const utils = require('@iobroker/adapter-core'); class TestAdapter extends utils.Adapter { - /** - * @param {Partial} [options={}] - */ - constructor(options) { - super({ - ...options, - name: "test-adapter", - }); - this.on("ready", this.onReady); - this.on("objectChange", this.onObjectChange); - this.on("stateChange", this.onStateChange); - // this.on("message", this.onMessage); - this.on("unload", this.onUnload); - } + /** + * @param {Partial} [options={}] + */ + constructor(options) { + super({ + ...options, + name: 'test-adapter', + }); + this.on('ready', this.onReady); + this.on('objectChange', this.onObjectChange); + this.on('stateChange', this.onStateChange); + // this.on("message", this.onMessage); + this.on('unload', this.onUnload); + } - /** - * Is called when databases are connected and adapter received configuration. - */ - onReady() {} + /** + * Is called when databases are connected and adapter received configuration. + */ + onReady() {} - /** - * Is called when adapter shuts down - callback has to be called under any circumstances! - * @param {() => void} callback - */ - onUnload(callback) { - callback(); - } + /** + * Is called when adapter shuts down - callback has to be called under any circumstances! + * @param {() => void} callback + */ + onUnload(callback) { + callback(); + } - /** - * Is called if a subscribed object changes - * @param {string} id - * @param {ioBroker.Object | null | undefined} obj - */ - onObjectChange(id, obj) {} + /** + * Is called if a subscribed object changes + * @param {string} id + * @param {ioBroker.Object | null | undefined} obj + */ + onObjectChange(id, obj) {} - /** - * Is called if a subscribed state changes - * @param {string} id - * @param {ioBroker.State | null | undefined} state - */ - onStateChange(id, state) {} + /** + * Is called if a subscribed state changes + * @param {string} id + * @param {ioBroker.State | null | undefined} state + */ + onStateChange(id, state) {} - // /** - // * Some message was sent to this instance over message box. Used by email, pushover, text2speech, ... - // * Using this method requires "common.message" property to be set to true in io-package.json - // * @param {ioBroker.Message} obj - // */ - // onMessage(obj) { - // if (typeof obj === "object" && obj.message) { - // if (obj.command === "send") { - // // e.g. send email or pushover or whatever - // this.log.info("send command"); + // /** + // * Some message was sent to this instance over message box. Used by email, pushover, text2speech, ... + // * Using this method requires "common.message" property to be set to true in io-package.json + // * @param {ioBroker.Message} obj + // */ + // onMessage(obj) { + // if (typeof obj === "object" && obj.message) { + // if (obj.command === "send") { + // // e.g. send email or pushover or whatever + // this.log.info("send command"); - // // Send response in callback if required - // if (obj.callback) this.sendTo(obj.from, obj.command, "Message received", obj.callback); - // } - // } - // } + // // Send response in callback if required + // if (obj.callback) this.sendTo(obj.from, obj.command, "Message received", obj.callback); + // } + // } + // } } if (module.parent) { - // Export the constructor in compact mode - /** - * @param {Partial} [options={}] - */ - module.exports = (options) => new TestAdapter(options); + // Export the constructor in compact mode + /** + * @param {Partial} [options={}] + */ + module.exports = options => new TestAdapter(options); } else { - // otherwise start the instance directly - new TestAdapter(); + // otherwise start the instance directly + new TestAdapter(); } diff --git a/test/unit/loader/asyncMocks/main.js b/test/unit/loader/asyncMocks/main.js index 3f7895f5..868e654e 100644 --- a/test/unit/loader/asyncMocks/main.js +++ b/test/unit/loader/asyncMocks/main.js @@ -1,51 +1,51 @@ -"use strict"; +'use strict'; // The adapter-core module gives you access to the core ioBroker functions // you need to create an adapter -const utils = require("@iobroker/adapter-core"); +const utils = require('@iobroker/adapter-core'); /** * Starts the adapter instance * @param {Partial} [options] */ function startAdapter(options) { - // Create the adapter and define its methods - const adapter = utils.adapter( - Object.assign({}, options, { - name: "test-adapter", + // Create the adapter and define its methods + const adapter = utils.adapter( + Object.assign({}, options, { + name: 'test-adapter', - // The ready callback is called when databases are connected and adapter received configuration. - // start here! - ready: async () => { - // Reported in worx adapter - await adapter.setStateAsync("foo", { val: 1, ack: true }); - // Reported in linkeddevices - await adapter.getForeignObjectsAsync("*"); - await adapter.subscribeStatesAsync("*"); - await adapter.subscribeForeignStatesAsync("*"); - // Added after https://github.com/ioBroker/testing/issues/249 - await adapter.getPortAsync(1000); - }, + // The ready callback is called when databases are connected and adapter received configuration. + // start here! + ready: async () => { + // Reported in worx adapter + await adapter.setStateAsync('foo', { val: 1, ack: true }); + // Reported in linkeddevices + await adapter.getForeignObjectsAsync('*'); + await adapter.subscribeStatesAsync('*'); + await adapter.subscribeForeignStatesAsync('*'); + // Added after https://github.com/ioBroker/testing/issues/249 + await adapter.getPortAsync(1000); + }, - // is called when adapter shuts down - callback has to be called under any circumstances! - unload: (callback) => { - callback(); - }, + // is called when adapter shuts down - callback has to be called under any circumstances! + unload: callback => { + callback(); + }, - // is called if a subscribed object changes - objectChange: (id, obj) => {}, + // is called if a subscribed object changes + objectChange: (id, obj) => {}, - // is called if a subscribed state changes - stateChange: (id, state) => {}, - }), - ); - return adapter; + // is called if a subscribed state changes + stateChange: (id, state) => {}, + }), + ); + return adapter; } if (module.parent) { - // Export startAdapter in compact mode - module.exports = startAdapter; + // Export startAdapter in compact mode + module.exports = startAdapter; } else { - // otherwise start the instance directly - startAdapter(); + // otherwise start the instance directly + startAdapter(); } diff --git a/test/unit/loader/dataDir/main.js b/test/unit/loader/dataDir/main.js index 50939287..d4f42d2f 100644 --- a/test/unit/loader/dataDir/main.js +++ b/test/unit/loader/dataDir/main.js @@ -1,35 +1,33 @@ // This file is used to test the unit test harness -"use strict"; +'use strict'; -const utils = require("@iobroker/adapter-core"); +const utils = require('@iobroker/adapter-core'); class TestAdapter extends utils.Adapter { - /** - * @param {Partial} [options={}] - */ - constructor(options) { - super({ - ...options, - name: "test-adapter", - ready: () => {}, - }); + /** + * @param {Partial} [options={}] + */ + constructor(options) { + super({ + ...options, + name: 'test-adapter', + ready: () => {}, + }); - // Ensure that the new mock methods work - console.assert(typeof utils.getAbsoluteDefaultDataDir() === "string"); - console.assert( - typeof utils.getAbsoluteInstanceDataDir(this) === "string", - ); - } + // Ensure that the new mock methods work + console.assert(typeof utils.getAbsoluteDefaultDataDir() === 'string'); + console.assert(typeof utils.getAbsoluteInstanceDataDir(this) === 'string'); + } } if (module.parent) { - // Export the constructor in compact mode - /** - * @param {Partial} [options={}] - */ - module.exports = (options) => new TestAdapter(options); + // Export the constructor in compact mode + /** + * @param {Partial} [options={}] + */ + module.exports = options => new TestAdapter(options); } else { - // otherwise start the instance directly - new TestAdapter(); + // otherwise start the instance directly + new TestAdapter(); } diff --git a/test/unit/loader/mocks/main.js b/test/unit/loader/mocks/main.js index a4398ae6..e7212099 100644 --- a/test/unit/loader/mocks/main.js +++ b/test/unit/loader/mocks/main.js @@ -1,8 +1,8 @@ -const utils = require("@iobroker/adapter-core"); +const utils = require('@iobroker/adapter-core'); module.exports = () => { - const secondary = require("./secondary"); - return { - main: utils.controllerDir, - secondary: secondary.controllerDir, - }; + const secondary = require('./secondary'); + return { + main: utils.controllerDir, + secondary: secondary.controllerDir, + }; }; diff --git a/test/unit/loader/mocks/secondary.js b/test/unit/loader/mocks/secondary.js index 8a71e0a1..066118d1 100644 --- a/test/unit/loader/mocks/secondary.js +++ b/test/unit/loader/mocks/secondary.js @@ -1,5 +1,5 @@ -const utils = require("@iobroker/adapter-core"); +const utils = require('@iobroker/adapter-core'); module.exports = { - controllerDir: utils.controllerDir, + controllerDir: utils.controllerDir, }; diff --git a/test/unit/loader/terminate/terminate_both.js b/test/unit/loader/terminate/terminate_both.js index e038363c..dc3bf0b4 100644 --- a/test/unit/loader/terminate/terminate_both.js +++ b/test/unit/loader/terminate/terminate_both.js @@ -1,43 +1,43 @@ -"use strict"; +'use strict'; // The adapter-core module gives you access to the core ioBroker functions // you need to create an adapter -const utils = require("@iobroker/adapter-core"); +const utils = require('@iobroker/adapter-core'); /** * Starts the adapter instance * @param {Partial} [options] */ function startAdapter(options) { - // Create the adapter and define its methods - const adapter = utils.adapter( - Object.assign({}, options, { - name: "test-adapter", + // Create the adapter and define its methods + const adapter = utils.adapter( + Object.assign({}, options, { + name: 'test-adapter', - // The ready callback is called when databases are connected and adapter received configuration. - // start here! - ready: () => { - adapter.terminate("reason", 2); - }, + // The ready callback is called when databases are connected and adapter received configuration. + // start here! + ready: () => { + adapter.terminate('reason', 2); + }, - // is called when adapter shuts down - callback has to be called under any circumstances! - unload: (callback) => { - callback(); - }, + // is called when adapter shuts down - callback has to be called under any circumstances! + unload: callback => { + callback(); + }, - // is called if a subscribed object changes - objectChange: (id, obj) => {}, + // is called if a subscribed object changes + objectChange: (id, obj) => {}, - // is called if a subscribed state changes - stateChange: (id, state) => {}, - }), - ); + // is called if a subscribed state changes + stateChange: (id, state) => {}, + }), + ); } if (module.parent) { - // Export startAdapter in compact mode - module.exports = startAdapter; + // Export startAdapter in compact mode + module.exports = startAdapter; } else { - // otherwise start the instance directly - startAdapter(); + // otherwise start the instance directly + startAdapter(); } diff --git a/test/unit/loader/terminate/terminate_code.js b/test/unit/loader/terminate/terminate_code.js index 54f54e10..40bf53a1 100644 --- a/test/unit/loader/terminate/terminate_code.js +++ b/test/unit/loader/terminate/terminate_code.js @@ -1,43 +1,43 @@ -"use strict"; +'use strict'; // The adapter-core module gives you access to the core ioBroker functions // you need to create an adapter -const utils = require("@iobroker/adapter-core"); +const utils = require('@iobroker/adapter-core'); /** * Starts the adapter instance * @param {Partial} [options] */ function startAdapter(options) { - // Create the adapter and define its methods - const adapter = utils.adapter( - Object.assign({}, options, { - name: "test-adapter", + // Create the adapter and define its methods + const adapter = utils.adapter( + Object.assign({}, options, { + name: 'test-adapter', - // The ready callback is called when databases are connected and adapter received configuration. - // start here! - ready: () => { - adapter.terminate(1); - }, + // The ready callback is called when databases are connected and adapter received configuration. + // start here! + ready: () => { + adapter.terminate(1); + }, - // is called when adapter shuts down - callback has to be called under any circumstances! - unload: (callback) => { - callback(); - }, + // is called when adapter shuts down - callback has to be called under any circumstances! + unload: callback => { + callback(); + }, - // is called if a subscribed object changes - objectChange: (id, obj) => {}, + // is called if a subscribed object changes + objectChange: (id, obj) => {}, - // is called if a subscribed state changes - stateChange: (id, state) => {}, - }), - ); + // is called if a subscribed state changes + stateChange: (id, state) => {}, + }), + ); } if (module.parent) { - // Export startAdapter in compact mode - module.exports = startAdapter; + // Export startAdapter in compact mode + module.exports = startAdapter; } else { - // otherwise start the instance directly - startAdapter(); + // otherwise start the instance directly + startAdapter(); } diff --git a/test/unit/loader/terminate/terminate_reason.js b/test/unit/loader/terminate/terminate_reason.js index e6ff2652..f4e24d7e 100644 --- a/test/unit/loader/terminate/terminate_reason.js +++ b/test/unit/loader/terminate/terminate_reason.js @@ -1,43 +1,43 @@ -"use strict"; +'use strict'; // The adapter-core module gives you access to the core ioBroker functions // you need to create an adapter -const utils = require("@iobroker/adapter-core"); +const utils = require('@iobroker/adapter-core'); /** * Starts the adapter instance * @param {Partial} [options] */ function startAdapter(options) { - // Create the adapter and define its methods - const adapter = utils.adapter( - Object.assign({}, options, { - name: "test-adapter", + // Create the adapter and define its methods + const adapter = utils.adapter( + Object.assign({}, options, { + name: 'test-adapter', - // The ready callback is called when databases are connected and adapter received configuration. - // start here! - ready: () => { - adapter.terminate("reason"); - }, + // The ready callback is called when databases are connected and adapter received configuration. + // start here! + ready: () => { + adapter.terminate('reason'); + }, - // is called when adapter shuts down - callback has to be called under any circumstances! - unload: (callback) => { - callback(); - }, + // is called when adapter shuts down - callback has to be called under any circumstances! + unload: callback => { + callback(); + }, - // is called if a subscribed object changes - objectChange: (id, obj) => {}, + // is called if a subscribed object changes + objectChange: (id, obj) => {}, - // is called if a subscribed state changes - stateChange: (id, state) => {}, - }), - ); + // is called if a subscribed state changes + stateChange: (id, state) => {}, + }), + ); } if (module.parent) { - // Export startAdapter in compact mode - module.exports = startAdapter; + // Export startAdapter in compact mode + module.exports = startAdapter; } else { - // otherwise start the instance directly - startAdapter(); + // otherwise start the instance directly + startAdapter(); } diff --git a/test/unit/loader/thisContext/main.js b/test/unit/loader/thisContext/main.js index 386f9831..8ce77748 100644 --- a/test/unit/loader/thisContext/main.js +++ b/test/unit/loader/thisContext/main.js @@ -1,40 +1,40 @@ // This file is used to test the unit test harness -"use strict"; +'use strict'; -const utils = require("@iobroker/adapter-core"); +const utils = require('@iobroker/adapter-core'); class TestAdapter extends utils.Adapter { - /** - * @param {Partial} [options={}] - */ - constructor(options) { - super({ - ...options, - name: "test-adapter", - }); - this.on("ready", this.onReady); - } + /** + * @param {Partial} [options={}] + */ + constructor(options) { + super({ + ...options, + name: 'test-adapter', + }); + this.on('ready', this.onReady); + } - /** - * Is called when databases are connected and adapter received configuration. - */ - async onReady() { - this.testMethod(); - } + /** + * Is called when databases are connected and adapter received configuration. + */ + async onReady() { + this.testMethod(); + } - testMethod() { - return true; - } + testMethod() { + return true; + } } if (module.parent) { - // Export the constructor in compact mode - /** - * @param {Partial} [options={}] - */ - module.exports = (options) => new TestAdapter(options); + // Export the constructor in compact mode + /** + * @param {Partial} [options={}] + */ + module.exports = options => new TestAdapter(options); } else { - // otherwise start the instance directly - new TestAdapter(); + // otherwise start the instance directly + new TestAdapter(); } diff --git a/tsconfig.build.json b/tsconfig.build.json index 8e36b596..3f84faf2 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,16 +1,12 @@ // Specialized tsconfig to only compile .ts-files in the src dir { - "extends": "./tsconfig.json", - "compilerOptions": { - "allowJs": false, - "checkJs": false, - "noEmit": false, - "declaration": true - }, - "include": [ - "src/**/*.ts" - ], - "exclude": [ - "src/**/*.test.ts" - ] + "extends": "./tsconfig.json", + "compilerOptions": { + "allowJs": false, + "checkJs": false, + "noEmit": false, + "declaration": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts"] } diff --git a/tsconfig.json b/tsconfig.json index 318c40ad..26362d48 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,33 +1,33 @@ // Root tsconfig to set the settings and power editor support for all TS files { - // To update the compilation target, install a different version of @tsconfig/node... and reference it here - // https://github.com/tsconfig/bases#node-14-tsconfigjson - "extends": "@tsconfig/node14/tsconfig.json", - "compilerOptions": { - // do not compile anything; this file is just to configure type checking - // the compilation is configured in tsconfig.build.json - "noEmit": true, + // To update the compilation target, install a different version of @tsconfig/node... and reference it here + // https://github.com/tsconfig/bases#node-14-tsconfigjson + "extends": "@tsconfig/node16/tsconfig.json", + "compilerOptions": { + // do not compile anything; this file is just to configure type checking + // the compilation is configured in tsconfig.build.json + "noEmit": true, - // Never emit faulty JS - "noEmitOnError": true, + // Never emit faulty JS + "noEmitOnError": true, - "outDir": "build/", - "removeComments": false, - "newLine": "lf", + "outDir": "build/", + "removeComments": false, + "newLine": "lf", - // Avoid runtime imports that are unnecessary - // "importsNotUsedAsValues": "error", + // Avoid runtime imports that are unnecessary + // "importsNotUsedAsValues": "error", - // Required for TS debugging, turn on if necessary - "sourceMap": false, - "inlineSourceMap": false, - "baseUrl": ".", - "paths": { - "ioBroker": ["@iobroker/types"], - "Mocha": ["@types/mocha"] - }, - "types": ["@iobroker/types", "node", "@types/mocha"] - }, - "include": ["**/*.ts"], - "exclude": ["build/**", "node_modules/**"] + // Required for TS debugging, turn on if necessary + "sourceMap": false, + "inlineSourceMap": false, + "baseUrl": ".", + "paths": { + "ioBroker": ["@iobroker/types"], + "Mocha": ["@types/mocha"] + }, + "types": ["@iobroker/types", "node", "@types/mocha"] + }, + "include": ["**/*.ts"], + "exclude": ["build/**", "node_modules/**"] }