at master 234 lines 6.7 kB view raw
1{ 2 lib, 3 fetchurl, 4 stdenv, 5 callPackages, 6 runCommand, 7 cctools, 8}: 9 10let 11 inherit (builtins) 12 match 13 elemAt 14 toJSON 15 removeAttrs 16 ; 17 inherit (lib) importJSON mapAttrs; 18 19 matchGitHubReference = match "github(.com)?:.+"; 20 getName = package: package.name or "unknown"; 21 getVersion = package: package.version or "0.0.0"; 22 23 # Fetch a module from package-lock.json -> packages 24 fetchModule = 25 { 26 module, 27 npmRoot ? null, 28 fetcherOpts, 29 }: 30 ( 31 if module ? "resolved" && module.resolved != null then 32 ( 33 let 34 # Parse scheme from URL 35 mUrl = match "(.+)://(.+)" module.resolved; 36 scheme = elemAt mUrl 0; 37 in 38 ( 39 if mUrl == null then 40 ( 41 assert npmRoot != null; 42 { 43 outPath = npmRoot + "/${module.resolved}"; 44 } 45 ) 46 else if (scheme == "http" || scheme == "https") then 47 (fetchurl ( 48 { 49 url = module.resolved; 50 hash = module.integrity; 51 } 52 // fetcherOpts 53 )) 54 else if lib.hasPrefix "git" module.resolved then 55 (builtins.fetchGit ( 56 { 57 url = module.resolved; 58 } 59 // fetcherOpts 60 )) 61 else 62 throw "Unsupported URL scheme: ${scheme}" 63 ) 64 ) 65 else 66 null 67 ); 68 69 cleanModule = lib.flip removeAttrs [ 70 "link" # Remove link not to symlink directories. These have been processed to store paths already. 71 "funding" # Remove funding to get rid sponsorship nag in build output 72 ]; 73 74 # Manage node_modules outside of the store with hooks 75 hooks = callPackages ./hooks { }; 76 77in 78lib.fix (self: { 79 importNpmLock = 80 { 81 npmRoot ? null, 82 package ? importJSON (npmRoot + "/package.json"), 83 packageLock ? importJSON (npmRoot + "/package-lock.json"), 84 pname ? getName package, 85 version ? getVersion package, 86 # A map of additional fetcher options forwarded to the fetcher used to download the package. 87 # Example: { "node_modules/axios" = { curlOptsList = [ "--verbose" ]; }; } 88 # This will download the axios package with curl's verbose option. 89 fetcherOpts ? { }, 90 # A map from node_module path to an alternative package to use instead of fetching the source in package-lock.json. 91 # Example: { "node_modules/axios" = stdenv.mkDerivation { ... }; } 92 # This is useful if you want to inject custom sources for a specific package. 93 packageSourceOverrides ? { }, 94 }: 95 let 96 mapLockDependencies = mapAttrs ( 97 name: version: 98 ( 99 # Substitute the constraint with the version of the dependency from the top-level of package-lock. 100 if 101 ( 102 # if the version is `latest` 103 version == "latest" 104 || 105 # Or if it's a github reference 106 matchGitHubReference version != null 107 ) 108 then 109 packageLock'.packages.${"node_modules/${name}"}.version 110 # But not a regular version constraint 111 else 112 version 113 ) 114 ); 115 116 packageLock' = packageLock // { 117 packages = mapAttrs ( 118 modulePath: module: 119 let 120 src = 121 packageSourceOverrides.${modulePath} or (fetchModule { 122 inherit module npmRoot; 123 fetcherOpts = fetcherOpts.${modulePath} or { }; 124 }); 125 in 126 cleanModule module 127 // lib.optionalAttrs (src != null) { 128 resolved = "file:${src}"; 129 } 130 // lib.optionalAttrs (module ? dependencies) { 131 dependencies = mapLockDependencies module.dependencies; 132 } 133 // lib.optionalAttrs (module ? optionalDependencies) { 134 optionalDependencies = mapLockDependencies module.optionalDependencies; 135 } 136 ) packageLock.packages; 137 }; 138 139 mapPackageDependencies = mapAttrs ( 140 name: _: packageLock'.packages.${"node_modules/${name}"}.resolved 141 ); 142 143 # Substitute dependency references in package.json with Nix store paths 144 packageJSON' = 145 package 146 // lib.optionalAttrs (package ? dependencies) { 147 dependencies = mapPackageDependencies package.dependencies; 148 } 149 // lib.optionalAttrs (package ? devDependencies) { 150 devDependencies = mapPackageDependencies package.devDependencies; 151 }; 152 153 pname = package.name or "unknown"; 154 155 in 156 runCommand "${pname}-${version}-sources" 157 { 158 inherit pname version; 159 160 passAsFile = [ 161 "package" 162 "packageLock" 163 ]; 164 165 package = toJSON packageJSON'; 166 packageLock = toJSON packageLock'; 167 } 168 '' 169 mkdir $out 170 cp "$packagePath" $out/package.json 171 cp "$packageLockPath" $out/package-lock.json 172 ''; 173 174 # Build node modules from package.json & package-lock.json 175 buildNodeModules = 176 { 177 npmRoot ? null, 178 package ? importJSON (npmRoot + "/package.json"), 179 packageLock ? importJSON (npmRoot + "/package-lock.json"), 180 nodejs, 181 derivationArgs ? { }, 182 }: 183 stdenv.mkDerivation ( 184 { 185 pname = derivationArgs.pname or "${getName package}-node-modules"; 186 version = derivationArgs.version or getVersion package; 187 188 dontUnpack = true; 189 190 npmDeps = self.importNpmLock { 191 inherit npmRoot package packageLock; 192 }; 193 194 package = toJSON package; 195 packageLock = toJSON packageLock; 196 197 installPhase = '' 198 runHook preInstall 199 mkdir $out 200 cp package.json $out/ 201 cp package-lock.json $out/ 202 [[ -d node_modules ]] && mv node_modules $out/ 203 runHook postInstall 204 ''; 205 } 206 // derivationArgs 207 // { 208 nativeBuildInputs = [ 209 nodejs 210 nodejs.passthru.python 211 hooks.npmConfigHook 212 ] 213 ++ lib.optionals stdenv.hostPlatform.isDarwin [ cctools ] 214 ++ derivationArgs.nativeBuildInputs or [ ]; 215 216 passAsFile = [ 217 "package" 218 "packageLock" 219 ] 220 ++ derivationArgs.passAsFile or [ ]; 221 222 postPatch = '' 223 cp --no-preserve=mode "$packagePath" package.json 224 cp --no-preserve=mode "$packageLockPath" package-lock.json 225 '' 226 + derivationArgs.postPatch or ""; 227 } 228 ); 229 230 inherit hooks; 231 inherit (hooks) npmConfigHook linkNodeModulesHook; 232 233 __functor = self: self.importNpmLock; 234})