391

How can I detect whether my Node.JS file was called using SH:node path-to-file or JS:require('path-to-file')?

This is the Node.JS equivalent to my previous question in Perl: How can I run my Perl script only if it wasn't loaded with require?

pppery
  • 3,731
  • 22
  • 33
  • 46
700 Software
  • 85,281
  • 83
  • 234
  • 341
  • 5
    Possible duplicate of [node.js equivalent of python's if \_\_name\_\_ == '\_\_main\_\_'](http://stackoverflow.com/questions/4981891/node-js-equivalent-of-pythons-if-name-main) – GingerPlusPlus May 21 '16 at 17:23
  • 2
    @GingerPlusPlus That was fun. _That_ question is marked as a dupe, and sends you back here. This one isn't as old and the answers are effectively the same, so your guess on why that one lost the dupe war is as good as mine. – ruffin Jun 12 '22 at 01:36
  • pondering the implication of when providing information becomes 'war'... – john k Apr 05 '23 at 17:19

8 Answers8

625
if (require.main === module) {
    console.log('called directly');
} else {
    console.log('required as a module');
}

See documentation for this here: https://nodejs.org/docs/latest/api/modules.html#modules_accessing_the_main_module

nicolaskruchten
  • 26,384
  • 8
  • 83
  • 101
  • 5
    Is there any way to get around this? I have code (which I don't have control over) that does this, but I need to require() it and have it act as though it was called directly. Basically, I need to fool something that uses that test into thinking it was called directly. – Kevin Nov 17 '15 at 22:17
  • 3
    @Kevin I don't know about doing this with `require()`, but you could maybe do it with either importing the file then running `eval` on it, or by running `require('child_process').exec('node the_file.js')` – MalcolmOcean Apr 29 '17 at 14:52
  • 3
    When using ES modules with Node.js, you can use the [`es-main`](https://www.npmjs.com/package/es-main) package to check if a module was run directly. – Tim Schaub Feb 19 '20 at 23:41
  • 1
    I regret using ES modules. All the tutorials were written before they existed and there's so much stuff that doesn't work and now I have to install npm packages just so I can import useful functions from a script without executing the actual script?? – Boris Verkhovskiy Sep 29 '21 at 19:13
  • @Kevin Is there a way to prevent somebody from getting around my check? I have code that I don't want to ever get tricked into thinking it was called directly... Is there any way to prevent somebody from preventing me from tricking their code? When does it end? – Michael Sep 23 '22 at 23:21
108

There is another, slightly shorter way (not outlined in the mentioned docs).

var runningAsScript = !module.parent;

I outlined more details about how this all works under the hood in this blog post.

m-a-r-c-e-l-i-n-o
  • 2,622
  • 18
  • 26
Thorsten Lorenz
  • 11,781
  • 8
  • 52
  • 62
  • +1, I like this better, but I will hesitate before switching accepted answers. :) – 700 Software Mar 04 '13 at 14:51
  • 10
    As I indicated, the official way that is documented is the one @nicolaskruchten outlined. This is just an alternative, no need to switch accepted answer. Both work. – Thorsten Lorenz Mar 06 '13 at 03:50
  • 15
    I had to use this rather than the documented way - the documented way works for eg. `node script.js` but not `cat script.js | node`. This way works for both. – Tim Malone Dec 14 '17 at 23:47
  • 1
    Please note that `module.parent` is deprecated ([since v14.6.0, v12.19.0](https://nodejs.org/api/modules.html#moduleparent)) so you should avoid it. – pixelistik Aug 21 '22 at 09:18
23

For those using ES Modules, solutions using require.main, module.parent and __dirname/__filename won’t work because they aren’t available in ESM.

Instead, you can use import.meta.url (requires Node 10.12+) to determine if the current file is being run directly (rather than imported by another file):

import { resolve } from 'path'
import { fileURLToPath } from 'url'

const pathToThisFile = resolve(fileURLToPath(import.meta.url))
const pathPassedToNode = resolve(process.argv[1])
const isThisFileBeingRunViaCLI = pathToThisFile.includes(pathPassedToNode)

// Note:
// - `resolve()` is used to handle symlinks
// - `includes()` is used to handle cases where the file extension was omitted
//   when passed to node

This works by

  1. Converting the import.meta.url value (a file path like file:///Users/username/foo/bar.js) into a regular path, e.g. /Users/username/foo/bar.js
  2. Comparing that path to the first argument of the current process. If the file was invoked using node foo/bar.js then the first argument will be foo/bar.js.

ESLint Note: If ESLint doesn’t like the import.meta.url syntax then you’ll need to update to ESLint ^7.2.0 and turn your ecmaVersion up to 11 (2020).

More info: process.argv, import.meta.url

seanCodes
  • 553
  • 7
  • 10
  • 1
    This breaks if your working directory is a symlink. E.g. on Mac OS, /tmp -> /private/tmp. If you cd /tmp and run a script there, meta.url = file:///private/tmp/..., while process.argv[1] = /tmp/... . – hraban Sep 19 '21 at 17:28
  • 1
    rather than checking `fileURLtoPath` directly, use the `path` module to compare the `resolve`d paths, so you don't run into symlink issues. – Mike 'Pomax' Kamermans Nov 07 '21 at 16:41
  • See also separate question https://stackoverflow.com/questions/34842738/if-name-main-equivalent-in-javascript-es6-modules – Beni Cherniavsky-Paskin Aug 03 '22 at 15:45
  • This however won't work if you tried invoking the file without the file extension from the CLI. Since the `node` command supports that, we can modify the last statement to `const isRunningDirectlyViaCLI = modulePath.includes(nodePath)` – Sowmen Rahman Jul 06 '23 at 11:54
14

I was a little confused by the terminology used in the explanation(s). So I had to do a couple quick tests.

I found that these produce the same results:

var isCLI = !module.parent;
var isCLI = require.main === module;

And for the other confused people (and to answer the question directly):

var isCLI = require.main === module;
var wasRequired = !isCLI;
bob
  • 7,539
  • 2
  • 46
  • 42
8

I always find myself trying to recall how to write this goddamn code snippet, so I decided to create a simple module for it. It took me a bit to make it work since accessing caller's module info is not straightforward, but it was fun to see how it could be done.

So the idea is to call a module and ask it if the caller module is the main one. We have to figure out the module of the caller function. My first approach was a variation of the accepted answer:

module.exports = function () {
    return require.main === module.parent;
};

But that is not guaranteed to work. module.parent points to the module which loaded us into memory, not the one calling us. If it is the caller module that loaded this helper module into memory, we're good. But if it isn't, it won't work. So we need to try something else. My solution was to generate a stack trace and get the caller's module name from there:

module.exports = function () {
    // generate a stack trace
    const stack = (new Error()).stack;
    // the third line refers to our caller
    const stackLine = stack.split("\n")[2];
    // extract the module name from that line
    const callerModuleName = /\((.*):\d+:\d+\)$/.exec(stackLine)[1];

    return require.main.filename === callerModuleName;
};

Save this as is-main-module.js and now you can do:

const isMainModule = require("./is-main-module");

if (isMainModule()) {
    console.info("called directly");
} else {
    console.info("required as a module");
}

Which is easier to remember.

Lucio Paiva
  • 19,015
  • 11
  • 82
  • 104
  • 2
    Very cool. I love it when common code snippets are abbreviated to a single name. Small adjustment: `return require.main /*this is undefined if we started node interactively*/ && require.main.filename === callerModuleName;` – masterxilo Feb 06 '18 at 10:43
8

Try this if you are using ES6 modules:

if (process.mainModule.filename === __filename) {
  console.log('running as main module')
}
Kebot
  • 257
  • 2
  • 8
3

First, let's define the problem better. My assumption is that what you are really looking for is whether your script owns process.argv (i.e. whether your script is responsible for processing process.argv). With this assumption in mind, the code and tests below are accurate.

module.parent works excellently, but it is deprecated for good reasons (a module might have multiple parents, in which case module.parent only represents the first parent), so use the following future-proof condition to cover all cases:

if (
  typeof process === 'object' && process && process.argv
   && (
    (
      typeof module === 'object' && module
       && (
        !module.parent
         || require.main === module
         || (process.mainModule && process.mainModule.filename === __filename)
         || (__filename === "[stdin]" && __dirname === ".")
       )
    )
    || (
      typeof document === "object"
      && (function() {
       var scripts = document.getElementsByTagName("script");
       try { // in case we are in a special environment without path
         var normalize = require("path").normalize;
         for (var i=0,len=scripts.length|0; i < len; i=i+1|0)
           if (normalize(scripts[i].src.replace(/^file:/i,"")) === __filename)
             return true;
       } catch(e) {}
      })()
    )
   )
) {
    // this module is top-level and invoked directly by the CLI
    console.log("Invoked from CLI");
} else {
    console.log("Not invoked from CLI");
}

It works correctly in all of the scripts in all of the following cases and never throws any errors:

  • Requiring the script (e.x. require('./main.js'))
  • Directly invoking the script (e.x. nodejs cli.js)
  • Preloading another script (e.x. nodejs -r main.js cli.js)
  • Piping into node CLI (e.x. cat cli.js | nodejs)
  • Piping with preloading (e.x. cat cli.js | nodejs -r main.js)
  • In workers (e.x. new Worker('./worker.js'))
  • In evaled workers (e.x. new Worker('if (<test for CLI>) ...', {eval: true}))
  • Inside ES6 modules (e.x. nodejs --experimental-modules cli-es6.js)
  • Modules with preload (e.x. nodejs --experimental-modules -r main-es6.js cli-es6.js)
  • Piped ES6 modules (e.x. cat cli-es6.js | nodejs --experimental-modules)
  • Pipe+preload module (e.x. cat cli-es6.js | nodejs --experimental-modules -r main-es6.js)
  • In the browser (in which case, CLI is false because there is no process.argv)
  • In mixed browser+server environments (e.x. ElectronJS, in which case both inline scripts and all modules loaded via <script> tags are considered CLI)

The only case where is does not work is when you preload the top-level script (e.x. nodejs -r cli.js cli.js). This problem cannot be solved by piping (e.x. cat cli.js | nodejs -r cli.js) because that executes the script twice (once as a required module and once as top-level). I do not believe there is any possible fix for this because there is no way to know what the main script will be from inside a preloaded script.

Theoretically, errors might be thrown from inside of a getter for an object (e.x. if someone were crazy enough to do Object.defineProperty(globalThis, "process", { get(){throw 0} });), however this will never happen under default circumstances for the properties used in the code snippet in any environment.

Jack G
  • 4,553
  • 2
  • 41
  • 50
  • 3
    This doesn't work for es modules (where neither `module` or `document` are defined globals). You can verify this in a dir with a `"type": "module"` package.json, a `test.js` file that has your code, and then running `node test.js`. It will incorrectly report `Not invoked from CLI`. – Mike 'Pomax' Kamermans Nov 07 '21 at 16:34
2

How can I detect whether my node.js file was called directly from console (windows and unix systems) or loaded using the ESM module import ( import {foo} from 'bar.js')

Such functionality is not exposed. For the moment you should separate your cli and library logic into separate files.

Answer from node.js core contributor devsnek replying to nodejs/help/issues/2420

It's the right answer in my point of view

joemaller
  • 19,579
  • 7
  • 67
  • 84
Gabriel
  • 3,633
  • 1
  • 23
  • 13