I wrote a command line application in TypeScript which calls ts-node through shebang.

1
2
#!/usr/bin/env ts-node
/// my script ...

It worked fine in both macOS and Linux.

Error

But when I tried to run it in Windows/pwsh, an error occurred.

1
2
3
4
5
6
Line |
24 | & "ts-node$exe" "$basedir/node_modules/<MY_APP>/src/cli.ts"
| ~~~~~~~~~~~~~
| The term 'ts-node.exe' is not recognized as a name of a cmdlet, function, script file,
| or executable program. Check the spelling of the name, or if a path was included,
| verify that the path is correct and try again.

Investigation

I reinstalled ts-node package globally to reassure that it is correctly installed:

1
npm install -g ts-node typescript

However, the error still exits.

Yes, ts-node is correctly installed, I can even call it from command line:

1
2
ts-node --version
// output: v10.4.0

Then I tried find where the ts-node.exe is installed, it surprised me that there is never a ts-node.exe. No wonder why pwsh couldn’t find ts-node.exe.

Install ts-node globally by running npm install -g ts-node will only create the following files in npm’s exec directory:

1
2
3
4
5
6
7
8
9
10
11
12
ts-node
ts-node-cwd
ts-node-cwd.cmd
ts-node-cwd.ps1
ts-node-script
ts-node-script.cmd
ts-node-script.ps1
ts-node-transpile-only
ts-node-transpile-only.cmd
ts-node-transpile-only.ps1
ts-node.cmd
ts-node.ps1

I opened my command line application’s npm executable script to see what went wrong, found that the generated pwsh script is regardlessly calling the ENV declared in shebang through an executable file with a .exe suffix, and this is obviously wrong!

1
2
3
4
5
6
7
8
9
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent

$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}

You can’t call an executable that does NOT exit.

Solution

So, I created an alias that maps ts-node.ps1 which is the right executable to ts-node.exe to fool my app’s executable script that’s generated by npm.:

1
2
3
4
# Set an alias in user's PowerShell Profile to enable it globally
Add-Content $PROFILE "Set-Alias -Name ts-node.exe -Value ts-node.ps1"
# Or enable it in current pwsh instance.
Set-Alias -Name ts-node.exe -Value ts-node.ps1

Problem solved.