mocha: Bin exported without extension, breaks node's native loader picker and ts-node/esm

Description

custom loaders like ts-node have a really hard time figuring out the mocha “binary”

The binary lacks.js or cjs extensions. Changing the node_modules/.bin/mocha extension isn’t enough.

Steps to Reproduce

  1. Sandbox: https://codesandbox.io/s/mocha-ts-node-16-ctt5k?file=/package.json
  2. Open Terminal
  3. Run npm run broken-test-npx or run source env.sh; mocha test/test.js

OR

  1. npm install ts-node typescript @types/mocha mocha on a type: "modules" project.json
  2. Set export NODE_OPTIONS="--loader ts-node/esm/transpile-only"
  3. Run npx mocha test/any-test-at-all.js

Expected behavior:

No errors, tests run.

Actual behavior:

Setting a loader prevents node from running the mocha binary, in any way

$ npm run test # Copies mocha to mocha.js as workaround
3 passing (3ms)
$ npm run test-cjs # Copies mocha to mocha.cjs as workaround
1 passing (2ms)
$ npm run broken-test-npx # Node cant tell what loader to use and errors out
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension "" for /sandbox/node_modules/mocha/bin/mocha
$ npm run broken-test-direct # same behavior as before
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension "" for /sandbox/node_modules/mocha/bin/mocha

Reproduces how often: 100% of the time

Versions

  • The output of mocha: 8.4.0
  • The output of node --version: 14 (Sandbox) and 16 as well
  • Your operating system
    • name and version: yes
    • architecture (32 or 64-bit): 64
  • Your shell (e.g., bash, zsh, PowerShell, cmd):
  • Your browser and version (if running browser tests):
  • Any third-party Mocha-related modules (and their versions):
  • Any code transpiler being used (and its version): ts-node loader

Additional Information

Transpile only mode does not make a difference. Extension of tests do not make a difference. As long as the loader boots up, when node proceeds to guess which loader to use, node errors out.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 1
  • Comments: 32 (20 by maintainers)

Commits related to this issue

Most upvoted comments

https://github.com/mochajs/mocha/issues/4645#issuecomment-854989395

^^ This is a fully backwards-compatible fix and takes little effort, right? I feel like there’s some confusion with talk of this being a breaking change, but it’s not. Is there anything we can explain further?

@juergba why are you against this? (or at least two bin entry points: one without an extension and one with it)

This is a confirmed bug in nodejs. Please remind them that they need to fix it! I have cited mocha as one of the libraries being broken by it, but it will help if mocha’s users and maintainers also remind them (politely) that they need to fix this! https://github.com/nodejs/node/issues/33226

To reproduce the bug, use a completely empty --loader. This will prove that node is responsible for the error, not ts-node nor any other loader.

$ node -v
v16.2.0

$ touch empty-loader.mjs
$ touch extensionless-entrypoint
$ node --loader empty-loader.mjs ./extensionless-entrypoint
(node:10077) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
node:internal/process/esm_loader:74
    internalBinding('errors').triggerUncaughtException(
                              ^

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension "" for /c/Users/cspot/extensionless-entrypoint
    at new NodeError (node:internal/errors:363:5)
    at Loader.defaultGetFormat [as _getFormat] (node:internal/modules/esm/get_format:71:15)
    at Loader.getFormat (node:internal/modules/esm/loader:105:42)
    at Loader.getModuleJob (node:internal/modules/esm/loader:243:31)
    at async Loader.import (node:internal/modules/esm/loader:177:17)
    at async Object.loadESM (node:internal/process/esm_loader:68:5) {
  code: 'ERR_UNKNOWN_FILE_EXTENSION'
}

EDIT Fixed a typo above; the node --loader invocation was missing the ./extensionless-entrypoint

ts-node 10.6.0 implements a workaround that hopefully mimics the bugfix which will eventually be published in node core. This means that, for users of mocha and ts-node, upgrading to the latest ts-node should avoid this issue.

https://github.com/TypeStrong/ts-node/releases/tag/v10.6.0

Yeah some tools have hardcoded paths to the extensionless JS file, so just as easy to keep it there and add an extension-ed file that requires it. (Or vice versa, either works)

On Thu, Jan 27, 2022, 1:27 PM Juerg B. @.***> wrote:

You don’t have to do this: npm and yarn will take care of it automatically.

This is not true. Take a look at e.g. nanoid after installation with npm, there is a bin/nanoid.cjs but no bin/nanoid.

— Reply to this email directly, view it on GitHub https://github.com/mochajs/mocha/issues/4645#issuecomment-1023519831, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAC35OCZCFLDEB42M26PVULUYGFANANCNFSM46AJASBQ . You are receiving this because you were mentioned.Message ID: @.***>

You don’t have to do this: npm and yarn will take care of it automatically.

This is not true. Take a look at e.g. nanoid after installation with npm, there is a bin/nanoid.cjs but no bin/nanoid.

I spoke with npm and yarn authors, and both package managers automatically alias a non-naked bin with a naked version (but not the other way around).

If you mean: bin/mocha.js results in : .bin/mocha then I agree. Otherwise …?

I can live with adding an additional bin/mocha.js requiring the naked one. The second binary bin/_mocha is on sneaky deprecation, we should leave it as is.

@JakobJingleheimer please go ahead with your PR, if you are still on fire. Otherwise we wait till Mocha@10.

The first line #!/usr/bin/env node means, this file is an executable, right? In Linux only?

Technically the execute bit tells Linux it’s an executable, and the two-byte sequence #! is a magic number understood by the Linux kernel, used to differentiate different binary formats. That’s my understanding at least.

Package managers use https://www.npmjs.com/package/@zkochan/cmd-shim to generate .ps1 and .cmd shims that achieve the same effect on Windows.

Yes, I’m 99% sure the historic reason is days of yore (unix/linux).

Note that adding the .js file extension would NOT be a breaking change as npm and yarn will automatically handle the extension-less part for you.

Also, I’m completely confident that making the two tiny changes I suggested will 100% be compatible now and in future, wherever loaders lands 😉

@lachrist cough I am on the node team 🙂

There is a more recent discussion, but I’m not linking to it at the moment because I need to port it from a PR (that I need to closed) to an actual discussion.

@lachrist There is no loader option in Mocha.

Just use 'experimental-loader' : './lib/loader.mjs'. Or with the alias name: loader: './lib/loader.mjs'

Edit: Mocha recognizes Node’s option and forwards them to Node by spawning a child-process.

I think if mocha wants to implement a simple fix, they can add a new bin entrypoint file with a file extension (make sure to keep the old one, too, for backwards-compatibility) and then point package.json "bin" field to the new entrypoint with the file extension.

I believe that update can be published as a non-breaking change.