<commit_before>{ stdenv, runCommand, nodejs, neededNatives}:

{
  name, src,

  # Node package name
  pkgName ? (builtins.parseDrvName name).name,

  # List or attribute set of dependencies
  deps ? {},

  # List or attribute set of peer depencies
  peerDependencies ? [],

  # Whether package is binary or library
  bin ? null,

  # Flags passed to npm install
  flags ? [],

  # Command to be run before shell hook
  preShellHook ? "",

  # Command to be run after shell hook
  postShellHook ? "",

  # Attribute set of already resolved deps (internal),
  # for avoiding infinite recursion
  resolvedDeps ? {},

  ...
} @ args:

with stdenv.lib;

let
  npmFlags = concatStringsSep " " (map (v: "--${v}") flags);

  sources = runCommand "node-sources" {} ''
    tar --no-same-owner --no-same-permissions -xf ${nodejs.src}
    mv *node* $out
  '';

  # Convert deps to attribute set
  attrDeps = if isAttrs deps then deps else
    (listToAttrs (map (dep: nameValuePair dep.name dep) deps));

  # All required node modules, without already resolved dependencies
  requiredDeps = removeAttrs attrDeps (attrNames resolvedDeps);

  # Recursive dependencies that we want to avoid with shim creation
  recursiveDeps = removeAttrs attrDeps (attrNames requiredDeps);

  peerDeps = filter (dep: dep.pkgName != pkgName) peerDependencies;

  self = let
    # Pass resolved dependencies to dependencies of this package
    deps = map (
      dep: dep.override {
        resolvedDeps = resolvedDeps // { "${name}" = self; };
      }
    ) (attrValues requiredDeps);

    patchShebangs = dir: ''
        node=`type -p node`
        coffee=`type -p coffee || true`
        find -L ${dir} -type f -print0 | \
        xargs -0 sed --follow-symlinks -i \
            -e 's@#!/usr/bin/env node@#!'"$node"'@' \
            -e 's@#!/usr/bin/env coffee@#!'"$coffee"'@' \
            -e 's@#!/.*/node@#!'"$node"'@' \
            -e 's@#!/.*/coffee@#!'"$coffee"'@' || true
    '';

  in stdenv.mkDerivation ({
    inherit src;

    configurePhase = ''
      runHook preConfigure

      ${patchShebangs "./"}

      # Some version specifiers (latest, unstable, URLs, file paths) force NPM
      # to make remote connections or consult paths outside the Nix store.
      # The following JavaScript replaces these by * to prevent that:
      # Also some packages require a specific npm version because npm may
      # resovle dependencies differently, but npm is not used by Nix for dependency
      # reslution, so these requirements are dropped.

      (
      cat <<EOF
        var fs = require('fs');
        var url = require('url');<commit_msg>buildNodePackage: patch shebangs only in text files<commit_after>{ stdenv, runCommand, nodejs, neededNatives}:

{
  name, src,

  # Node package name
  pkgName ? (builtins.parseDrvName name).name,

  # List or attribute set of dependencies
  deps ? {},

  # List or attribute set of peer depencies
  peerDependencies ? [],

  # Whether package is binary or library
  bin ? null,

  # Flags passed to npm install
  flags ? [],

  # Command to be run before shell hook
  preShellHook ? "",

  # Command to be run after shell hook
  postShellHook ? "",

  # Attribute set of already resolved deps (internal),
  # for avoiding infinite recursion
  resolvedDeps ? {},

  ...
} @ args:

with stdenv.lib;

let
  npmFlags = concatStringsSep " " (map (v: "--${v}") flags);

  sources = runCommand "node-sources" {} ''
    tar --no-same-owner --no-same-permissions -xf ${nodejs.src}
    mv *node* $out
  '';

  # Convert deps to attribute set
  attrDeps = if isAttrs deps then deps else
    (listToAttrs (map (dep: nameValuePair dep.name dep) deps));

  # All required node modules, without already resolved dependencies
  requiredDeps = removeAttrs attrDeps (attrNames resolvedDeps);

  # Recursive dependencies that we want to avoid with shim creation
  recursiveDeps = removeAttrs attrDeps (attrNames requiredDeps);

  peerDeps = filter (dep: dep.pkgName != pkgName) peerDependencies;

  self = let
    # Pass resolved dependencies to dependencies of this package
    deps = map (
      dep: dep.override {
        resolvedDeps = resolvedDeps // { "${name}" = self; };
      }
    ) (attrValues requiredDeps);

    patchShebangs = dir: ''
        node=`type -p node`
        coffee=`type -p coffee || true`
        find -L ${dir} -type f -print0 | xargs -0 grep -Il . | \
        xargs sed --follow-symlinks -i \
            -e 's@#!/usr/bin/env node@#!'"$node"'@' \
            -e 's@#!/usr/bin/env coffee@#!'"$coffee"'@' \
            -e 's@#!/.*/node@#!'"$node"'@' \
            -e 's@#!/.*/coffee@#!'"$coffee"'@' || true
    '';

  in stdenv.mkDerivation ({
    inherit src;

    configurePhase = ''
      runHook preConfigure

      ${patchShebangs "./"}

      # Some version specifiers (latest, unstable, URLs, file paths) force NPM
      # to make remote connections or consult paths outside the Nix store.
      # The following JavaScript replaces these by * to prevent that:
      # Also some packages require a specific npm version because npm may
      # resovle dependencies differently, but npm is not used by Nix for dependency
      # reslution, so these requirements are dropped.

      (
      cat <<EOF
        var fs = require('fs');
        var url = require('url');
{'fraction_non_alphanumeric': 0.12068640392230813, 'fraction_numerical': 0.0007542900245144258, 'mean_word_length': 2.698744769874477, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2362216', 'n_tokens_mistral': 1686, 'n_tokens_neox': 1598, 'n_words': 548}