40

Normally in node files I just put

#!/usr/bin/env node 

at the top and make it executable to create a file that can be run from a bash terminal. However if I do that in a Typescript file, the compiler says "error TS1001: Unexpected character "#"" and refuses to compile it. So how can I make a shell executable node file with Typescript?

Update: This has been fixed https://github.com/Microsoft/TypeScript/issues/2749 shebangs now passthrough

Tom Larkworthy
  • 2,104
  • 1
  • 20
  • 29
  • Write the executable in JS and just require() your TypeScript file. – jgillich Apr 25 '14 at 16:25
  • that's a work around not a solution. What if I want to process command line arguments in TypeScript? – Tom Larkworthy Apr 25 '14 at 16:40
  • Shebangs assume that the target interpreter uses `#` as its comment character, so that the shebang itself is ignored. You have the added complication that there isn't a TypeScript interpreter; you first compile it to JavaScript, then interpret the result. – chepner Apr 25 '14 at 16:42
  • `process.argv` is available to the entire process, that's not an issue. But of course it works around a bug in the TypeScript compiler, aside from getting it fixed there is probably not much you can do. – jgillich Apr 25 '14 at 16:43
  • Are you using `tsc` to compile the typescript file into JS? – WiredPrairie Apr 25 '14 at 17:01
  • yes I am using tcs and it does not like the shebang*, so it does not survive to the js file which is, of course, what would actually be the executable. * bloody microsoft, lol FYI @chepner the '#' is not a comment in Typescript. Its unrecognised. Comments are c style – Tom Larkworthy Apr 25 '14 at 17:08
  • 1
    ok I reported it https://typescript.codeplex.com/workitem/2465 – Tom Larkworthy Apr 25 '14 at 17:17
  • 1
    ok so they immediately closed the bug report because they don't understand shebangs. Upvote if you think its a missing feature... – Tom Larkworthy Apr 25 '14 at 23:16

8 Answers8

26

See https://github.com/Microsoft/TypeScript/blob/master/bin/tsc for an example. Basically have a dummy file without the .js extension and just require the actual .js file.

E.g. In file named tsc:

#!/usr/bin/env node
require('./tsc.js')
basarat
  • 261,912
  • 58
  • 460
  • 511
17

You were right to report the bug to Microsoft, and they were wrong to close it as wontfix.

Until it is fixed, here's a workaround. Paste the following into a text file and save it as shebangify:

#!/usr/bin/env node
var fs = require('fs');
var path = process.argv[2];
var data = "#!/usr/bin/env node\n\n";
data += fs.readFileSync(path);
fs.writeFileSync(path, data);

(N.B. To keep this answer concise, the code above doesn't have any error-checking or other refinements, so use at your own risk or use this instead. Also, see this SO question for more info about prepending to files.)

Make the file executable with by using a terminal to navigate to the file's directory and executing:

$ chmod +x shebangify

Once you have created a Typescript program (e.g. called myscript.ts) that you wish to compile and turn into a shell script (e.g. called myscript), do so by executing a sequence along these lines in your terminal:

$ tsc --out myscript myscript.ts ; ./shebangify myscript ; chmod +x myscript
  • 1
    This doesn't play well with source maps. – chocolateboy Aug 08 '15 at 18:35
  • 1
    @chocolateboy I haven't used source maps before. Thanks for prompting me to look them up! I'm not sure many people minify their shell scripts, even if they are using node.js, but I could well be mistaken. Perhaps doing so improves performance? Anyhow, if you have a solution to the questioner's problem that *does* play well with source maps, please post it as an answer, or suggest an appropriate edit to mine :) UPDATE: just saw your comment below that shebang support has been [added](https://github.com/Microsoft/TypeScript/pull/4120). Hurrah! –  Aug 08 '15 at 21:17
  • 4
    The issue opened by @Jonathan (see [answer](https://stackoverflow.com/a/29616276/710951)) was finally resolved and today TypeScript passes shebangs through ✨ – Alba Mendez Apr 29 '19 at 19:06
  • 1
    Whoops, didn't saw the last bit on your comment, I think it'd be good to mention the news in the answer – Alba Mendez Apr 29 '19 at 19:08
  • It's worth explaining *why* MS should fix it. I think the answer is because the shebang syntax is specifically required by Node. Is that correct? – jpmc26 May 08 '19 at 00:23
  • To fix the issue with source maps, instead of _adding_ a line to the top of the script, provide a dummy comment line at the top of the script and _replace_ this line in shebangify – Gena Batsyan Mar 10 '20 at 09:14
9

If you have TypeScript and ts-node installed globally:

npm install typescript ts-node -g

You can now easily do this with:

#!/usr/bin/env ts-node

console.log('Hello world')
Breck
  • 2,075
  • 1
  • 13
  • 10
7

I don't have enough reputation points to post a comment, but I'd just thought it'd be good for everyone to know that I opened a new issue on GitHub since that's what the Typescript devs are using to track things like this: https://github.com/Microsoft/TypeScript/issues/2749 .

Jonathan
  • 151
  • 1
  • 4
  • 5
    Shebang support has been [added](https://github.com/Microsoft/TypeScript/issues/4120) and will be available in TypeScript 1.6. In the meantime, it can be tested with `npm install -g typescript@next`. – chocolateboy Aug 08 '15 at 18:57
4

In case anyone is still struggling with making it work, the ts file should start with #! node instead of #!/usr/bin/env node, and tsc will take care of the rest.

Victor Mukherjee
  • 10,487
  • 16
  • 54
  • 97
  • Both `#! node` and `#!/usr/bin/env node` are equivalent in most systems. The problem is that, in some systems, `#! node` asks for a relative path to the script, and the script will fail if `node` is not in the same folder as the script. `/usr/bin/env node` searches for the path of the node binary in the system `$PATH` variable, making it more portable. Apart from portability, they are functionally the same. – Felds Liscia Jan 29 '21 at 17:50
1

I've never been able to get ts-node to work, so I finally made my own way to write shell scripts in TypeScript. If there were a package manager for Bash I would make a package, but there isn't, so I just put this script in my path as ts-exec:

#!/usr/bin/env bash

file_to_run="$1"
basename=`basename "$1"`
tmp_prefix=`basename "$BASH_SOURCE"`

TMPDIR=`mktemp -d -t "$tmp_prefix-XXXXXXXXXX"`
pushd "$TMPDIR" > /dev/null

cp "$1" "$basename.ts"
tsc "$basename"
node "$basename.js"

popd > /dev/null
rm -rf "$TMPDIR"

And now I can do things like this:

#!/usr/bin/env ts-exec

let greeting: string = "Hello World!";

console.log( greeting );

And it works.

Of course, it does have some limitations

  • It's only suitable for scripts that are confined to a single file
  • It doesn't do any error checking
  • It has implicit dependencies
  • It doesn't have an installer

... so basically it's for bash nerds who want to use TypeScript for small scripts that would be a pain to write as Bash scripts. I'm still baffled that ts-node doesn't cover this case, and I'd rather not have to futz with temp files that might get left behind and waste space if there's an error, but so far this covers my use-case. (Besides, I've got that cronjob that deletes everything in ~/tmp that's more than 31622400 seconds old every night, so stray temp files can't eat my whole system.)

ShadSterling
  • 1,792
  • 1
  • 21
  • 40
1

As of ts-node v8.9.0 it seems like the recommended way to do this is with the following:

#!/usr/bin/env ts-node-script
robd
  • 9,646
  • 5
  • 40
  • 59
0

If you don't want to install TS and ts-node globally and want to make the script runnable by the file path directly, create a file for example cli.ts next to local node_modules and put this as the first line

#!/usr/bin/env ./node_modules/.bin/ts-node

console.log('Wow');

Then execute by calling ./cli.ts

tom10271
  • 4,222
  • 5
  • 33
  • 62