prefetch-yarn-deps, fetchYarnDeps: init

Yureka d6e0195c 7141eb9c

+284
+74
pkgs/build-support/node/fetch-yarn-deps/default.nix
···
··· 1 + { stdenv, lib, makeWrapper, coreutils, nix-prefetch-git, fetchurl, nodejs-slim, prefetch-yarn-deps, cacert, callPackage }: 2 + 3 + let 4 + yarnpkg-lockfile-tar = fetchurl { 5 + url = "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz"; 6 + sha512 = "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ=="; 7 + }; 8 + 9 + in { 10 + prefetch-yarn-deps = stdenv.mkDerivation { 11 + name = "prefetch-yarn-deps"; 12 + 13 + dontUnpack = true; 14 + 15 + nativeBuildInputs = [ makeWrapper ]; 16 + buildInputs = [ coreutils nix-prefetch-git nodejs-slim ]; 17 + 18 + buildPhase = '' 19 + runHook preBuild 20 + 21 + mkdir libexec 22 + tar --strip-components=1 -xf ${yarnpkg-lockfile-tar} package/index.js 23 + mv index.js libexec/yarnpkg-lockfile.js 24 + cp ${./index.js} libexec/index.js 25 + patchShebangs libexec/index.js 26 + 27 + runHook postBuild 28 + ''; 29 + 30 + installPhase = '' 31 + runHook preInstall 32 + 33 + mkdir -p $out/bin 34 + cp -r libexec $out 35 + makeWrapper $out/libexec/index.js $out/bin/prefetch-yarn-deps \ 36 + --prefix PATH : ${lib.makeBinPath [ coreutils nix-prefetch-git ]} 37 + 38 + runHook postInstall 39 + ''; 40 + }; 41 + 42 + fetchYarnDeps = let 43 + f = { 44 + name ? "offline", 45 + yarnLock, 46 + hash ? "", 47 + sha256 ? "", 48 + }: let 49 + hash_ = 50 + if hash != "" then { outputHashAlgo = null; outputHash = hash; } 51 + else if sha256 != "" then { outputHashAlgo = "sha256"; outputHash = sha256; } 52 + else throw "fetchYarnDeps requires a hash"; 53 + in stdenv.mkDerivation { 54 + inherit name; 55 + 56 + dontUnpack = true; 57 + dontInstall = true; 58 + 59 + nativeBuildInputs = [ prefetch-yarn-deps ]; 60 + GIT_SSL_CAINFO = "${cacert}/etc/ssl/certs/ca-bundle.crt"; 61 + 62 + buildPhase = '' 63 + mkdir -p $out 64 + (cd $out; prefetch-yarn-deps --verbose --builder ${yarnLock}) 65 + ''; 66 + 67 + outputHashMode = "recursive"; 68 + inherit (hash_) outputHashAlgo outputHash; 69 + }; 70 + 71 + in lib.setFunctionArgs f (lib.functionArgs f) // { 72 + tests = callPackage ./tests {}; 73 + }; 74 + }
+168
pkgs/build-support/node/fetch-yarn-deps/index.js
···
··· 1 + #!/usr/bin/env node 2 + 'use strict' 3 + 4 + const fs = require('fs') 5 + const crypto = require('crypto') 6 + const process = require('process') 7 + const https = require('https') 8 + const child_process = require('child_process') 9 + const path = require('path') 10 + const lockfile = require('./yarnpkg-lockfile.js') 11 + const { promisify } = require('util') 12 + 13 + const execFile = promisify(child_process.execFile) 14 + 15 + const exec = async (...args) => { 16 + const res = await execFile(...args) 17 + if (res.error) throw new Error(res.stderr) 18 + return res 19 + } 20 + 21 + const downloadFileHttps = (fileName, url, expectedHash) => { 22 + return new Promise((resolve, reject) => { 23 + https.get(url, (res) => { 24 + const file = fs.createWriteStream(fileName) 25 + const hash = crypto.createHash('sha1') 26 + res.pipe(file) 27 + res.pipe(hash).setEncoding('hex') 28 + res.on('end', () => { 29 + file.close() 30 + const h = hash.read() 31 + if (h != expectedHash) return reject(new Error(`hash mismatch, expected ${expectedHash}, got ${h}`)) 32 + resolve() 33 + }) 34 + res.on('error', e => reject(e)) 35 + }) 36 + }) 37 + } 38 + 39 + const downloadGit = async (fileName, url, rev) => { 40 + await exec('nix-prefetch-git', [ 41 + '--out', fileName + '.tmp', 42 + '--url', url, 43 + '--rev', rev, 44 + '--builder' 45 + ]) 46 + 47 + await exec('tar', [ 48 + // hopefully make it reproducible across runs and systems 49 + '--owner=0', '--group=0', '--numeric-owner', '--format=gnu', '--sort=name', '--mtime=@1', 50 + 51 + // Set u+w because tar-fs can't unpack archives with read-only dirs: https://github.com/mafintosh/tar-fs/issues/79 52 + '--mode', 'u+w', 53 + 54 + '-C', fileName + '.tmp', 55 + '-cf', fileName, '.' 56 + ]) 57 + 58 + await exec('rm', [ '-rf', fileName + '.tmp', ]) 59 + } 60 + 61 + const downloadPkg = (pkg, verbose) => { 62 + const [ url, hash ] = pkg.resolved.split('#') 63 + if (verbose) console.log('downloading ' + url) 64 + if (url.startsWith('https://codeload.github.com/') && url.includes('/tar.gz/')) { 65 + const fileName = path.basename(url) 66 + const s = url.split('/') 67 + downloadGit(fileName, `https://github.com/${s[3]}/${s[4]}.git`, s[6]) 68 + } else if (url.startsWith('https://')) { 69 + const fileName = url 70 + .replace(/https:\/\/(.)*(.com)\//g, '') // prevents having long directory names 71 + .replace(/[@/%:-]/g, '_') // replace @ and : and - and % characters with underscore 72 + 73 + return downloadFileHttps(fileName, url, hash) 74 + } else if (url.startsWith('git+')) { 75 + const fileName = path.basename(url) 76 + return downloadGit(fileName, url.replace(/^git\+/, ''), hash) 77 + } else { 78 + throw new Error('don\'t know how to download "' + url + '"') 79 + } 80 + } 81 + 82 + const performParallel = tasks => { 83 + const worker = async () => { 84 + while (tasks.length > 0) await tasks.shift()() 85 + } 86 + 87 + const workers = [] 88 + for (let i = 0; i < 4; i++) { 89 + workers.push(worker()) 90 + } 91 + 92 + return Promise.all(workers) 93 + } 94 + 95 + const prefetchYarnDeps = async (lockData, verbose) => { 96 + const tasks = Object.values( 97 + Object.entries(lockData.object) 98 + .map(([key, value]) => { 99 + return { key, ...value } 100 + }) 101 + .reduce((out, pkg) => { 102 + out[pkg.resolved] = pkg 103 + return out 104 + }, {}) 105 + ) 106 + .map(pkg => () => downloadPkg(pkg, verbose)) 107 + 108 + await performParallel(tasks) 109 + if (verbose) console.log('Done') 110 + } 111 + 112 + const showUsage = async () => { 113 + process.stderr.write(` 114 + syntax: prefetch-yarn-deps [path to yarn.lock] [options] 115 + 116 + Options: 117 + -h --help Show this help 118 + -v --verbose Verbose output 119 + --builder Only perform the download to current directory, then exit 120 + `) 121 + process.exit(1) 122 + } 123 + 124 + const main = async () => { 125 + const args = process.argv.slice(2) 126 + let next, lockFile, verbose, isBuilder 127 + while (next = args.shift()) { 128 + if (next == '--builder') { 129 + isBuilder = true 130 + } else if (next == '--verbose' || next == '-v') { 131 + verbose = true 132 + } else if (next == '--help' || next == '-h') { 133 + showUsage() 134 + } else if (!lockFile) { 135 + lockFile = next 136 + } else { 137 + showUsage() 138 + } 139 + } 140 + let lockContents 141 + try { 142 + lockContents = await fs.promises.readFile(lockFile || 'yarn.lock', 'utf-8') 143 + } catch(e) { 144 + showUsage() 145 + } 146 + const lockData = lockfile.parse(lockContents) 147 + 148 + if (isBuilder) { 149 + await prefetchYarnDeps(lockData, verbose) 150 + } else { 151 + const { stdout: tmpDir } = await exec('mktemp', [ '-d' ]) 152 + 153 + try { 154 + process.chdir(tmpDir.trim()) 155 + await prefetchYarnDeps(lockData, verbose) 156 + const { stdout: hash } = await exec('nix-hash', [ '--type', 'sha256', '--base32', tmpDir.trim() ]) 157 + console.log(hash) 158 + } finally { 159 + await exec('rm', [ '-rf', tmpDir.trim() ]) 160 + } 161 + } 162 + } 163 + 164 + main() 165 + .catch(e => { 166 + console.error(e) 167 + process.exit(1) 168 + })
+16
pkgs/build-support/node/fetch-yarn-deps/tests/default.nix
···
··· 1 + { invalidateFetcherByDrvHash, fetchYarnDeps, ... }: 2 + 3 + { 4 + simple = invalidateFetcherByDrvHash fetchYarnDeps { 5 + yarnLock = ./simple.lock; 6 + sha256 = "sha256-Erdkw2E8wWT09jFNLXGkrdwKl0HuSZWnUDJUrV95vSE="; 7 + }; 8 + gitDep = invalidateFetcherByDrvHash fetchYarnDeps { 9 + yarnLock = ./git.lock; 10 + sha256 = "sha256-lAqN4LpoE+jgsQO1nDtuORwcVEO7ogEV53jCu2jFJUI="; 11 + }; 12 + githubDep = invalidateFetcherByDrvHash fetchYarnDeps { 13 + yarnLock = ./github.lock; 14 + sha256 = "sha256-Tsfgyjxz8x6gNmfN0xR7G/NQNoEs4svxRN/N+26vfJU="; 15 + }; 16 + }
+7
pkgs/build-support/node/fetch-yarn-deps/tests/git.lock
···
··· 1 + # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 + # yarn lockfile v1 3 + 4 + 5 + "async@git+https://github.com/caolan/async": 6 + version "3.2.1" 7 + resolved "git+https://github.com/caolan/async#fc9ba651341af5ab974aade6b1640e345912be83"
+7
pkgs/build-support/node/fetch-yarn-deps/tests/github.lock
···
··· 1 + # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 + # yarn lockfile v1 3 + 4 + 5 + "async@github:caolan/async": 6 + version "3.2.1" 7 + resolved "https://codeload.github.com/caolan/async/tar.gz/fc9ba651341af5ab974aade6b1640e345912be83"
+8
pkgs/build-support/node/fetch-yarn-deps/tests/simple.lock
···
··· 1 + # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 + # yarn lockfile v1 3 + 4 + 5 + lit-html@1: 6 + version "1.4.1" 7 + resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-1.4.1.tgz#0c6f3ee4ad4eb610a49831787f0478ad8e9ae5e0" 8 + integrity sha512-B9btcSgPYb1q4oSOb/PrOT6Z/H+r6xuNzfH4lFli/AWhYwdtrgQkQWBbIc6mdnf6E2IL3gDXdkkqNktpU0OZQA==
+4
pkgs/top-level/all-packages.nix
··· 468 469 fetchMavenArtifact = callPackage ../build-support/fetchmavenartifact { }; 470 471 find-cursor = callPackage ../tools/X11/find-cursor { }; 472 473 flare-floss = callPackage ../tools/security/flare-floss { };
··· 468 469 fetchMavenArtifact = callPackage ../build-support/fetchmavenartifact { }; 470 471 + inherit (callPackage ../build-support/node/fetch-yarn-deps { }) 472 + prefetch-yarn-deps 473 + fetchYarnDeps; 474 + 475 find-cursor = callPackage ../tools/X11/find-cursor { }; 476 477 flare-floss = callPackage ../tools/security/flare-floss { };