lol
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 23.11-beta 208 lines 6.2 kB view raw
1#!/usr/bin/env node 2'use strict' 3 4const fs = require('fs') 5const crypto = require('crypto') 6const process = require('process') 7const https = require('https') 8const child_process = require('child_process') 9const path = require('path') 10const lockfile = require('./yarnpkg-lockfile.js') 11const { promisify } = require('util') 12const url = require('url') 13const { urlToName } = require('./common.js') 14 15const execFile = promisify(child_process.execFile) 16 17const exec = async (...args) => { 18 const res = await execFile(...args) 19 if (res.error) throw new Error(res.stderr) 20 return res 21} 22 23const downloadFileHttps = (fileName, url, expectedHash, hashType = 'sha1') => { 24 return new Promise((resolve, reject) => { 25 const get = (url, redirects = 0) => https.get(url, (res) => { 26 if(redirects > 10) { 27 reject('Too many redirects!'); 28 return; 29 } 30 if(res.statusCode === 301 || res.statusCode === 302) { 31 return get(res.headers.location, redirects + 1) 32 } 33 const file = fs.createWriteStream(fileName) 34 const hash = crypto.createHash(hashType) 35 res.pipe(file) 36 res.pipe(hash).setEncoding('hex') 37 res.on('end', () => { 38 file.close() 39 const h = hash.read() 40 if (expectedHash === undefined){ 41 console.log(`Warning: lockfile url ${url} doesn't end in "#<hash>" to validate against. Downloaded file had hash ${h}.`); 42 } else if (h != expectedHash) return reject(new Error(`hash mismatch, expected ${expectedHash}, got ${h}`)) 43 resolve() 44 }) 45 res.on('error', e => reject(e)) 46 }) 47 get(url) 48 }) 49} 50 51const downloadGit = async (fileName, url, rev) => { 52 await exec('nix-prefetch-git', [ 53 '--out', fileName + '.tmp', 54 '--url', url, 55 '--rev', rev, 56 '--builder' 57 ]) 58 59 await exec('tar', [ 60 // hopefully make it reproducible across runs and systems 61 '--owner=0', '--group=0', '--numeric-owner', '--format=gnu', '--sort=name', '--mtime=@1', 62 63 // Set u+w because tar-fs can't unpack archives with read-only dirs: https://github.com/mafintosh/tar-fs/issues/79 64 '--mode', 'u+w', 65 66 '-C', fileName + '.tmp', 67 '-cf', fileName, '.' 68 ]) 69 70 await exec('rm', [ '-rf', fileName + '.tmp', ]) 71} 72 73const isGitUrl = pattern => { 74 // https://github.com/yarnpkg/yarn/blob/3119382885ea373d3c13d6a846de743eca8c914b/src/resolvers/exotics/git-resolver.js#L15-L47 75 const GIT_HOSTS = ['github.com', 'gitlab.com', 'bitbucket.com', 'bitbucket.org'] 76 const GIT_PATTERN_MATCHERS = [/^git:/, /^git\+.+:/, /^ssh:/, /^https?:.+\.git$/, /^https?:.+\.git#.+/] 77 78 for (const matcher of GIT_PATTERN_MATCHERS) if (matcher.test(pattern)) return true 79 80 const {hostname, path} = url.parse(pattern) 81 if (hostname && path && GIT_HOSTS.indexOf(hostname) >= 0 82 // only if dependency is pointing to a git repo, 83 // e.g. facebook/flow and not file in a git repo facebook/flow/archive/v1.0.0.tar.gz 84 && path.split('/').filter(p => !!p).length === 2 85 ) return true 86 87 return false 88} 89 90const downloadPkg = (pkg, verbose) => { 91 const [ name, spec ] = pkg.key.split('@', 2); 92 if (spec.startsWith('file:')) { 93 console.info(`ignoring relative file:path dependency "${spec}"`) 94 return 95 } 96 97 const [ url, hash ] = pkg.resolved.split('#') 98 if (verbose) console.log('downloading ' + url) 99 const fileName = urlToName(url) 100 if (url.startsWith('https://codeload.github.com/') && url.includes('/tar.gz/')) { 101 const s = url.split('/') 102 return downloadGit(fileName, `https://github.com/${s[3]}/${s[4]}.git`, s[s.length-1]) 103 } else if (url.startsWith('https://github.com/') && url.endsWith('.tar.gz')) { 104 const s = url.split('/') 105 return downloadGit(fileName, `https://github.com/${s[3]}/${s[4]}.git`, s[s.length-1].replace(/.tar.gz$/, '')) 106 } else if (isGitUrl(url)) { 107 return downloadGit(fileName, url.replace(/^git\+/, ''), hash) 108 } else if (url.startsWith('https://')) { 109 if (typeof pkg.integrity === 'string' || pkg.integrity instanceof String) { 110 const [ type, checksum ] = pkg.integrity.split('-') 111 return downloadFileHttps(fileName, url, Buffer.from(checksum, 'base64').toString('hex'), type) 112 } 113 return downloadFileHttps(fileName, url, hash) 114 } else if (url.startsWith('file:')) { 115 console.warn(`ignoring unsupported file:path url "${url}"`) 116 } else { 117 throw new Error('don\'t know how to download "' + url + '"') 118 } 119} 120 121const performParallel = tasks => { 122 const worker = async () => { 123 while (tasks.length > 0) await tasks.shift()() 124 } 125 126 const workers = [] 127 for (let i = 0; i < 4; i++) { 128 workers.push(worker()) 129 } 130 131 return Promise.all(workers) 132} 133 134const prefetchYarnDeps = async (lockContents, verbose) => { 135 const lockData = lockfile.parse(lockContents) 136 const tasks = Object.values( 137 Object.entries(lockData.object) 138 .map(([key, value]) => { 139 return { key, ...value } 140 }) 141 .reduce((out, pkg) => { 142 out[pkg.resolved] = pkg 143 return out 144 }, {}) 145 ) 146 .map(pkg => () => downloadPkg(pkg, verbose)) 147 148 await performParallel(tasks) 149 await fs.promises.writeFile('yarn.lock', lockContents) 150 if (verbose) console.log('Done') 151} 152 153const showUsage = async () => { 154 process.stderr.write(` 155syntax: prefetch-yarn-deps [path to yarn.lock] [options] 156 157Options: 158 -h --help Show this help 159 -v --verbose Verbose output 160 --builder Only perform the download to current directory, then exit 161`) 162 process.exit(1) 163} 164 165const main = async () => { 166 const args = process.argv.slice(2) 167 let next, lockFile, verbose, isBuilder 168 while (next = args.shift()) { 169 if (next == '--builder') { 170 isBuilder = true 171 } else if (next == '--verbose' || next == '-v') { 172 verbose = true 173 } else if (next == '--help' || next == '-h') { 174 showUsage() 175 } else if (!lockFile) { 176 lockFile = next 177 } else { 178 showUsage() 179 } 180 } 181 let lockContents 182 try { 183 lockContents = await fs.promises.readFile(lockFile || 'yarn.lock', 'utf-8') 184 } catch { 185 showUsage() 186 } 187 188 if (isBuilder) { 189 await prefetchYarnDeps(lockContents, verbose) 190 } else { 191 const { stdout: tmpDir } = await exec('mktemp', [ '-d' ]) 192 193 try { 194 process.chdir(tmpDir.trim()) 195 await prefetchYarnDeps(lockContents, verbose) 196 const { stdout: hash } = await exec('nix-hash', [ '--type', 'sha256', '--base32', tmpDir.trim() ]) 197 console.log(hash) 198 } finally { 199 await exec('rm', [ '-rf', tmpDir.trim() ]) 200 } 201 } 202} 203 204main() 205 .catch(e => { 206 console.error(e) 207 process.exit(1) 208 })