From 52cdfb3a4e11126d0f5cfb6daf4c4cec785c681b Mon Sep 17 00:00:00 2001 From: hombach Date: Sat, 31 Oct 2026 11:58:26 +0100 Subject: [PATCH 1/3] fix: do not pull esbuild 0.11 into every adapter via @alcalzone/esbuild-register @alcalzone/esbuild-register is only used inside the integration test directory, where controllerSetup already installs it. Remove it from the dependencies of @iobroker/testing, so adapters no longer get esbuild@0.11.23 (GHSA-67mh-4wv8-2f99), which Dependabot cannot update. In the generated package.json of the test directory, force esbuild@^0.25.0 for @alcalzone/esbuild-register via a scoped npm override. --- CHANGELOG.md | 3 + .../integration/lib/controllerSetup.d.ts | 6 ++ .../tests/integration/lib/controllerSetup.js | 56 ++++++++++------ package-lock.json | 27 -------- package.json | 1 - .../integration/lib/controllerSetup.test.ts | 43 ++++++++++++ src/tests/integration/lib/controllerSetup.ts | 65 ++++++++++++------- 7 files changed, 129 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af73d1fe..8b222415 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ PLACEHOLDER for the next version: ## **WORK IN PROGRESS** --> +## **WORK IN PROGRESS** +* (@hombach) Fixed: `@iobroker/testing` no longer pulls `esbuild@0.11` (GHSA-67mh-4wv8-2f99) into every adapter. `@alcalzone/esbuild-register` is removed from the dependencies, as it is only needed inside the integration test directory, where it is already installed. There, an npm `overrides` entry now forces `esbuild@^0.25.0` for `@alcalzone/esbuild-register` + ## 6.3.1 (2026-10-13) * (Garfonso) Fixed: with npm >= 12 the test installation stayed empty, because npm blocks the install scripts of js-controller (`setup first`) and esbuild. Both are now listed in `allowScripts` of the generated `package.json` diff --git a/build/tests/integration/lib/controllerSetup.d.ts b/build/tests/integration/lib/controllerSetup.d.ts index 701c9c0c..c36df487 100644 --- a/build/tests/integration/lib/controllerSetup.d.ts +++ b/build/tests/integration/lib/controllerSetup.d.ts @@ -32,6 +32,12 @@ export declare class ControllerSetup { * This is necessary when switching JS-Controller versions, so no stale files and states are left behind. */ private clearTestDir; + /** + * Creates the contents of the package.json in the test directory + * + * @param controllerVersion The version (or dist-tag) of JS-Controller to install + */ + createTestDirPackageJson(controllerVersion: string): Record; prepareTestDir(controllerVersion?: string): Promise; /** * Tests if JS-Controller is already installed diff --git a/build/tests/integration/lib/controllerSetup.js b/build/tests/integration/lib/controllerSetup.js index 80cf8a37..343dbd8a 100644 --- a/build/tests/integration/lib/controllerSetup.js +++ b/build/tests/integration/lib/controllerSetup.js @@ -117,26 +117,13 @@ class ControllerSetup { await this.clearLogDir(); debug(' => done!'); } - async prepareTestDir(controllerVersion) { - const nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10); - // js-controller 7.2.3 dropped support for Node.js 18 and 20. If no specific - // version was requested, and we are running on such a Node.js version, pin - // js-controller to the last version that still supports it. - if (!controllerVersion) { - controllerVersion = nodeMajorVersion <= 20 ? '7.2.2' : 'dev'; - } - debug(`Preparing the test directory. JS-Controller version: "${controllerVersion}"...`); - // Make sure the test dir exists - await (0, fs_extra_1.ensureDir)(this.testDir); - // If the test directory was previously used with a different JS-Controller version, - // remove the installed files and the data directory, so no stale state is left behind - const installedControllerVersion = await this.getInstalledControllerVersion(); - if (installedControllerVersion && installedControllerVersion !== controllerVersion) { - debug(`JS-Controller version changed from "${installedControllerVersion}" to "${controllerVersion}", cleaning up...`); - await this.clearTestDir(); - } - // Write the package.json - const packageJson = { + /** + * Creates the contents of the package.json in the test directory + * + * @param controllerVersion The version (or dist-tag) of JS-Controller to install + */ + createTestDirPackageJson(controllerVersion) { + return { name: path.basename(this.testDir), version: '1.0.0', main: 'index.js', @@ -151,6 +138,14 @@ class ControllerSetup { // @alcalzone/esbuild-register is needed to run TypeScript adapters '@alcalzone/esbuild-register': '^2.5.1-1', }, + // @alcalzone/esbuild-register@2.5.1-1 (the latest version) depends on esbuild@^0.11.5, + // which is affected by GHSA-67mh-4wv8-2f99. Force a fixed esbuild version, but only + // for esbuild-register, so an esbuild dependency of the adapter itself is not touched + overrides: { + '@alcalzone/esbuild-register': { + esbuild: '^0.25.0', + }, + }, // npm >= 12 blocks dependency install scripts by default: // - js-controller needs its install script ("setup first") to create the database // - esbuild (used by @alcalzone/esbuild-register) downloads its binary @@ -160,6 +155,27 @@ class ControllerSetup { }, description: '', }; + } + async prepareTestDir(controllerVersion) { + const nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10); + // js-controller 7.2.3 dropped support for Node.js 18 and 20. If no specific + // version was requested, and we are running on such a Node.js version, pin + // js-controller to the last version that still supports it. + if (!controllerVersion) { + controllerVersion = nodeMajorVersion <= 20 ? '7.2.2' : 'dev'; + } + debug(`Preparing the test directory. JS-Controller version: "${controllerVersion}"...`); + // Make sure the test dir exists + await (0, fs_extra_1.ensureDir)(this.testDir); + // If the test directory was previously used with a different JS-Controller version, + // remove the installed files and the data directory, so no stale state is left behind + const installedControllerVersion = await this.getInstalledControllerVersion(); + if (installedControllerVersion && installedControllerVersion !== controllerVersion) { + debug(`JS-Controller version changed from "${installedControllerVersion}" to "${controllerVersion}", cleaning up...`); + await this.clearTestDir(); + } + // Write the package.json + const packageJson = this.createTestDirPackageJson(controllerVersion); await (0, fs_extra_1.writeJSON)(path.join(this.testDir, 'package.json'), packageJson, { spaces: 2, }); diff --git a/package-lock.json b/package-lock.json index 41b07d26..b7ed40a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "6.3.1", "license": "MIT", "dependencies": { - "@alcalzone/esbuild-register": "^2.5.1-1", "@types/chai": "^4.3.20", "@types/chai-as-promised": "^7.1.8", "@types/mocha": "^10.0.10", @@ -46,16 +45,6 @@ "node": ">=22.19.0" } }, - "node_modules/@alcalzone/esbuild-register": { - "version": "2.5.1-1", - "resolved": "https://registry.npmjs.org/@alcalzone/esbuild-register/-/esbuild-register-2.5.1-1.tgz", - "integrity": "sha512-u19/K/ZaiWQhYIQZeF2FImUeJDiMD4i2h7qPdU0HVFCxoQynye59N4oy6EvjxeXaxLnGkppcb5KKeiYD4cT1iQ==", - "license": "MIT", - "dependencies": { - "esbuild": "^0.11.5", - "jsonc-parser": "^3.0.0" - } - }, "node_modules/@alcalzone/pak": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@alcalzone/pak/-/pak-0.12.0.tgz", @@ -2822,16 +2811,6 @@ "license": "MIT", "optional": true }, - "node_modules/esbuild": { - "version": "0.11.23", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.11.23.tgz", - "integrity": "sha512-iaiZZ9vUF5wJV8ob1tl+5aJTrwDczlvGP0JoMmnpC2B0ppiMCu8n8gmy5ZTGl5bcG081XBVn+U+jP+mPFm5T5Q==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -4737,12 +4716,6 @@ "node": ">=6" } }, - "node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "license": "MIT" - }, "node_modules/jsonfile": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", diff --git a/package.json b/package.json index 3500ad67..b0df0996 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "typescript": "~6.0.3" }, "dependencies": { - "@alcalzone/esbuild-register": "^2.5.1-1", "@types/chai": "^4.3.20", "@types/chai-as-promised": "^7.1.8", "@types/mocha": "^10.0.10", diff --git a/src/tests/integration/lib/controllerSetup.test.ts b/src/tests/integration/lib/controllerSetup.test.ts index 1ed16320..afa57974 100644 --- a/src/tests/integration/lib/controllerSetup.test.ts +++ b/src/tests/integration/lib/controllerSetup.test.ts @@ -136,3 +136,46 @@ describe('ControllerSetup.disableDiagnosticReporting()', () => { expect(written).to.have.length(0); }); }); + +describe('ControllerSetup.createTestDirPackageJson()', () => { + let tmp: string; + let adapterDir: string; + let testDir: string; + + beforeEach(() => { + tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'testing-pkg-')); + adapterDir = path.join(tmp, 'ioBroker.demo'); + testDir = path.join(tmp, 'test'); + fs.outputJSONSync(path.join(adapterDir, 'package.json'), { name: 'iobroker.demo', version: '0.0.1' }); + fs.outputJSONSync(path.join(adapterDir, 'io-package.json'), { common: { name: 'demo' } }); + }); + + afterEach(() => { + fs.removeSync(tmp); + }); + + it('installs the requested JS-Controller version and esbuild-register', () => { + const packageJson = new ControllerSetup(adapterDir, testDir).createTestDirPackageJson('7.2.2'); + expect(packageJson.dependencies).to.deep.equal({ + 'iobroker.js-controller': '7.2.2', + '@alcalzone/esbuild-register': '^2.5.1-1', + }); + }); + + it('forces a fixed esbuild version (GHSA-67mh-4wv8-2f99) for esbuild-register only', () => { + const packageJson = new ControllerSetup(adapterDir, testDir).createTestDirPackageJson('dev'); + expect(packageJson.overrides).to.deep.equal({ + '@alcalzone/esbuild-register': { + esbuild: '^0.25.0', + }, + }); + }); + + it('allows the install scripts of js-controller and esbuild', () => { + const packageJson = new ControllerSetup(adapterDir, testDir).createTestDirPackageJson('dev'); + expect(packageJson.allowScripts).to.deep.equal({ + 'iobroker.js-controller': true, + esbuild: true, + }); + }); +}); diff --git a/src/tests/integration/lib/controllerSetup.ts b/src/tests/integration/lib/controllerSetup.ts index 59f22ff5..ad2d4cf6 100644 --- a/src/tests/integration/lib/controllerSetup.ts +++ b/src/tests/integration/lib/controllerSetup.ts @@ -92,6 +92,46 @@ export class ControllerSetup { debug(' => done!'); } + /** + * Creates the contents of the package.json in the test directory + * + * @param controllerVersion The version (or dist-tag) of JS-Controller to install + */ + public createTestDirPackageJson(controllerVersion: string): Record { + return { + 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, + // @alcalzone/esbuild-register is needed to run TypeScript adapters + '@alcalzone/esbuild-register': '^2.5.1-1', + }, + // @alcalzone/esbuild-register@2.5.1-1 (the latest version) depends on esbuild@^0.11.5, + // which is affected by GHSA-67mh-4wv8-2f99. Force a fixed esbuild version, but only + // for esbuild-register, so an esbuild dependency of the adapter itself is not touched + overrides: { + '@alcalzone/esbuild-register': { + esbuild: '^0.25.0', + }, + }, + // npm >= 12 blocks dependency install scripts by default: + // - js-controller needs its install script ("setup first") to create the database + // - esbuild (used by @alcalzone/esbuild-register) downloads its binary + allowScripts: { + [`${this.appName}.js-controller`]: true, + esbuild: true, + }, + description: '', + }; + } + public async prepareTestDir(controllerVersion?: string): Promise { const nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10); @@ -117,30 +157,7 @@ export class ControllerSetup { } // 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, - // @alcalzone/esbuild-register is needed to run TypeScript adapters - '@alcalzone/esbuild-register': '^2.5.1-1', - }, - // npm >= 12 blocks dependency install scripts by default: - // - js-controller needs its install script ("setup first") to create the database - // - esbuild (used by @alcalzone/esbuild-register) downloads its binary - allowScripts: { - [`${this.appName}.js-controller`]: true, - esbuild: true, - }, - description: '', - }; + const packageJson = this.createTestDirPackageJson(controllerVersion); await writeJSON(path.join(this.testDir, 'package.json'), packageJson, { spaces: 2, }); From e66a457352b48fdab08a6e11611b5f133b9435e2 Mon Sep 17 00:00:00 2001 From: hombach Date: Mon, 2 Nov 2026 19:19:16 +0100 Subject: [PATCH 2/3] feat: start TypeScript adapters with tsx instead of @alcalzone/esbuild-register As suggested in the review of #751: tsx is the current state of the art, also runs TypeScript files inside node_modules and depends on a current esbuild, so no npm override is needed in the test directory anymore. --- CHANGELOG.md | 2 +- CLAUDE.md | 2 +- build/tests/integration/lib/controllerSetup.js | 14 +++----------- build/tests/integration/lib/harness.js | 4 ++-- src/tests/integration/lib/controllerSetup.test.ts | 12 ++++-------- src/tests/integration/lib/controllerSetup.ts | 14 +++----------- src/tests/integration/lib/harness.ts | 4 ++-- 7 files changed, 16 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b222415..a998ca67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ## **WORK IN PROGRESS** --> ## **WORK IN PROGRESS** -* (@hombach) Fixed: `@iobroker/testing` no longer pulls `esbuild@0.11` (GHSA-67mh-4wv8-2f99) into every adapter. `@alcalzone/esbuild-register` is removed from the dependencies, as it is only needed inside the integration test directory, where it is already installed. There, an npm `overrides` entry now forces `esbuild@^0.25.0` for `@alcalzone/esbuild-register` +* (@hombach) Fixed: `@iobroker/testing` no longer pulls `esbuild@0.11` (GHSA-67mh-4wv8-2f99) into every adapter. `@alcalzone/esbuild-register` is removed from the dependencies. TypeScript adapters are now started with [`tsx`](https://tsx.is) (`node --import tsx`) instead of `@alcalzone/esbuild-register`. `tsx` is installed in the integration test directory only and depends on a current `esbuild` ## 6.3.1 (2026-10-13) * (Garfonso) Fixed: with npm >= 12 the test installation stayed empty, because npm blocks the install scripts of js-controller (`setup first`) and esbuild. Both are now listed in `allowScripts` of the generated `package.json` diff --git a/CLAUDE.md b/CLAUDE.md index 0eb2ebca..3d6c6887 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,7 +47,7 @@ This is the core of the library. `testAdapter()` in `integration/index.ts` build 3. **`shutdownTests` (after)** — stops the controller and adapter. The collaborating classes in `integration/lib/`: -- **`TestHarness`** (`harness.ts`) — an `EventEmitter` that is the object user tests interact with. Spawns the adapter in a child process (`node`, or `node -r @alcalzone/esbuild-register` for `.ts` main files), monitors its `alive`/exit state, and exposes `startAdapter`, `startAdapterAndWait`, `stopAdapter`, `changeAdapterConfig`, `sendTo` (message-box emulation via `enableSendTo`), and direct `.objects` / `.states` DB access. +- **`TestHarness`** (`harness.ts`) — an `EventEmitter` that is the object user tests interact with. Spawns the adapter in a child process (`node`, or `node --import tsx` for `.ts` main files), monitors its `alive`/exit state, and exposes `startAdapter`, `startAdapterAndWait`, `stopAdapter`, `changeAdapterConfig`, `sendTo` (message-box emulation via `enableSendTo`), and direct `.objects` / `.states` DB access. - **`DBConnection`** (`dbConnection.ts`) — wraps the objects and states DB servers + clients. Supports both `file` and `jsonl` DB types, and handles backup/restore used for test isolation. - **`ControllerSetup`** / **`AdapterSetup`** — install and configure JS-Controller and the adapter into the temp test dir; `executeCommand` in `src/lib/` runs the npm/install subprocesses. diff --git a/build/tests/integration/lib/controllerSetup.js b/build/tests/integration/lib/controllerSetup.js index 343dbd8a..20c0debd 100644 --- a/build/tests/integration/lib/controllerSetup.js +++ b/build/tests/integration/lib/controllerSetup.js @@ -135,20 +135,12 @@ class ControllerSetup { license: 'ISC', dependencies: { [`${this.appName}.js-controller`]: controllerVersion, - // @alcalzone/esbuild-register is needed to run TypeScript adapters - '@alcalzone/esbuild-register': '^2.5.1-1', - }, - // @alcalzone/esbuild-register@2.5.1-1 (the latest version) depends on esbuild@^0.11.5, - // which is affected by GHSA-67mh-4wv8-2f99. Force a fixed esbuild version, but only - // for esbuild-register, so an esbuild dependency of the adapter itself is not touched - overrides: { - '@alcalzone/esbuild-register': { - esbuild: '^0.25.0', - }, + // tsx is needed to run TypeScript adapters + tsx: '^4.20.0', }, // npm >= 12 blocks dependency install scripts by default: // - js-controller needs its install script ("setup first") to create the database - // - esbuild (used by @alcalzone/esbuild-register) downloads its binary + // - esbuild (used by tsx) downloads its binary allowScripts: { [`${this.appName}.js-controller`]: true, esbuild: true, diff --git a/build/tests/integration/lib/harness.js b/build/tests/integration/lib/harness.js index b64c1aac..1e77a8d5 100644 --- a/build/tests/integration/lib/harness.js +++ b/build/tests/integration/lib/harness.js @@ -221,11 +221,11 @@ class TestHarness extends node_events_1.EventEmitter { this._adapterExit = code != undefined ? code : signal; this.emit('failed', this._adapterExit); }; - // Determine if we need to use esbuild-register for TypeScript files + // TypeScript adapters are started with tsx const isTypeScript = mainFileAbsolute.endsWith('.ts'); const command = isWindows ? 'node.exe' : 'node'; const args = isTypeScript - ? ['-r', '@alcalzone/esbuild-register', mainFileRelative, '--console'] + ? ['--import', 'tsx', mainFileRelative, '--console'] : [mainFileRelative, '--console']; this._adapterProcess = (0, node_child_process_1.spawn)(command, args, { cwd: this.testAdapterDir, diff --git a/src/tests/integration/lib/controllerSetup.test.ts b/src/tests/integration/lib/controllerSetup.test.ts index afa57974..69d891ec 100644 --- a/src/tests/integration/lib/controllerSetup.test.ts +++ b/src/tests/integration/lib/controllerSetup.test.ts @@ -154,21 +154,17 @@ describe('ControllerSetup.createTestDirPackageJson()', () => { fs.removeSync(tmp); }); - it('installs the requested JS-Controller version and esbuild-register', () => { + it('installs the requested JS-Controller version and tsx', () => { const packageJson = new ControllerSetup(adapterDir, testDir).createTestDirPackageJson('7.2.2'); expect(packageJson.dependencies).to.deep.equal({ 'iobroker.js-controller': '7.2.2', - '@alcalzone/esbuild-register': '^2.5.1-1', + tsx: '^4.20.0', }); }); - it('forces a fixed esbuild version (GHSA-67mh-4wv8-2f99) for esbuild-register only', () => { + it('does not need npm overrides', () => { const packageJson = new ControllerSetup(adapterDir, testDir).createTestDirPackageJson('dev'); - expect(packageJson.overrides).to.deep.equal({ - '@alcalzone/esbuild-register': { - esbuild: '^0.25.0', - }, - }); + expect(packageJson).not.to.have.property('overrides'); }); it('allows the install scripts of js-controller and esbuild', () => { diff --git a/src/tests/integration/lib/controllerSetup.ts b/src/tests/integration/lib/controllerSetup.ts index ad2d4cf6..c72ac245 100644 --- a/src/tests/integration/lib/controllerSetup.ts +++ b/src/tests/integration/lib/controllerSetup.ts @@ -110,20 +110,12 @@ export class ControllerSetup { license: 'ISC', dependencies: { [`${this.appName}.js-controller`]: controllerVersion, - // @alcalzone/esbuild-register is needed to run TypeScript adapters - '@alcalzone/esbuild-register': '^2.5.1-1', - }, - // @alcalzone/esbuild-register@2.5.1-1 (the latest version) depends on esbuild@^0.11.5, - // which is affected by GHSA-67mh-4wv8-2f99. Force a fixed esbuild version, but only - // for esbuild-register, so an esbuild dependency of the adapter itself is not touched - overrides: { - '@alcalzone/esbuild-register': { - esbuild: '^0.25.0', - }, + // tsx is needed to run TypeScript adapters + tsx: '^4.20.0', }, // npm >= 12 blocks dependency install scripts by default: // - js-controller needs its install script ("setup first") to create the database - // - esbuild (used by @alcalzone/esbuild-register) downloads its binary + // - esbuild (used by tsx) downloads its binary allowScripts: { [`${this.appName}.js-controller`]: true, esbuild: true, diff --git a/src/tests/integration/lib/harness.ts b/src/tests/integration/lib/harness.ts index e81565d9..bc59a145 100644 --- a/src/tests/integration/lib/harness.ts +++ b/src/tests/integration/lib/harness.ts @@ -225,11 +225,11 @@ export class TestHarness extends EventEmitter { this.emit('failed', this._adapterExit); }; - // Determine if we need to use esbuild-register for TypeScript files + // TypeScript adapters are started with tsx const isTypeScript = mainFileAbsolute.endsWith('.ts'); const command = isWindows ? 'node.exe' : 'node'; const args = isTypeScript - ? ['-r', '@alcalzone/esbuild-register', mainFileRelative, '--console'] + ? ['--import', 'tsx', mainFileRelative, '--console'] : [mainFileRelative, '--console']; this._adapterProcess = spawn(command, args, { From d92741278330bf9cc6d1401606d6a4abee5c5013 Mon Sep 17 00:00:00 2001 From: hombach Date: Sun, 8 Nov 2026 13:51:22 +0100 Subject: [PATCH 3/3] test: cover TypeScript and JavaScript adapter startup arguments --- src/tests/integration/lib/harness.test.ts | 53 +++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/tests/integration/lib/harness.test.ts b/src/tests/integration/lib/harness.test.ts index 9ac72cf0..b0b1cab3 100644 --- a/src/tests/integration/lib/harness.test.ts +++ b/src/tests/integration/lib/harness.test.ts @@ -1,6 +1,7 @@ import { expect, use } from 'chai'; import chaiAsPromised from 'chai-as-promised'; import * as fs from 'fs-extra'; +import childProcess from 'node:child_process'; import { EventEmitter } from 'node:events'; import * as os from 'node:os'; import * as path from 'node:path'; @@ -61,6 +62,58 @@ describe('TestHarness', () => { await fs.remove(testDir); }); + describe('startAdapter()', () => { + let spawn: sinon.SinonStub; + let installedAdapterDir: string; + + beforeEach(async () => { + const adapterDir = path.join(testDir, 'source'); + const runtimeDir = path.join(testDir, 'runtime'); + installedAdapterDir = path.join(runtimeDir, 'node_modules', 'iobroker.test-adapter'); + await fs.copy(path.join(testDir, 'package.json'), path.join(adapterDir, 'package.json')); + await fs.copy(path.join(testDir, 'io-package.json'), path.join(adapterDir, 'io-package.json')); + await fs.copy(adapterDir, installedAdapterDir); + harness = new TestHarness(adapterDir, runtimeDir, dbConnection as unknown as DBConnection); + + // No real process or controller is needed to verify the startup contract. + const process = Object.assign(new EventEmitter(), { + stdout: new EventEmitter(), + stderr: new EventEmitter(), + }); + spawn = sinon.stub(childProcess, 'spawn').returns(process as unknown as childProcess.ChildProcess); + }); + + afterEach(() => { + spawn?.restore(); + }); + + it('starts an installed TypeScript entry with tsx and the installed adapter working directory', async () => { + const main = path.join('src', 'main.ts'); + await fs.writeJson(path.join(installedAdapterDir, 'io-package.json'), { + common: { name: 'test-adapter', main }, + }); + await fs.outputFile(path.join(installedAdapterDir, main), 'export {};'); + + await harness.startAdapter(); + + sinon.assert.calledOnce(spawn); + expect(spawn.firstCall.args[0]).to.equal(process.platform === 'win32' ? 'node.exe' : 'node'); + expect(spawn.firstCall.args[1]).to.deep.equal(['--import', 'tsx', main, '--console']); + expect(spawn.firstCall.args[2].cwd).to.equal(installedAdapterDir); + }); + + it('keeps JavaScript startup arguments unchanged without a TypeScript loader', async () => { + await fs.outputFile(path.join(installedAdapterDir, 'main.js'), 'module.exports = {};'); + + await harness.startAdapter(); + + sinon.assert.calledOnce(spawn); + expect(spawn.firstCall.args[0]).to.equal(process.platform === 'win32' ? 'node.exe' : 'node'); + expect(spawn.firstCall.args[1]).to.deep.equal(['main.js', '--console']); + expect(spawn.firstCall.args[2].cwd).to.equal(installedAdapterDir); + }); + }); + describe('encryptValue() / decryptValue()', () => { it('encrypts a value with the secret from system.config', async () => { expect(await harness.encryptValue('s3cret!')).to.equal(encrypt(secret, 's3cret!'));