This is possible from within Node, if you're willing to use a compiled extension: node-kexec.
I preform almost precisely the tasks you want to in my project's executable as follows (forgive the CoffeeScript):
page = (cb)->
   # If we reach this point in the code and $_PAGINATED is already set, then we've
   # successfully paginated the script and should now actually run the code meant
   # to be run *inside* a pager.
   if process.env['_PAGINATED']?
      return cb()
   # I use tricks like this to control the pager itself; they can be super-dirty,
   # though, and mutating shell-command lines without a *lot* of careful
   # invocation logic is generally a bad idea unless you have a good reason:
   pager = process.env.PAGER || 'less --chop-long-lines'
   pager = pager.replace /less(\s|$)/, 'less --RAW-CONTROL-CHARS$1'
   # I use this elsewhere in my code-base to override `term.columns` if it is
   # unset; because the pager often doesn't properly report terminal-width
   process.env['PAGINATED_COLUMNS'] = term.columns
   process.env['_PAGINATED'] = 'yes'
   # This is a horrible hack. Thanks, Stack Overflow.
   #    <https://stackoverflow.com/a/22827128>
   escapeShellArg = (cmd)-> "'" + cmd.replace(/\'/g, "'\\''") + "'"
   # This is how we *re-invoke* precisely the exact instructions that our script /
   # executable was originally given; in addition to ensuring that `process.argv`
   # is the same by doing this, `kexec` will already ensure that our replacement
   # inherits our `process.stdout` and etc.
   #
   # (These arguments are invoked *in a shell*, as in `"sh" "-c" ...`, by
   # `kexec()`!)
   params = process.argv.slice()
   params = params.map (arg)-> escapeShellArg arg
   params.push '|'
   params.push pager
   log.debug "!! Forking and exec'ing to pager: `#{pager}`"
   log.wtf "-- Invocation via `sh -c`:", params.join ' '
   kexec params.join ' '
This is invoked as simply as you'd expect; something like page(print_help_text) (which is how I'm using it).
There's also a couple obvious gotchas: it's not going to magically fork your program where it is invoked, it's going to re-execute the entire program up to the point where it got invoked; so you'll want to make sure that anything happening before invoking page() is deterministic in nature; i.e. precisely the same things will occur if the program is re-invoked with the same set of command-line arguments. (It's a convenience, not magic.) You probably also want to make sure the code leading up to page() is idempotent, i.e. doesn't have any undesired side-effects when run twice.
(If you want to do this without compiling a native extension, try and get Node core to add an exec function like Ruby's. :P)
Nota bene: If you do decide to do this, please make it configurable with the standard --[no-]pager flag. Pagers can be a nice convenience, but not everybody wants to use one.
On the same note, please realize that compiled dependencies can cause a lot of people trouble; personally, I keep kexec in my package.json's optionalDependencies, and then use a try/catch (or a convenience like optional) to source it. That way, if it fails to install on the user's system, your code still runs as expected, just without the nicety of the pager.