about summary refs log tree commit diff
path: root/nixpkgs/pkgs/build-support/node/build-npm-package
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/pkgs/build-support/node/build-npm-package')
-rw-r--r--nixpkgs/pkgs/build-support/node/build-npm-package/default.nix88
-rw-r--r--nixpkgs/pkgs/build-support/node/build-npm-package/hooks/default.nix48
-rw-r--r--nixpkgs/pkgs/build-support/node/build-npm-package/hooks/npm-build-hook.sh38
-rw-r--r--nixpkgs/pkgs/build-support/node/build-npm-package/hooks/npm-config-hook.sh119
-rw-r--r--nixpkgs/pkgs/build-support/node/build-npm-package/hooks/npm-install-hook.sh71
5 files changed, 364 insertions, 0 deletions
diff --git a/nixpkgs/pkgs/build-support/node/build-npm-package/default.nix b/nixpkgs/pkgs/build-support/node/build-npm-package/default.nix
new file mode 100644
index 000000000000..1c7bf63e8cd6
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/node/build-npm-package/default.nix
@@ -0,0 +1,88 @@
+{ lib
+, stdenv
+, fetchNpmDeps
+, buildPackages
+, nodejs
+, darwin
+} @ topLevelArgs:
+
+{ name ? "${args.pname}-${args.version}"
+, src ? null
+, srcs ? null
+, sourceRoot ? null
+, prePatch ? ""
+, patches ? [ ]
+, postPatch ? ""
+, nativeBuildInputs ? [ ]
+, buildInputs ? [ ]
+  # The output hash of the dependencies for this project.
+  # Can be calculated in advance with prefetch-npm-deps.
+, npmDepsHash ? ""
+  # Whether to force the usage of Git dependencies that have install scripts, but not a lockfile.
+  # Use with care.
+, forceGitDeps ? false
+  # Whether to force allow an empty dependency cache.
+  # This can be enabled if there are truly no remote dependencies, but generally an empty cache indicates something is wrong.
+, forceEmptyCache ? false
+  # Whether to make the cache writable prior to installing dependencies.
+  # Don't set this unless npm tries to write to the cache directory, as it can slow down the build.
+, makeCacheWritable ? false
+  # The script to run to build the project.
+, npmBuildScript ? "build"
+  # Flags to pass to all npm commands.
+, npmFlags ? [ ]
+  # Flags to pass to `npm ci`.
+, npmInstallFlags ? [ ]
+  # Flags to pass to `npm rebuild`.
+, npmRebuildFlags ? [ ]
+  # Flags to pass to `npm run ${npmBuildScript}`.
+, npmBuildFlags ? [ ]
+  # Flags to pass to `npm pack`.
+, npmPackFlags ? [ ]
+  # Flags to pass to `npm prune`.
+, npmPruneFlags ? npmInstallFlags
+  # Value for npm `--workspace` flag and directory in which the files to be installed are found.
+, npmWorkspace ? null
+, nodejs ? topLevelArgs.nodejs
+, npmDeps ?  fetchNpmDeps {
+  inherit forceGitDeps forceEmptyCache src srcs sourceRoot prePatch patches postPatch;
+  name = "${name}-npm-deps";
+  hash = npmDepsHash;
+}
+  # Custom npmConfigHook
+, npmConfigHook ? null
+  # Custom npmBuildHook
+, npmBuildHook ? null
+  # Custom npmInstallHook
+, npmInstallHook ? null
+, ...
+} @ args:
+
+let
+  # .override {} negates splicing, so we need to use buildPackages explicitly
+  npmHooks = buildPackages.npmHooks.override {
+    inherit nodejs;
+  };
+in
+stdenv.mkDerivation (args // {
+  inherit npmDeps npmBuildScript;
+
+  nativeBuildInputs = nativeBuildInputs
+    ++ [
+      nodejs
+      # Prefer passed hooks
+      (if npmConfigHook != null then npmConfigHook else npmHooks.npmConfigHook)
+      (if npmBuildHook != null then npmBuildHook else npmHooks.npmBuildHook)
+      (if npmInstallHook != null then npmInstallHook else npmHooks.npmInstallHook)
+      nodejs.python
+    ]
+    ++ lib.optionals stdenv.isDarwin [ darwin.cctools ];
+  buildInputs = buildInputs ++ [ nodejs ];
+
+  strictDeps = true;
+
+  # Stripping takes way too long with the amount of files required by a typical Node.js project.
+  dontStrip = args.dontStrip or true;
+
+  meta = (args.meta or { }) // { platforms = args.meta.platforms or nodejs.meta.platforms; };
+})
diff --git a/nixpkgs/pkgs/build-support/node/build-npm-package/hooks/default.nix b/nixpkgs/pkgs/build-support/node/build-npm-package/hooks/default.nix
new file mode 100644
index 000000000000..36f0319e3d23
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/node/build-npm-package/hooks/default.nix
@@ -0,0 +1,48 @@
+{ lib
+, srcOnly
+, makeSetupHook
+, makeWrapper
+, nodejs
+, jq
+, prefetch-npm-deps
+, diffutils
+, installShellFiles
+}:
+
+{
+  npmConfigHook = makeSetupHook
+    {
+      name = "npm-config-hook";
+      substitutions = {
+        nodeSrc = srcOnly nodejs;
+        nodeGyp = "${nodejs}/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js";
+
+        # Specify `diff`, `jq`, and `prefetch-npm-deps` by abspath to ensure that the user's build
+        # inputs do not cause us to find the wrong binaries.
+        diff = "${diffutils}/bin/diff";
+        jq = "${jq}/bin/jq";
+        prefetchNpmDeps = "${prefetch-npm-deps}/bin/prefetch-npm-deps";
+
+        nodeVersion = nodejs.version;
+        nodeVersionMajor = lib.versions.major nodejs.version;
+      };
+    } ./npm-config-hook.sh;
+
+  npmBuildHook = makeSetupHook
+    {
+      name = "npm-build-hook";
+    } ./npm-build-hook.sh;
+
+  npmInstallHook = makeSetupHook
+    {
+      name = "npm-install-hook";
+      propagatedBuildInputs = [
+        installShellFiles
+        makeWrapper
+      ];
+      substitutions = {
+        hostNode = "${nodejs}/bin/node";
+        jq = "${jq}/bin/jq";
+      };
+    } ./npm-install-hook.sh;
+}
diff --git a/nixpkgs/pkgs/build-support/node/build-npm-package/hooks/npm-build-hook.sh b/nixpkgs/pkgs/build-support/node/build-npm-package/hooks/npm-build-hook.sh
new file mode 100644
index 000000000000..c341f672363a
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/node/build-npm-package/hooks/npm-build-hook.sh
@@ -0,0 +1,38 @@
+# shellcheck shell=bash
+
+npmBuildHook() {
+    echo "Executing npmBuildHook"
+
+    runHook preBuild
+
+    if [ -z "${npmBuildScript-}" ]; then
+        echo
+        echo "ERROR: no build script was specified"
+        echo 'Hint: set `npmBuildScript`, override `buildPhase`, or set `dontNpmBuild = true`.'
+        echo
+
+        exit 1
+    fi
+
+    if ! npm run ${npmWorkspace+--workspace=$npmWorkspace} "$npmBuildScript" $npmBuildFlags "${npmBuildFlagsArray[@]}" $npmFlags "${npmFlagsArray[@]}"; then
+        echo
+        echo 'ERROR: `npm build` failed'
+        echo
+        echo "Here are a few things you can try, depending on the error:"
+        echo "1. Make sure your build script ($npmBuildScript) exists"
+        echo '  If there is none, set `dontNpmBuild = true`.'
+        echo '2. If the error being thrown is something similar to "error:0308010C:digital envelope routines::unsupported", add `NODE_OPTIONS = "--openssl-legacy-provider"` to your derivation'
+        echo "  See https://github.com/webpack/webpack/issues/14532 for more information."
+        echo
+
+        exit 1
+    fi
+
+    runHook postBuild
+
+    echo "Finished npmBuildHook"
+}
+
+if [ -z "${dontNpmBuild-}" ] && [ -z "${buildPhase-}" ]; then
+    buildPhase=npmBuildHook
+fi
diff --git a/nixpkgs/pkgs/build-support/node/build-npm-package/hooks/npm-config-hook.sh b/nixpkgs/pkgs/build-support/node/build-npm-package/hooks/npm-config-hook.sh
new file mode 100644
index 000000000000..486b0c2f8372
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/node/build-npm-package/hooks/npm-config-hook.sh
@@ -0,0 +1,119 @@
+# shellcheck shell=bash
+
+npmConfigHook() {
+    echo "Executing npmConfigHook"
+
+    # Use npm patches in the nodejs package
+    export NIX_NODEJS_BUILDNPMPACKAGE=1
+    export prefetchNpmDeps="@prefetchNpmDeps@"
+
+    if [ -n "${npmRoot-}" ]; then
+      pushd "$npmRoot"
+    fi
+
+    echo "Configuring npm"
+
+    export HOME="$TMPDIR"
+    export npm_config_nodedir="@nodeSrc@"
+    export npm_config_node_gyp="@nodeGyp@"
+
+    if [ -z "${npmDeps-}" ]; then
+        echo
+        echo "ERROR: no dependencies were specified"
+        echo 'Hint: set `npmDeps` if using these hooks individually. If this is happening with `buildNpmPackage`, please open an issue.'
+        echo
+
+        exit 1
+    fi
+
+    local -r cacheLockfile="$npmDeps/package-lock.json"
+    local -r srcLockfile="$PWD/package-lock.json"
+
+    echo "Validating consistency between $srcLockfile and $cacheLockfile"
+
+    if ! @diff@ "$srcLockfile" "$cacheLockfile"; then
+      # If the diff failed, first double-check that the file exists, so we can
+      # give a friendlier error msg.
+      if ! [ -e "$srcLockfile" ]; then
+        echo
+        echo "ERROR: Missing package-lock.json from src. Expected to find it at: $srcLockfile"
+        echo "Hint: You can copy a vendored package-lock.json file via postPatch."
+        echo
+
+        exit 1
+      fi
+
+      if ! [ -e "$cacheLockfile" ]; then
+        echo
+        echo "ERROR: Missing lockfile from cache. Expected to find it at: $cacheLockfile"
+        echo
+
+        exit 1
+      fi
+
+      echo
+      echo "ERROR: npmDepsHash is out of date"
+      echo
+      echo "The package-lock.json in src is not the same as the in $npmDeps."
+      echo
+      echo "To fix the issue:"
+      echo '1. Use `lib.fakeHash` as the npmDepsHash value'
+      echo "2. Build the derivation and wait for it to fail with a hash mismatch"
+      echo "3. Copy the 'got: sha256-' value back into the npmDepsHash field"
+      echo
+
+      exit 1
+    fi
+
+    export CACHE_MAP_PATH="$TMP/MEOW"
+    @prefetchNpmDeps@ --map-cache
+
+    @prefetchNpmDeps@ --fixup-lockfile "$srcLockfile"
+
+    local cachePath
+
+    if [ -z "${makeCacheWritable-}" ]; then
+        cachePath="$npmDeps"
+    else
+        echo "Making cache writable"
+        cp -r "$npmDeps" "$TMPDIR/cache"
+        chmod -R 700 "$TMPDIR/cache"
+        cachePath="$TMPDIR/cache"
+    fi
+
+    npm config set cache "$cachePath"
+    npm config set offline true
+    npm config set progress false
+
+    echo "Installing dependencies"
+
+    if ! npm ci --ignore-scripts $npmInstallFlags "${npmInstallFlagsArray[@]}" $npmFlags "${npmFlagsArray[@]}"; then
+        echo
+        echo "ERROR: npm failed to install dependencies"
+        echo
+        echo "Here are a few things you can try, depending on the error:"
+        echo '1. Set `makeCacheWritable = true`'
+        echo "  Note that this won't help if npm is complaining about not being able to write to the logs directory -- look above that for the actual error."
+        echo '2. Set `npmFlags = [ "--legacy-peer-deps" ]`'
+        echo
+
+        exit 1
+    fi
+
+    patchShebangs node_modules
+
+    npm rebuild $npmRebuildFlags "${npmRebuildFlagsArray[@]}" $npmFlags "${npmFlagsArray[@]}"
+
+    patchShebangs node_modules
+
+    rm "$CACHE_MAP_PATH"
+    unset CACHE_MAP_PATH
+
+    if [ -n "${npmRoot-}" ]; then
+      popd
+    fi
+
+    echo "Finished npmConfigHook"
+}
+
+postPatchHooks+=(npmConfigHook)
diff --git a/nixpkgs/pkgs/build-support/node/build-npm-package/hooks/npm-install-hook.sh b/nixpkgs/pkgs/build-support/node/build-npm-package/hooks/npm-install-hook.sh
new file mode 100644
index 000000000000..750ed421789f
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/node/build-npm-package/hooks/npm-install-hook.sh
@@ -0,0 +1,71 @@
+# shellcheck shell=bash
+
+npmInstallHook() {
+    echo "Executing npmInstallHook"
+
+    runHook preInstall
+
+    local -r packageOut="$out/lib/node_modules/$(@jq@ --raw-output '.name' package.json)"
+
+    # `npm pack` writes to cache so temporarily override it
+    while IFS= read -r file; do
+        local dest="$packageOut/$(dirname "$file")"
+        mkdir -p "$dest"
+        cp "${npmWorkspace-.}/$file" "$dest"
+    done < <(@jq@ --raw-output '.[0].files | map(.path | select(. | startswith("node_modules/") | not)) | join("\n")' <<< "$(npm_config_cache="$HOME/.npm" npm pack --json --dry-run --loglevel=warn --no-foreground-scripts ${npmWorkspace+--workspace=$npmWorkspace} $npmPackFlags "${npmPackFlagsArray[@]}" $npmFlags "${npmFlagsArray[@]}")")
+
+    # Based on code from Python's buildPythonPackage wrap.sh script, for
+    # supporting both the case when makeWrapperArgs is an array and a
+    # IFS-separated string.
+    #
+    # TODO: remove the string branch when __structuredAttrs are used.
+    if [[ "${makeWrapperArgs+defined}" == "defined" && "$(declare -p makeWrapperArgs)" =~ ^'declare -a makeWrapperArgs=' ]]; then
+        local -a user_args=("${makeWrapperArgs[@]}")
+    else
+        local -a user_args="(${makeWrapperArgs:-})"
+    fi
+    while IFS=" " read -ra bin; do
+        mkdir -p "$out/bin"
+        makeWrapper @hostNode@ "$out/bin/${bin[0]}" --add-flags "$packageOut/${bin[1]}" "${user_args[@]}"
+    done < <(@jq@ --raw-output '(.bin | type) as $typ | if $typ == "string" then
+        .name + " " + .bin
+        elif $typ == "object" then .bin | to_entries | map(.key + " " + .value) | join("\n")
+        elif $typ == "null" then empty
+        else "invalid type " + $typ | halt_error end' "${npmWorkspace-.}/package.json")
+
+    while IFS= read -r man; do
+        installManPage "$packageOut/$man"
+    done < <(@jq@ --raw-output '(.man | type) as $typ | if $typ == "string" then .man
+        elif $typ == "list" then .man | join("\n")
+        elif $typ == "null" then empty
+        else "invalid type " + $typ | halt_error end' "${npmWorkspace-.}/package.json")
+
+    local -r nodeModulesPath="$packageOut/node_modules"
+
+    if [ ! -d "$nodeModulesPath" ]; then
+        if [ -z "${dontNpmPrune-}" ]; then
+            if ! npm prune --omit=dev --no-save ${npmWorkspace+--workspace=$npmWorkspace} $npmPruneFlags "${npmPruneFlagsArray[@]}" $npmFlags "${npmFlagsArray[@]}"; then
+              echo
+              echo
+              echo "ERROR: npm prune step failed"
+              echo
+              echo 'If npm tried to download additional dependencies above, try setting `dontNpmPrune = true`.'
+              echo
+
+              exit 1
+            fi
+        fi
+
+        find node_modules -maxdepth 1 -type d -empty -delete
+
+        cp -r node_modules "$nodeModulesPath"
+    fi
+
+    runHook postInstall
+
+    echo "Finished npmInstallHook"
+}
+
+if [ -z "${dontNpmInstall-}" ] && [ -z "${installPhase-}" ]; then
+    installPhase=npmInstallHook
+fi