From 94fead75ec6b9d675063d165334d795e607e2043 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczak Date: Fri, 20 Feb 2026 15:52:31 +0000 Subject: [PATCH 1/2] fix: guard parent access in `addTsEsmHook` for ESM-to-CJS sub-dependency resolution --- .gitignore | 1 + lib/cli.js | 2 +- .../node_modules/cjs-helper/lib/utils.js | 6 ++++++ .../node_modules/cjs-helper/package.json | 4 ++++ .../node_modules/esm-dep/index.js | 6 ++++++ .../node_modules/esm-dep/package.json | 11 +++++++++++ test/cli_typescript_esm_dep/package.json | 1 + test/cli_typescript_esm_dep/simple.ts | 15 +++++++++++++++ test/cli_typescript_esm_dep/tsconfig.json | 8 ++++++++ test/typescript.js | 10 ++++++++++ 10 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 test/cli_typescript_esm_dep/node_modules/cjs-helper/lib/utils.js create mode 100644 test/cli_typescript_esm_dep/node_modules/cjs-helper/package.json create mode 100644 test/cli_typescript_esm_dep/node_modules/esm-dep/index.js create mode 100644 test/cli_typescript_esm_dep/node_modules/esm-dep/package.json create mode 100644 test/cli_typescript_esm_dep/package.json create mode 100644 test/cli_typescript_esm_dep/simple.ts create mode 100644 test/cli_typescript_esm_dep/tsconfig.json diff --git a/.gitignore b/.gitignore index 46e60268..8f49133e 100755 --- a/.gitignore +++ b/.gitignore @@ -15,5 +15,6 @@ **/.idea !test/cli_coverage/node_modules +!test/cli_typescript_esm_dep/node_modules test_runner test/cli/test/leaks.js diff --git a/lib/cli.js b/lib/cli.js index 06638af9..369b4d80 100755 --- a/lib/cli.js +++ b/lib/cli.js @@ -89,7 +89,7 @@ internals.addTsEsmHook = function () { Module._resolveFilename = function (request, parent, ...rest) { - if (request.endsWith('.js') && (parent.filename.endsWith('.ts') || parent.filename.endsWith('.tsx'))) { + if (request.endsWith('.js') && parent && parent.filename && (parent.filename.endsWith('.ts') || parent.filename.endsWith('.tsx'))) { const target = Path.join(parent.path, request); const tsEquivalent = `${target.slice(0, -3)}.ts`; diff --git a/test/cli_typescript_esm_dep/node_modules/cjs-helper/lib/utils.js b/test/cli_typescript_esm_dep/node_modules/cjs-helper/lib/utils.js new file mode 100644 index 00000000..b0efe011 --- /dev/null +++ b/test/cli_typescript_esm_dep/node_modules/cjs-helper/lib/utils.js @@ -0,0 +1,6 @@ +'use strict'; + +exports.double = function (n) { + + return n * 2; +}; diff --git a/test/cli_typescript_esm_dep/node_modules/cjs-helper/package.json b/test/cli_typescript_esm_dep/node_modules/cjs-helper/package.json new file mode 100644 index 00000000..232a5d3a --- /dev/null +++ b/test/cli_typescript_esm_dep/node_modules/cjs-helper/package.json @@ -0,0 +1,4 @@ +{ + "name": "cjs-helper", + "version": "1.0.0" +} diff --git a/test/cli_typescript_esm_dep/node_modules/esm-dep/index.js b/test/cli_typescript_esm_dep/node_modules/esm-dep/index.js new file mode 100644 index 00000000..857e9283 --- /dev/null +++ b/test/cli_typescript_esm_dep/node_modules/esm-dep/index.js @@ -0,0 +1,6 @@ +import { double } from 'cjs-helper/lib/utils.js'; + +export function add(a, b) { + + return double(a) - a + b; +} diff --git a/test/cli_typescript_esm_dep/node_modules/esm-dep/package.json b/test/cli_typescript_esm_dep/node_modules/esm-dep/package.json new file mode 100644 index 00000000..7f6935f8 --- /dev/null +++ b/test/cli_typescript_esm_dep/node_modules/esm-dep/package.json @@ -0,0 +1,11 @@ +{ + "name": "esm-dep", + "version": "1.0.0", + "type": "module", + "exports": { + ".": "./index.js" + }, + "dependencies": { + "cjs-helper": "1.0.0" + } +} diff --git a/test/cli_typescript_esm_dep/package.json b/test/cli_typescript_esm_dep/package.json new file mode 100644 index 00000000..5ffd9800 --- /dev/null +++ b/test/cli_typescript_esm_dep/package.json @@ -0,0 +1 @@ +{ "type": "module" } diff --git a/test/cli_typescript_esm_dep/simple.ts b/test/cli_typescript_esm_dep/simple.ts new file mode 100644 index 00000000..18eb9925 --- /dev/null +++ b/test/cli_typescript_esm_dep/simple.ts @@ -0,0 +1,15 @@ +import { expect } from '@hapi/code'; +import * as _Lab from '../../test_runner/index.js'; + +import { add } from 'esm-dep'; + + +const { describe, it } = exports.lab = _Lab.script(); + +describe('Test CLI', () => { + + it('imports from an ESM dependency', () => { + + expect(add(1, 1)).to.equal(2); + }); +}); diff --git a/test/cli_typescript_esm_dep/tsconfig.json b/test/cli_typescript_esm_dep/tsconfig.json new file mode 100644 index 00000000..1dbf06b4 --- /dev/null +++ b/test/cli_typescript_esm_dep/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "es2021", + "module": "commonjs", + "moduleResolution": "node", + "removeComments": true + } +} \ No newline at end of file diff --git a/test/typescript.js b/test/typescript.js index ce1610c8..68dc6602 100755 --- a/test/typescript.js +++ b/test/typescript.js @@ -4,6 +4,7 @@ const Fs = require('fs'); const Path = require('path'); const Code = require('@hapi/code'); +const Somever = require('@hapi/somever'); const _Lab = require('../test_runner'); const RunCli = require('./run_cli'); const Ts = require('typescript'); @@ -44,6 +45,15 @@ describe('TypeScript', () => { expect(result.output.split('Test duration').length - 1).to.equal(1); }); + it('supports TypeScript with ESM dependencies', { skip: !Somever.match(process.version, '>=20') }, async () => { + + process.chdir(Path.join(__dirname, 'cli_typescript_esm_dep')); + const result = await RunCli(['simple.ts', '-m', '2000', '--typescript']); + expect(result.errorOutput).to.equal(''); + expect(result.code).to.equal(0); + expect(result.output).to.contain('1 tests complete'); + }); + it('handles errors', async () => { process.chdir(Path.join(__dirname, 'cli_typescript')); From 2673dc532797c01a6cf37cafb64faed0c98721fd Mon Sep 17 00:00:00 2001 From: Tomasz Sobczak Date: Wed, 11 Mar 2026 09:59:29 +0000 Subject: [PATCH 2/2] fix: remove guard for `parent.filename` in lib/cli.js --- lib/cli.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cli.js b/lib/cli.js index 369b4d80..94fb2be6 100755 --- a/lib/cli.js +++ b/lib/cli.js @@ -89,7 +89,7 @@ internals.addTsEsmHook = function () { Module._resolveFilename = function (request, parent, ...rest) { - if (request.endsWith('.js') && parent && parent.filename && (parent.filename.endsWith('.ts') || parent.filename.endsWith('.tsx'))) { + if (request.endsWith('.js') && parent && (parent.filename.endsWith('.ts') || parent.filename.endsWith('.tsx'))) { const target = Path.join(parent.path, request); const tsEquivalent = `${target.slice(0, -3)}.ts`;