diff --git a/index.cjs b/index.cjs index 59736d6..cfa2ff5 100644 --- a/index.cjs +++ b/index.cjs @@ -21,12 +21,22 @@ * * @module @cldmv/node-android-tv-remote/cjs */ +"use strict"; -const { createRequire } = require("module"); -const requireESM = createRequire(__filename); +// index.cjs is a thin wrapper: it loads index.mjs through Node's synchronous require(esm). +// Node.js versions without require(esm) would fail with a bare ERR_REQUIRE_ESM, so fail +// early with a message that says what to do instead. +if (!process.features?.require_module) { + const error = new Error( + `@cldmv/node-android-tv-remote: require() needs Node.js ^20.19.0 or >=22.12.0 (this is ${process.version}). On older Node.js, load the package with import() instead.` + ); + error.code = "ERR_REQUIRE_ESM"; + throw error; +} -const { default: createRemote } = requireESM("./index.mjs"); +const { default: createRemote, createAndroidTVRemote } = require("./index.mjs"); module.exports = createRemote; module.exports.createRemote = createRemote; +module.exports.createAndroidTVRemote = createAndroidTVRemote; module.exports.default = createRemote; diff --git a/package-lock.json b/package-lock.json index 6933b2a..7bfd033 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cldmv/node-android-tv-remote", - "version": "2.1.6", + "version": "2.1.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@cldmv/node-android-tv-remote", - "version": "2.1.6", + "version": "2.1.7", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 4a2e36f..061f8a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cldmv/node-android-tv-remote", - "version": "2.1.6", + "version": "2.1.7", "description": "A Node.js library for controlling Android TV devices via ADB keycodes and text input.", "type": "module", "exports": { @@ -14,9 +14,10 @@ "setup-device": "node scripts/setup-device.mjs", "build": "echo '✓ no build step'", "build:ci": "echo '✓ no build step'", - "test": "node tests/run-vitest.mjs", + "test": "node tests/run-vitest.mjs && npm run test:cjs", + "test:cjs": "node --test tests/cjs/entry.test.cjs", "test:watch": "vitest --config .configs/vitest.config.mjs", - "coverage": "node tests/run-vitest.mjs --coverage-quiet", + "coverage": "node tests/run-vitest.mjs --coverage-quiet && npm run test:cjs", "ci:coverage": "npm run coverage", "lint": "eslint --config eslint.config.mjs .", "lint:fix": "eslint --config eslint.config.mjs . --fix", diff --git a/tests/cjs/entry.test.cjs b/tests/cjs/entry.test.cjs new file mode 100644 index 0000000..35ac9e5 --- /dev/null +++ b/tests/cjs/entry.test.cjs @@ -0,0 +1,56 @@ +/** + * + * @Project: @cldmv/node-android-tv-remote + * @Filename: /tests/cjs/entry.test.cjs + * @Date: 2026-10-03T00:00:00-07:00 (1791010800) + * @Author: Nate Corcoran + * @Email: + * ----- + * @Last modified by: Nate Corcoran (Shinrai@users.noreply.github.com) + * @Last modified time: 2026-10-03T10:37:08-07:00 (1791049028) + * ----- + * @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. + * + */ + +/** + * CommonJS entry tests. These run under Node's own test runner (`node --test`), not Vitest: + * Vitest loads files through its own module runner, so it cannot show whether a plain + * `require()` of the package works the way it does for a CommonJS consumer. + */ +"use strict"; + +const { test } = require("node:test"); +const assert = require("node:assert/strict"); +const { spawnSync } = require("node:child_process"); +const path = require("node:path"); + +const repoRoot = path.resolve(__dirname, "../.."); + +test("require() returns the same createRemote object as import", async () => { + const createRemote = require("../../index.cjs"); + const esm = await import("../../index.mjs"); + + // createRemote opens an ADB connection when called, so only identity/type is + // checked here - no sockets are opened in this test. + assert.equal(typeof createRemote, "function"); + assert.equal(createRemote, esm.default); + assert.equal(createRemote.createRemote, esm.default); + assert.equal(createRemote.default, esm.default); + assert.equal(typeof createRemote.createAndroidTVRemote, "function"); + assert.equal(createRemote.createAndroidTVRemote, esm.createAndroidTVRemote); +}); + +test("require() fails with a clear message where Node.js has no require(esm)", () => { + // --no-experimental-require-module turns require(esm) off, which is what Node.js + // versions before 20.19 / 22.12 look like to the entry. + const res = spawnSync(process.execPath, ["--no-experimental-require-module", "-e", "require('./index.cjs')"], { + cwd: repoRoot, + encoding: "utf8" + }); + + assert.notEqual(res.status, 0); + assert.match(res.stderr, /ERR_REQUIRE_ESM/); + assert.match(res.stderr, /require\(\) needs Node\.js \^20\.19\.0 or >=22\.12\.0/); + assert.match(res.stderr, /import\(\)/); +});