privatenumber / tsx

⚡️ TypeScript Execute | The easiest way to run TypeScript in Node.js
https://tsx.is
MIT License
8.64k stars 132 forks source link

Printing error from esbuild can fail #492

Open Joris-van-der-Wel opened 3 months ago

Joris-van-der-Wel commented 3 months ago

Acknowledgements

Minimal reproduction URL

na

Version

4.7.1

Node.js version

20.11.1

Package manager

npm

Operating system

macOS

Problem & Expected behavior

I have my project set up with tsx (macOS), which all works fine. However I wanted to test something in linux, so I created a VM with a bind mount to my project. This caused the node_modules folder that was installed on macOS, to also be used on linux. This is not supported by esbuild: https://github.com/evanw/esbuild/blob/c809af050a74f022d9cf61c66e13365434542420/lib/npm/node-platform.ts#L195

Which is my mistake. However this took me a bit to figure out, because tsx is accidentally hiding this error:

node --import tsx src/bin.mts --help

node:internal/process/esm_loader:34
      internalBinding('errors').triggerUncaughtException(
                                ^
TypeError [Error]: undefined is not iterable (cannot read property Symbol(Symbol.iterator))
    at bt (file:///foo/node_modules/tsx/dist/index-29948669.mjs:16:1598)
    at file:///foo/node_modules/tsx/dist/index-29948669.mjs:17:632
    at hr (file:///foo/node_modules/tsx/dist/index-29948669.mjs:16:1071)
    at Cr (file:///foo/node_modules/tsx/dist/index-29948669.mjs:17:560)
    at Z (file:///foo/node_modules/tsx/dist/esm/index.mjs:5:1755)
    at async nextLoad (node:internal/modules/esm/hooks:865:22)
    at async Hooks.load (node:internal/modules/esm/hooks:448:20)
    at async MessagePort.handleMessage (node:internal/modules/esm/worker:196:18)

Node.js v20.11.1

I think this is the code in question: https://github.com/privatenumber/tsx/blob/b39e9b143a9d080286c607f96aaa9f4b8ce8702a/src/utils/transform/index.ts#L28

Manually adding a console.log(error) to the minified code in node_modules/tsx, allowed me to figure out what was going on:

You installed esbuild for another platform than the one you're currently using.
This won't work because esbuild is written with native code and needs to
install a platform-specific binary executable.

Specifically the "@esbuild/darwin-arm64" package is present but this platform
needs the "@esbuild/linux-arm64" package instead. People often get into this
situation by installing esbuild on Windows or macOS and copying "node_modules"
into a Docker image that runs Linux, or by copying "node_modules" between
Windows and WSL environments.

If you are installing with npm, you can try not copying the "node_modules"
directory when you copy the files over, and running "npm ci" or "npm install"
on the destination platform after the copy. Or you could consider using yarn
instead of npm which has built-in support for installing a package on multiple
platforms simultaneously.

If you are installing with yarn, you can try listing both this platform and the
other platform in your ".yarnrc.yml" file using the "supportedArchitectures"
feature: https://yarnpkg.com/configuration/yarnrc/#supportedArchitectures
Keep in mind that this means multiple copies of esbuild will be present.

Another alternative is to use the "esbuild-wasm" package instead, which works
the same way on all platforms. But it comes with a heavy performance cost and
can sometimes be 10x slower than the "esbuild" package, so you may also not
want to do that.

    at generateBinPath (/foo/node_modules/esbuild/lib/main.js:1910:17)
    at esbuildCommandAndArgs (/foo/node_modules/esbuild/lib/main.js:1991:33)
    at ensureServiceIsRunning (/foo/node_modules/esbuild/lib/main.js:2162:25)
    at transform (/foo/node_modules/esbuild/lib/main.js:2049:37)
    at file:///foo/node_modules/tsx/dist/index-29948669.mjs:2553:32
    at hr (file:///foo/node_modules/tsx/dist/index-29948669.mjs:2511:29)
    at Cr (file:///foo/node_modules/tsx/dist/index-29948669.mjs:2550:32)
    at Z (file:///foo/node_modules/tsx/dist/esm/index.mjs:5:1755)
    at async nextLoad (node:internal/modules/esm/hooks:865:22)
    at async Hooks.load (node:internal/modules/esm/hooks:448:20)

This error value does not have an "errors" property.

I would suggest changing the code to look like (untested):

const handleEsbuildError = (
    error: TransformFailure | Error,
) => {
    let errorMessage;

    if ('errors' in error) { // assume TransformFailure
        const [firstError] = error.errors;
        errorMessage = `[esbuild Error]: ${firstError.text}`;

        if (firstError.location) {
            const {file, line, column} = firstError.location;
            errorMessage += `\n at ${file}:${line}:${column}`;
        }

    } else {
        errorMessage = `[esbuild Error]: ${error.message}`;
    }

    console.error(errorMessage);

    // eslint-disable-next-line n/no-process-exit
    process.exit(1);
};

Contributions

privatenumber commented 3 months ago

PR welcome but I'd need verification that the fix is effective

hermanbanken commented 3 months ago

Same issue. Really unhelpful error message.

narthur commented 3 months ago

I'm running into the same issue, though pnpm ls esbuild doesn't show anything so it looks like this issue might occur in situations not involving esbuild, too.

narthur commented 3 months ago

Oh, nvm. After patching tsx it looks like I'm getting the same error as @Joris-van-der-Wel.

Here's the patch file I created using pnpm patch in case it's useful to anyone:

tsx@4.7.1.patch

privatenumber commented 3 months ago

As per the Contribution Guide, I've hid comments that aren't productive towards landing a fix.

@narthur Would you be open to contributing a PR?

narthur commented 3 months ago

@privatenumber Sure, I can take a whack at it.

ariran5 commented 3 months ago

Same issue

package.json

{
  "type": "module"
}
tsx --env-file=.env src/index.ts

node:internal/process/esm_loader:34
      internalBinding('errors').triggerUncaughtException(
                                ^
TypeError [Error]: undefined is not iterable (cannot read property Symbol(Symbol.iterator))
    at bt (file:///Users/user/node_modules/tsx/dist/index-29948669.mjs:16:1598)
    at file:///Users/user/node_modules/tsx/dist/index-29948669.mjs:17:632
    at hr (file:///Users/user/node_modules/tsx/dist/index-29948669.mjs:16:1071)
    at Cr (file:///Users/user/node_modules/tsx/dist/index-29948669.mjs:17:560)
    at Z (file:///Users/user/node_modules/tsx/dist/esm/index.mjs:5:1755)
    at async nextLoad (node:internal/modules/esm/hooks:749:22)
    at async Hooks.load (node:internal/modules/esm/hooks:382:20)
    at async handleMessage (node:internal/modules/esm/worker:199:18)

Version 4.7.1 macOS Sonoma 14.1 Node.js v21.3.0

Version 4.6.2 works fine