Loading doc/languages-frameworks/javascript.section.md +31 −0 Original line number Diff line number Diff line Loading @@ -233,6 +233,37 @@ sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= It returns a derivation with all `package-lock.json` dependencies downloaded into `$out/`, usable as an npm cache. #### importNpmLock {#javascript-buildNpmPackage-importNpmLock} `importNpmLock` is a Nix function that requires the following optional arguments: - `npmRoot`: Path to package directory containing the source tree - `package`: Parsed contents of `package.json` - `packageLock`: Parsed contents of `package-lock.json` - `pname`: Package name - `version`: Package version It returns a derivation with a patched `package.json` & `package-lock.json` with all dependencies resolved to Nix store paths. This function is analogous to using `fetchNpmDeps`, but instead of specifying `hash` it uses metadata from `package.json` & `package-lock.json`. Note that `npmHooks.npmConfigHook` cannot be used with `importNpmLock`. You will instead need to use `importNpmLock.npmConfigHook`: ```nix { buildNpmPackage, importNpmLock }: buildNpmPackage { pname = "hello"; version = "0.1.0"; npmDeps = importNpmLock { npmRoot = ./.; }; npmConfigHook = importNpmLock.npmConfigHook; } ``` ### corepack {#javascript-corepack} This package puts the corepack wrappers for pnpm and yarn in your PATH, and they will honor the `packageManager` setting in the `package.json`. Loading pkgs/build-support/node/build-npm-package/default.nix +14 −3 Original line number Diff line number Diff line Loading @@ -49,6 +49,12 @@ name = "${name}-npm-deps"; hash = npmDepsHash; } # Custom npmConfigHook , npmConfigHook ? null # Custom npmBuildHook , npmBuildHook ? null # Custom npmInstallHook , npmInstallHook ? null , ... } @ args: Loading @@ -57,14 +63,19 @@ let npmHooks = buildPackages.npmHooks.override { inherit nodejs; }; inherit (npmHooks) npmConfigHook npmBuildHook npmInstallHook; in stdenv.mkDerivation (args // { inherit npmDeps npmBuildScript; nativeBuildInputs = nativeBuildInputs ++ [ nodejs npmConfigHook npmBuildHook npmInstallHook nodejs.python ] ++ [ 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 ]; Loading pkgs/build-support/node/import-npm-lock/default.nix 0 → 100644 +134 −0 Original line number Diff line number Diff line { lib , fetchurl , stdenv , callPackages , runCommand }: let inherit (builtins) match elemAt toJSON removeAttrs; inherit (lib) importJSON mapAttrs; matchGitHubReference = match "github(.com)?:.+"; getName = package: package.name or "unknown"; getVersion = package: package.version or "0.0.0"; # Fetch a module from package-lock.json -> packages fetchModule = { module , npmRoot ? null }: ( if module ? "resolved" then ( let # Parse scheme from URL mUrl = match "(.+)://(.+)" module.resolved; scheme = elemAt mUrl 0; in ( if mUrl == null then ( assert npmRoot != null; { outPath = npmRoot + "/${module.resolved}"; } ) else if (scheme == "http" || scheme == "https") then ( fetchurl { url = module.resolved; hash = module.integrity; } ) else if lib.hasPrefix "git" module.resolved then ( builtins.fetchGit { url = module.resolved; } ) else throw "Unsupported URL scheme: ${scheme}" ) ) else null ); # Manage node_modules outside of the store with hooks hooks = callPackages ./hooks { }; in { importNpmLock = { npmRoot ? null , package ? importJSON (npmRoot + "/package.json") , packageLock ? importJSON (npmRoot + "/package-lock.json") , pname ? getName package , version ? getVersion package }: let mapLockDependencies = mapAttrs (name: version: ( # Substitute the constraint with the version of the dependency from the top-level of package-lock. if ( # if the version is `latest` version == "latest" || # Or if it's a github reference matchGitHubReference version != null ) then packageLock'.packages.${"node_modules/${name}"}.version # But not a regular version constraint else version )); packageLock' = packageLock // { packages = mapAttrs (_: module: let src = fetchModule { inherit module npmRoot; }; in (removeAttrs module [ "link" "funding" ]) // lib.optionalAttrs (src != null) { resolved = "file:${src}"; } // lib.optionalAttrs (module ? dependencies) { dependencies = mapLockDependencies module.dependencies; } // lib.optionalAttrs (module ? optionalDependencies) { optionalDependencies = mapLockDependencies module.optionalDependencies; }) packageLock.packages; }; mapPackageDependencies = mapAttrs (name: _: packageLock'.packages.${"node_modules/${name}"}.resolved); # Substitute dependency references in package.json with Nix store paths packageJSON' = package // lib.optionalAttrs (package ? dependencies) { dependencies = mapPackageDependencies package.dependencies; } // lib.optionalAttrs (package ? devDependencies) { devDependencies = mapPackageDependencies package.devDependencies; }; pname = package.name or "unknown"; in runCommand "${pname}-${version}-sources" { inherit pname version; passAsFile = [ "package" "packageLock" ]; package = toJSON packageJSON'; packageLock = toJSON packageLock'; } '' mkdir $out cp "$packagePath" $out/package.json cp "$packageLockPath" $out/package-lock.json ''; inherit hooks; inherit (hooks) npmConfigHook; __functor = self: self.importNpmLock; } pkgs/build-support/node/import-npm-lock/hooks/canonicalize-symlinks.js 0 → 100644 +52 −0 Original line number Diff line number Diff line #!/usr/bin/env node const fs = require("fs"); const path = require("path"); // When installing files rewritten to the Nix store with npm // npm writes the symlinks relative to the build directory. // // This makes relocating node_modules tricky when refering to the store. // This script walks node_modules and canonicalizes symlinks. async function canonicalize(storePrefix, root) { console.log(storePrefix, root) const entries = await fs.promises.readdir(root); const paths = entries.map((entry) => path.join(root, entry)); const stats = await Promise.all( paths.map(async (path) => { return { path: path, stat: await fs.promises.lstat(path), }; }) ); const symlinks = stats.filter((stat) => stat.stat.isSymbolicLink()); const dirs = stats.filter((stat) => stat.stat.isDirectory()); // Canonicalize symlinks to their real path await Promise.all( symlinks.map(async (stat) => { const target = await fs.promises.realpath(stat.path); if (target.startsWith(storePrefix)) { await fs.promises.unlink(stat.path); await fs.promises.symlink(target, stat.path); } }) ); // Recurse into directories await Promise.all(dirs.map((dir) => canonicalize(storePrefix, dir.path))); } async function main() { const args = process.argv.slice(2); const storePrefix = args[0]; if (fs.existsSync("node_modules")) { await canonicalize(storePrefix, "node_modules"); } } main(); pkgs/build-support/node/import-npm-lock/hooks/default.nix 0 → 100644 +13 −0 Original line number Diff line number Diff line { callPackage, lib, makeSetupHook, srcOnly, nodejs }: { npmConfigHook = makeSetupHook { name = "npm-config-hook"; substitutions = { nodeSrc = srcOnly nodejs; nodeGyp = "${nodejs}/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js"; canonicalizeSymlinksScript = ./canonicalize-symlinks.js; storePrefix = builtins.storeDir; }; } ./npm-config-hook.sh; } Loading
doc/languages-frameworks/javascript.section.md +31 −0 Original line number Diff line number Diff line Loading @@ -233,6 +233,37 @@ sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= It returns a derivation with all `package-lock.json` dependencies downloaded into `$out/`, usable as an npm cache. #### importNpmLock {#javascript-buildNpmPackage-importNpmLock} `importNpmLock` is a Nix function that requires the following optional arguments: - `npmRoot`: Path to package directory containing the source tree - `package`: Parsed contents of `package.json` - `packageLock`: Parsed contents of `package-lock.json` - `pname`: Package name - `version`: Package version It returns a derivation with a patched `package.json` & `package-lock.json` with all dependencies resolved to Nix store paths. This function is analogous to using `fetchNpmDeps`, but instead of specifying `hash` it uses metadata from `package.json` & `package-lock.json`. Note that `npmHooks.npmConfigHook` cannot be used with `importNpmLock`. You will instead need to use `importNpmLock.npmConfigHook`: ```nix { buildNpmPackage, importNpmLock }: buildNpmPackage { pname = "hello"; version = "0.1.0"; npmDeps = importNpmLock { npmRoot = ./.; }; npmConfigHook = importNpmLock.npmConfigHook; } ``` ### corepack {#javascript-corepack} This package puts the corepack wrappers for pnpm and yarn in your PATH, and they will honor the `packageManager` setting in the `package.json`. Loading
pkgs/build-support/node/build-npm-package/default.nix +14 −3 Original line number Diff line number Diff line Loading @@ -49,6 +49,12 @@ name = "${name}-npm-deps"; hash = npmDepsHash; } # Custom npmConfigHook , npmConfigHook ? null # Custom npmBuildHook , npmBuildHook ? null # Custom npmInstallHook , npmInstallHook ? null , ... } @ args: Loading @@ -57,14 +63,19 @@ let npmHooks = buildPackages.npmHooks.override { inherit nodejs; }; inherit (npmHooks) npmConfigHook npmBuildHook npmInstallHook; in stdenv.mkDerivation (args // { inherit npmDeps npmBuildScript; nativeBuildInputs = nativeBuildInputs ++ [ nodejs npmConfigHook npmBuildHook npmInstallHook nodejs.python ] ++ [ 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 ]; Loading
pkgs/build-support/node/import-npm-lock/default.nix 0 → 100644 +134 −0 Original line number Diff line number Diff line { lib , fetchurl , stdenv , callPackages , runCommand }: let inherit (builtins) match elemAt toJSON removeAttrs; inherit (lib) importJSON mapAttrs; matchGitHubReference = match "github(.com)?:.+"; getName = package: package.name or "unknown"; getVersion = package: package.version or "0.0.0"; # Fetch a module from package-lock.json -> packages fetchModule = { module , npmRoot ? null }: ( if module ? "resolved" then ( let # Parse scheme from URL mUrl = match "(.+)://(.+)" module.resolved; scheme = elemAt mUrl 0; in ( if mUrl == null then ( assert npmRoot != null; { outPath = npmRoot + "/${module.resolved}"; } ) else if (scheme == "http" || scheme == "https") then ( fetchurl { url = module.resolved; hash = module.integrity; } ) else if lib.hasPrefix "git" module.resolved then ( builtins.fetchGit { url = module.resolved; } ) else throw "Unsupported URL scheme: ${scheme}" ) ) else null ); # Manage node_modules outside of the store with hooks hooks = callPackages ./hooks { }; in { importNpmLock = { npmRoot ? null , package ? importJSON (npmRoot + "/package.json") , packageLock ? importJSON (npmRoot + "/package-lock.json") , pname ? getName package , version ? getVersion package }: let mapLockDependencies = mapAttrs (name: version: ( # Substitute the constraint with the version of the dependency from the top-level of package-lock. if ( # if the version is `latest` version == "latest" || # Or if it's a github reference matchGitHubReference version != null ) then packageLock'.packages.${"node_modules/${name}"}.version # But not a regular version constraint else version )); packageLock' = packageLock // { packages = mapAttrs (_: module: let src = fetchModule { inherit module npmRoot; }; in (removeAttrs module [ "link" "funding" ]) // lib.optionalAttrs (src != null) { resolved = "file:${src}"; } // lib.optionalAttrs (module ? dependencies) { dependencies = mapLockDependencies module.dependencies; } // lib.optionalAttrs (module ? optionalDependencies) { optionalDependencies = mapLockDependencies module.optionalDependencies; }) packageLock.packages; }; mapPackageDependencies = mapAttrs (name: _: packageLock'.packages.${"node_modules/${name}"}.resolved); # Substitute dependency references in package.json with Nix store paths packageJSON' = package // lib.optionalAttrs (package ? dependencies) { dependencies = mapPackageDependencies package.dependencies; } // lib.optionalAttrs (package ? devDependencies) { devDependencies = mapPackageDependencies package.devDependencies; }; pname = package.name or "unknown"; in runCommand "${pname}-${version}-sources" { inherit pname version; passAsFile = [ "package" "packageLock" ]; package = toJSON packageJSON'; packageLock = toJSON packageLock'; } '' mkdir $out cp "$packagePath" $out/package.json cp "$packageLockPath" $out/package-lock.json ''; inherit hooks; inherit (hooks) npmConfigHook; __functor = self: self.importNpmLock; }
pkgs/build-support/node/import-npm-lock/hooks/canonicalize-symlinks.js 0 → 100644 +52 −0 Original line number Diff line number Diff line #!/usr/bin/env node const fs = require("fs"); const path = require("path"); // When installing files rewritten to the Nix store with npm // npm writes the symlinks relative to the build directory. // // This makes relocating node_modules tricky when refering to the store. // This script walks node_modules and canonicalizes symlinks. async function canonicalize(storePrefix, root) { console.log(storePrefix, root) const entries = await fs.promises.readdir(root); const paths = entries.map((entry) => path.join(root, entry)); const stats = await Promise.all( paths.map(async (path) => { return { path: path, stat: await fs.promises.lstat(path), }; }) ); const symlinks = stats.filter((stat) => stat.stat.isSymbolicLink()); const dirs = stats.filter((stat) => stat.stat.isDirectory()); // Canonicalize symlinks to their real path await Promise.all( symlinks.map(async (stat) => { const target = await fs.promises.realpath(stat.path); if (target.startsWith(storePrefix)) { await fs.promises.unlink(stat.path); await fs.promises.symlink(target, stat.path); } }) ); // Recurse into directories await Promise.all(dirs.map((dir) => canonicalize(storePrefix, dir.path))); } async function main() { const args = process.argv.slice(2); const storePrefix = args[0]; if (fs.existsSync("node_modules")) { await canonicalize(storePrefix, "node_modules"); } } main();
pkgs/build-support/node/import-npm-lock/hooks/default.nix 0 → 100644 +13 −0 Original line number Diff line number Diff line { callPackage, lib, makeSetupHook, srcOnly, nodejs }: { npmConfigHook = makeSetupHook { name = "npm-config-hook"; substitutions = { nodeSrc = srcOnly nodejs; nodeGyp = "${nodejs}/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js"; canonicalizeSymlinksScript = ./canonicalize-symlinks.js; storePrefix = builtins.storeDir; }; } ./npm-config-hook.sh; }