'npm tells .env file doesn't exist (but it does), and it can even print it

All I want is to simply use an environment variable in an npm script without installing unnecessary dependencies.

I have a .env file in the project root, containing

AWS_S3_BUCKET_ID=whatever

My package.json includes these scripts

"scripts": {
  "grep": "echo $(grep AWS_S3_BUCKET_ID .env | cut -d '=' -f2)",
  "source": "source .env && echo $AWS_S3_BUCKET_ID",
  "test-source": "[ -f .env ] && cat .env && npm run source"
},

Believe it or not, npm run grep executes, while npm run source does not.

Now, the absurdity is that npm run source returns with the following error message

sh: line 0: source: .env: file not found

and npm run test-source first prints the file content (!!) and then says the file does not exist.

Is there a reason for this behaviour?

edit: I'm on Linux



Solution 1:[1]

Try this:

...
"source": ". ./.env && echo $AWS_S3_BUCKET_ID",
...

I couldn't get the source command to work at all in Ubuntu under an npm script (sh: 1: source: not found), but this worked for me with a minimal project set up like yours.

Noted in this answer: https://stackoverflow.com/a/670209/4068418

In the POSIX standard, which /bin/sh is supposed to respect, the command is . (a single dot), not source. The source command is a csh-ism that has been pulled into bash.

You can also use the env-cmd package from npm to be cross platform, but it seems you're okay with making your scripts Linux only.

Solution 2:[2]

It looks like this has to do with the fact that npm-run uses /bin/sh by default to run scripts rather than /bin/bash.

This may come as a surprise because on the /bin/sh dot command, the filename is relative to the $PATH and doesn't include the current working directory. You can actually test it through:

$ sh
sh-5.1$ . .env
sh: .: .env: file not found
sh-5.1$

This is the reason you need to make sure the relative path is explicitly mentioned as . ./.env. However, on /bin/bash the dot command (.) is an alias to source, which behaves differently and includes the current working directory as part of the file lookup, so both the below commands are synonyms and work "as you would expect"

$ /bin/bash
$ . .env
$ source .env

To work around this, you can, as @wowyesokay mentioned, simply use the relative file path, or run the NPM scripts with the flag script-shell flag. As npm run test-source --script-shell /bin/bash. Alternatively, you may want to add that option to your .npmrc file, so all calls use script-shell as bash.

On this page you can find some valuable explanation about the dot command on /bin/sh, most notably, this part:

If there are slashes in the file name, . (dot) looks for the named file. If there are no slashes . (dot) searches for file in the directories specified in the PATH variable. This may surprise some people when they use dot to run a file in the working directory, but their search rules are not set up to look at the working directory. As a result, the shell does not find the shell file.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 zanona