lib: migrate requests to fetch (#2220)

PR-URL: https://github.com/nodejs/node-gyp/pull/2220
Reviewed-By: Jiawen Geng <technicalcute@gmail.com>
This commit is contained in:
Matias Lopez 2021-03-17 21:30:04 -04:00 committed by GitHub
parent a78b584236
commit e81602ef55
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 394 additions and 610 deletions

View file

@ -4,55 +4,42 @@ const fs = require('graceful-fs')
const os = require('os')
const tar = require('tar')
const path = require('path')
const util = require('util')
const stream = require('stream')
const crypto = require('crypto')
const log = require('npmlog')
const semver = require('semver')
const request = require('request')
const fetch = require('make-fetch-happen')
const processRelease = require('./process-release')
const win = process.platform === 'win32'
const getProxyFromURI = require('./proxy')
const streamPipeline = util.promisify(stream.pipeline)
function install (fs, gyp, argv, callback) {
var release = processRelease(argv, gyp, process.version, process.release)
/**
* @param {typeof import('graceful-fs')} fs
*/
// ensure no double-callbacks happen
function cb (err) {
if (cb.done) {
return
}
cb.done = true
if (err) {
log.warn('install', 'got an error, rolling back install')
// roll-back the install if anything went wrong
gyp.commands.remove([release.versionDir], function () {
callback(err)
})
} else {
callback(null, release.version)
}
}
async function install (fs, gyp, argv) {
const release = processRelease(argv, gyp, process.version, process.release)
// Determine which node dev files version we are installing
log.verbose('install', 'input version string %j', release.version)
if (!release.semver) {
// could not parse the version string with semver
return callback(new Error('Invalid version number: ' + release.version))
throw new Error('Invalid version number: ' + release.version)
}
if (semver.lt(release.version, '0.8.0')) {
return callback(new Error('Minimum target version is `0.8.0` or greater. Got: ' + release.version))
throw new Error('Minimum target version is `0.8.0` or greater. Got: ' + release.version)
}
// 0.x.y-pre versions are not published yet and cannot be installed. Bail.
if (release.semver.prerelease[0] === 'pre') {
log.verbose('detected "pre" node version', release.version)
if (gyp.opts.nodedir) {
log.verbose('--nodedir flag was passed; skipping install', gyp.opts.nodedir)
callback()
} else {
callback(new Error('"pre" versions of node cannot be installed, use the --nodedir flag instead'))
if (!gyp.opts.nodedir) {
throw new Error('"pre" versions of node cannot be installed, use the --nodedir flag instead')
}
log.verbose('--nodedir flag was passed; skipping install', gyp.opts.nodedir)
return
}
@ -60,296 +47,225 @@ function install (fs, gyp, argv, callback) {
log.verbose('install', 'installing version: %s', release.versionDir)
// the directory where the dev files will be installed
var devDir = path.resolve(gyp.devDir, release.versionDir)
const devDir = path.resolve(gyp.devDir, release.versionDir)
// If '--ensure' was passed, then don't *always* install the version;
// check if it is already installed, and only install when needed
if (gyp.opts.ensure) {
log.verbose('install', '--ensure was passed, so won\'t reinstall if already installed')
fs.stat(devDir, function (err) {
if (err) {
if (err.code === 'ENOENT') {
log.verbose('install', 'version not already installed, continuing with install', release.version)
go()
} else if (err.code === 'EACCES') {
eaccesFallback(err)
} else {
cb(err)
try {
await fs.promises.stat(devDir)
} catch (err) {
if (err.code === 'ENOENT') {
log.verbose('install', 'version not already installed, continuing with install', release.version)
try {
return await go()
} catch (err) {
return rollback(err)
}
return
} else if (err.code === 'EACCES') {
return eaccesFallback(err)
}
log.verbose('install', 'version is already installed, need to check "installVersion"')
var installVersionFile = path.resolve(devDir, 'installVersion')
fs.readFile(installVersionFile, 'ascii', function (err, ver) {
if (err && err.code !== 'ENOENT') {
return cb(err)
}
var installVersion = parseInt(ver, 10) || 0
log.verbose('got "installVersion"', installVersion)
log.verbose('needs "installVersion"', gyp.package.installVersion)
if (installVersion < gyp.package.installVersion) {
log.verbose('install', 'version is no good; reinstalling')
go()
} else {
log.verbose('install', 'version is good')
cb()
}
})
})
throw err
}
log.verbose('install', 'version is already installed, need to check "installVersion"')
const installVersionFile = path.resolve(devDir, 'installVersion')
let installVersion = 0
try {
const ver = await fs.promises.readFile(installVersionFile, 'ascii')
installVersion = parseInt(ver, 10) || 0
} catch (err) {
if (err.code !== 'ENOENT') {
throw err
}
}
log.verbose('got "installVersion"', installVersion)
log.verbose('needs "installVersion"', gyp.package.installVersion)
if (installVersion < gyp.package.installVersion) {
log.verbose('install', 'version is no good; reinstalling')
try {
return await go()
} catch (err) {
return rollback(err)
}
}
log.verbose('install', 'version is good')
} else {
go()
try {
return await go()
} catch (err) {
return rollback(err)
}
}
function getContentSha (res, callback) {
var shasum = crypto.createHash('sha256')
res.on('data', function (chunk) {
shasum.update(chunk)
}).on('end', function () {
callback(null, shasum.digest('hex'))
})
}
function go () {
async function go () {
log.verbose('ensuring nodedir is created', devDir)
// first create the dir for the node dev files
fs.mkdir(devDir, { recursive: true }, function (err, created) {
if (err) {
if (err.code === 'EACCES') {
eaccesFallback(err)
} else {
cb(err)
}
return
}
try {
const created = await fs.promises.mkdir(devDir, { recursive: true })
if (created) {
log.verbose('created nodedir', created)
}
// now download the node tarball
var tarPath = gyp.opts.tarball
var badDownload = false
var extractCount = 0
var contentShasums = {}
var expectShasums = {}
// checks if a file to be extracted from the tarball is valid.
// only .h header files and the gyp files get extracted
function isValid (path) {
var isValid = valid(path)
if (isValid) {
log.verbose('extracted file from tarball', path)
extractCount++
} else {
// invalid
log.silly('ignoring from tarball', path)
}
return isValid
} catch (err) {
if (err.code === 'EACCES') {
return eaccesFallback(err)
}
// download the tarball and extract!
if (tarPath) {
return tar.extract({
file: tarPath,
strip: 1,
filter: isValid,
cwd: devDir
}).then(afterTarball, cb)
}
throw err
}
// now download the node tarball
const tarPath = gyp.opts.tarball
let extractCount = 0
const contentShasums = {}
const expectShasums = {}
// checks if a file to be extracted from the tarball is valid.
// only .h header files and the gyp files get extracted
function isValid (path) {
const isValid = valid(path)
if (isValid) {
log.verbose('extracted file from tarball', path)
extractCount++
} else {
// invalid
log.silly('ignoring from tarball', path)
}
return isValid
}
// download the tarball and extract!
if (tarPath) {
await tar.extract({
file: tarPath,
strip: 1,
filter: isValid,
cwd: devDir
})
} else {
try {
var req = download(gyp, process.env, release.tarballUrl)
} catch (e) {
return cb(e)
}
const res = await download(gyp, release.tarballUrl)
// something went wrong downloading the tarball?
req.on('error', function (err) {
if (res.status !== 200) {
throw new Error(`${res.status} response downloading ${release.tarballUrl}`)
}
await streamPipeline(
res.body,
// content checksum
new ShaSum((_, checksum) => {
const filename = path.basename(release.tarballUrl).trim()
contentShasums[filename] = checksum
log.verbose('content checksum', filename, checksum)
}),
tar.extract({
strip: 1,
cwd: devDir,
filter: isValid
})
)
} catch (err) {
// something went wrong downloading the tarball?
if (err.code === 'ENOTFOUND') {
return cb(new Error('This is most likely not a problem with node-gyp or the package itself and\n' +
throw new Error('This is most likely not a problem with node-gyp or the package itself and\n' +
'is related to network connectivity. In most cases you are behind a proxy or have bad \n' +
'network settings.'))
'network settings.')
}
badDownload = true
cb(err)
})
throw err
}
}
req.on('close', function () {
if (extractCount === 0) {
cb(new Error('Connection closed while downloading tarball file'))
}
})
// invoked after the tarball has finished being extracted
if (extractCount === 0) {
throw new Error('There was a fatal problem while downloading/extracting the tarball')
}
req.on('response', function (res) {
if (res.statusCode !== 200) {
badDownload = true
cb(new Error(res.statusCode + ' response downloading ' + release.tarballUrl))
return
}
// content checksum
getContentSha(res, function (_, checksum) {
var filename = path.basename(release.tarballUrl).trim()
contentShasums[filename] = checksum
log.verbose('content checksum', filename, checksum)
})
log.verbose('tarball', 'done parsing tarball')
// start unzipping and untaring
res.pipe(tar.extract({
strip: 1,
cwd: devDir,
filter: isValid
}).on('close', afterTarball).on('error', cb))
})
const installVersionPath = path.resolve(devDir, 'installVersion')
await Promise.all([
// need to download node.lib
...(win ? downloadNodeLib() : []),
// write the "installVersion" file
fs.promises.writeFile(installVersionPath, gyp.package.installVersion + '\n'),
// Only download SHASUMS.txt if we downloaded something in need of SHA verification
...(!tarPath || win ? [downloadShasums()] : [])
])
// invoked after the tarball has finished being extracted
function afterTarball () {
if (badDownload) {
return
}
if (extractCount === 0) {
return cb(new Error('There was a fatal problem while downloading/extracting the tarball'))
}
log.verbose('tarball', 'done parsing tarball')
var async = 0
log.verbose('download contents checksum', JSON.stringify(contentShasums))
// check content shasums
for (const k in contentShasums) {
log.verbose('validating download checksum for ' + k, '(%s == %s)', contentShasums[k], expectShasums[k])
if (contentShasums[k] !== expectShasums[k]) {
throw new Error(k + ' local checksum ' + contentShasums[k] + ' not match remote ' + expectShasums[k])
}
}
if (win) {
// need to download node.lib
async++
downloadNodeLib(deref)
}
async function downloadShasums () {
log.verbose('check download content checksum, need to download `SHASUMS256.txt`...')
log.verbose('checksum url', release.shasumsUrl)
// write the "installVersion" file
async++
var installVersionPath = path.resolve(devDir, 'installVersion')
fs.writeFile(installVersionPath, gyp.package.installVersion + '\n', deref)
const res = await download(gyp, release.shasumsUrl)
// Only download SHASUMS.txt if we downloaded something in need of SHA verification
if (!tarPath || win) {
// download SHASUMS.txt
async++
downloadShasums(deref)
}
if (async === 0) {
// no async tasks required
cb()
}
function deref (err) {
if (err) {
return cb(err)
}
async--
if (!async) {
log.verbose('download contents checksum', JSON.stringify(contentShasums))
// check content shasums
for (var k in contentShasums) {
log.verbose('validating download checksum for ' + k, '(%s == %s)', contentShasums[k], expectShasums[k])
if (contentShasums[k] !== expectShasums[k]) {
cb(new Error(k + ' local checksum ' + contentShasums[k] + ' not match remote ' + expectShasums[k]))
return
}
}
cb()
}
}
if (res.status !== 200) {
throw new Error(`${res.status} status code downloading checksum`)
}
function downloadShasums (done) {
log.verbose('check download content checksum, need to download `SHASUMS256.txt`...')
log.verbose('checksum url', release.shasumsUrl)
try {
var req = download(gyp, process.env, release.shasumsUrl)
} catch (e) {
return cb(e)
for (const line of (await res.text()).trim().split('\n')) {
const items = line.trim().split(/\s+/)
if (items.length !== 2) {
return
}
req.on('error', done)
req.on('response', function (res) {
if (res.statusCode !== 200) {
done(new Error(res.statusCode + ' status code downloading checksum'))
return
}
var chunks = []
res.on('data', function (chunk) {
chunks.push(chunk)
})
res.on('end', function () {
var lines = Buffer.concat(chunks).toString().trim().split('\n')
lines.forEach(function (line) {
var items = line.trim().split(/\s+/)
if (items.length !== 2) {
return
}
// 0035d18e2dcf9aad669b1c7c07319e17abfe3762 ./node-v0.11.4.tar.gz
var name = items[1].replace(/^\.\//, '')
expectShasums[name] = items[0]
})
log.verbose('checksum data', JSON.stringify(expectShasums))
done()
})
})
// 0035d18e2dcf9aad669b1c7c07319e17abfe3762 ./node-v0.11.4.tar.gz
const name = items[1].replace(/^\.\//, '')
expectShasums[name] = items[0]
}
function downloadNodeLib (done) {
log.verbose('on Windows; need to download `' + release.name + '.lib`...')
var archs = ['ia32', 'x64', 'arm64']
var async = archs.length
archs.forEach(function (arch) {
var dir = path.resolve(devDir, arch)
var targetLibPath = path.resolve(dir, release.name + '.lib')
var libUrl = release[arch].libUrl
var libPath = release[arch].libPath
var name = arch + ' ' + release.name + '.lib'
log.verbose(name, 'dir', dir)
log.verbose(name, 'url', libUrl)
log.verbose('checksum data', JSON.stringify(expectShasums))
}
fs.mkdir(dir, { recursive: true }, function (err) {
if (err) {
return done(err)
}
log.verbose('streaming', name, 'to:', targetLibPath)
function downloadNodeLib () {
log.verbose('on Windows; need to download `' + release.name + '.lib`...')
const archs = ['ia32', 'x64', 'arm64']
return archs.map(async (arch) => {
const dir = path.resolve(devDir, arch)
const targetLibPath = path.resolve(dir, release.name + '.lib')
const { libUrl, libPath } = release[arch]
const name = `${arch} ${release.name}.lib`
log.verbose(name, 'dir', dir)
log.verbose(name, 'url', libUrl)
try {
var req = download(gyp, process.env, libUrl, cb)
} catch (e) {
return cb(e)
}
await fs.promises.mkdir(dir, { recursive: true })
log.verbose('streaming', name, 'to:', targetLibPath)
req.on('error', done)
req.on('response', function (res) {
if (res.statusCode === 403 || res.statusCode === 404) {
if (arch === 'arm64') {
// Arm64 is a newer platform on Windows and not all node distributions provide it.
log.verbose(`${name} was not found in ${libUrl}`)
} else {
log.warn(`${name} was not found in ${libUrl}`)
}
return
} else if (res.statusCode !== 200) {
done(new Error(res.statusCode + ' status code downloading ' + name))
return
}
const res = await download(gyp, libUrl)
getContentSha(res, function (_, checksum) {
contentShasums[libPath] = checksum
log.verbose('content checksum', libPath, checksum)
})
if (res.status === 403 || res.status === 404) {
if (arch === 'arm64') {
// Arm64 is a newer platform on Windows and not all node distributions provide it.
log.verbose(`${name} was not found in ${libUrl}`)
} else {
log.warn(`${name} was not found in ${libUrl}`)
}
return
} else if (res.status !== 200) {
throw new Error(`${res.status} status code downloading ${name}`)
}
var ws = fs.createWriteStream(targetLibPath)
ws.on('error', cb)
req.pipe(ws)
})
req.on('end', function () { --async || done() })
})
})
} // downloadNodeLib()
}) // mkdir()
return streamPipeline(
res.body,
new ShaSum((_, checksum) => {
contentShasums[libPath] = checksum
log.verbose('content checksum', libPath, checksum)
}),
fs.createWriteStream(targetLibPath)
)
})
} // downloadNodeLib()
} // go()
/**
@ -358,10 +274,17 @@ function install (fs, gyp, argv, callback) {
function valid (file) {
// header files
var extname = path.extname(file)
const extname = path.extname(file)
return extname === '.h' || extname === '.gypi'
}
async function rollback (err) {
log.warn('install', 'got an error, rolling back install')
// roll-back the install if anything went wrong
await util.promisify(gyp.commands.remove)([release.versionDir])
throw err
}
/**
* The EACCES fallback is a workaround for npm's `sudo` behavior, where
* it drops the permissions before invoking any child processes (like
@ -371,14 +294,14 @@ function install (fs, gyp, argv, callback) {
* the compilation will succeed...
*/
function eaccesFallback (err) {
var noretry = '--node_gyp_internal_noretry'
async function eaccesFallback (err) {
const noretry = '--node_gyp_internal_noretry'
if (argv.indexOf(noretry) !== -1) {
return cb(err)
throw err
}
var tmpdir = os.tmpdir()
const tmpdir = os.tmpdir()
gyp.devDir = path.resolve(tmpdir, '.node-gyp')
var userString = ''
let userString = ''
try {
// os.userInfo can fail on some systems, it's not critical here
userString = ` ("${os.userInfo().username}")`
@ -389,59 +312,65 @@ function install (fs, gyp, argv, callback) {
log.verbose('tmpdir == cwd', 'automatically will remove dev files after to save disk space')
gyp.todo.push({ name: 'remove', args: argv })
}
gyp.commands.install([noretry].concat(argv), cb)
return util.promisify(gyp.commands.install)([noretry].concat(argv))
}
}
function download (gyp, env, url) {
class ShaSum extends stream.Transform {
constructor (callback) {
super()
this._callback = callback
this._digester = crypto.createHash('sha256')
}
_transform (chunk, _, callback) {
this._digester.update(chunk)
callback(null, chunk)
}
_flush (callback) {
this._callback(null, this._digester.digest('hex'))
callback()
}
}
async function download (gyp, url) {
log.http('GET', url)
var requestOpts = {
uri: url,
const requestOpts = {
headers: {
'User-Agent': 'node-gyp v' + gyp.version + ' (node ' + process.version + ')',
'User-Agent': `node-gyp v${gyp.version} (node ${process.version})`,
Connection: 'keep-alive'
}
},
proxy: gyp.opts.proxy,
noProxy: gyp.opts.noproxy
}
var cafile = gyp.opts.cafile
const cafile = gyp.opts.cafile
if (cafile) {
requestOpts.ca = readCAFile(cafile)
requestOpts.ca = await readCAFile(cafile)
}
// basic support for a proxy server
var proxyUrl = getProxyFromURI(gyp, env, url)
if (proxyUrl) {
if (/^https?:\/\//i.test(proxyUrl)) {
log.verbose('download', 'using proxy url: "%s"', proxyUrl)
requestOpts.proxy = proxyUrl
} else {
log.warn('download', 'ignoring invalid "proxy" config setting: "%s"', proxyUrl)
}
}
const res = await fetch(url, requestOpts)
log.http(res.status, res.url)
var req = request(requestOpts)
req.on('response', function (res) {
log.http(res.statusCode, url)
})
return req
return res
}
function readCAFile (filename) {
async function readCAFile (filename) {
// The CA file can contain multiple certificates so split on certificate
// boundaries. [\S\s]*? is used to match everything including newlines.
var ca = fs.readFileSync(filename, 'utf8')
var re = /(-----BEGIN CERTIFICATE-----[\S\s]*?-----END CERTIFICATE-----)/g
const ca = await fs.promises.readFile(filename, 'utf8')
const re = /(-----BEGIN CERTIFICATE-----[\S\s]*?-----END CERTIFICATE-----)/g
return ca.match(re)
}
module.exports = function (gyp, argv, callback) {
return install(fs, gyp, argv, callback)
install(fs, gyp, argv).then(callback.bind(undefined, null), callback)
}
module.exports.test = {
download: download,
install: install,
readCAFile: readCAFile
download,
install,
readCAFile
}
module.exports.usage = 'Install node development files for the specified node version.'

View file

@ -1,92 +0,0 @@
'use strict'
// Taken from https://github.com/request/request/blob/212570b/lib/getProxyFromURI.js
const url = require('url')
function formatHostname (hostname) {
// canonicalize the hostname, so that 'oogle.com' won't match 'google.com'
return hostname.replace(/^\.*/, '.').toLowerCase()
}
function parseNoProxyZone (zone) {
zone = zone.trim().toLowerCase()
var zoneParts = zone.split(':', 2)
var zoneHost = formatHostname(zoneParts[0])
var zonePort = zoneParts[1]
var hasPort = zone.indexOf(':') > -1
return { hostname: zoneHost, port: zonePort, hasPort: hasPort }
}
function uriInNoProxy (uri, noProxy) {
var port = uri.port || (uri.protocol === 'https:' ? '443' : '80')
var hostname = formatHostname(uri.hostname)
var noProxyList = noProxy.split(',')
// iterate through the noProxyList until it finds a match.
return noProxyList.map(parseNoProxyZone).some(function (noProxyZone) {
var isMatchedAt = hostname.indexOf(noProxyZone.hostname)
var hostnameMatched = (
isMatchedAt > -1 &&
(isMatchedAt === hostname.length - noProxyZone.hostname.length)
)
if (noProxyZone.hasPort) {
return (port === noProxyZone.port) && hostnameMatched
}
return hostnameMatched
})
}
function getProxyFromURI (gyp, env, uri) {
// If a string URI/URL was given, parse it into a URL object
if (typeof uri === 'string') {
// eslint-disable-next-line
uri = url.parse(uri)
}
// Decide the proper request proxy to use based on the request URI object and the
// environmental variables (NO_PROXY, HTTP_PROXY, etc.)
// respect NO_PROXY environment variables (see: https://lynx.invisible-island.net/lynx2.8.7/breakout/lynx_help/keystrokes/environments.html)
var noProxy = gyp.opts.noproxy || env.NO_PROXY || env.no_proxy || env.npm_config_noproxy || ''
// if the noProxy is a wildcard then return null
if (noProxy === '*') {
return null
}
// if the noProxy is not empty and the uri is found return null
if (noProxy !== '' && uriInNoProxy(uri, noProxy)) {
return null
}
// Check for HTTP or HTTPS Proxy in environment Else default to null
if (uri.protocol === 'http:') {
return gyp.opts.proxy ||
env.HTTP_PROXY ||
env.http_proxy ||
env.npm_config_proxy || null
}
if (uri.protocol === 'https:') {
return gyp.opts.proxy ||
env.HTTPS_PROXY ||
env.https_proxy ||
env.HTTP_PROXY ||
env.http_proxy ||
env.npm_config_proxy || null
}
// if none of that works, return null
// (What uri protocol are you using then?)
return null
}
module.exports = getProxyFromURI

View file

@ -25,9 +25,9 @@
"env-paths": "^2.2.0",
"glob": "^7.1.4",
"graceful-fs": "^4.2.3",
"make-fetch-happen": "^8.0.9",
"nopt": "^5.0.0",
"npmlog": "^4.1.2",
"request": "^2.88.2",
"rimraf": "^3.0.2",
"semver": "^7.3.2",
"tar": "^6.0.2",

View file

@ -1,8 +1,9 @@
'use strict'
const test = require('tap').test
const { test } = require('tap')
const fs = require('fs')
const path = require('path')
const util = require('util')
const http = require('http')
const https = require('https')
const install = require('../lib/install')
@ -14,191 +15,142 @@ const log = require('npmlog')
log.level = 'warn'
test('download over http', function (t) {
test('download over http', async (t) => {
t.plan(2)
var server = http.createServer(function (req, res) {
t.strictEqual(req.headers['user-agent'],
'node-gyp v42 (node ' + process.version + ')')
const server = http.createServer((req, res) => {
t.strictEqual(req.headers['user-agent'], `node-gyp v42 (node ${process.version})`)
res.end('ok')
server.close()
})
var host = 'localhost'
server.listen(0, host, function () {
var port = this.address().port
var gyp = {
opts: {},
version: '42'
}
var url = 'http://' + host + ':' + port
var req = install.test.download(gyp, {}, url)
req.on('response', function (res) {
var body = ''
res.setEncoding('utf8')
res.on('data', function (data) {
body += data
})
res.on('end', function () {
t.strictEqual(body, 'ok')
})
})
})
t.tearDown(() => new Promise((resolve) => server.close(resolve)))
const host = 'localhost'
await new Promise((resolve) => server.listen(0, host, resolve))
const { port } = server.address()
const gyp = {
opts: {},
version: '42'
}
const url = `http://${host}:${port}`
const res = await install.test.download(gyp, url)
t.strictEqual(await res.text(), 'ok')
})
test('download over https with custom ca', function (t) {
test('download over https with custom ca', async (t) => {
t.plan(3)
var cert = fs.readFileSync(path.join(__dirname, 'fixtures/server.crt'), 'utf8')
var key = fs.readFileSync(path.join(__dirname, 'fixtures/server.key'), 'utf8')
const cafile = path.join(__dirname, '/fixtures/ca.crt')
const [cert, key, ca] = await Promise.all([
fs.promises.readFile(path.join(__dirname, 'fixtures/server.crt'), 'utf8'),
fs.promises.readFile(path.join(__dirname, 'fixtures/server.key'), 'utf8'),
install.test.readCAFile(cafile)
])
var cafile = path.join(__dirname, '/fixtures/ca.crt')
var ca = install.test.readCAFile(cafile)
t.strictEqual(ca.length, 1)
var options = { ca: ca, cert: cert, key: key }
var server = https.createServer(options, function (req, res) {
t.strictEqual(req.headers['user-agent'],
'node-gyp v42 (node ' + process.version + ')')
const options = { ca: ca, cert: cert, key: key }
const server = https.createServer(options, (req, res) => {
t.strictEqual(req.headers['user-agent'], `node-gyp v42 (node ${process.version})`)
res.end('ok')
server.close()
})
server.on('clientError', function (err) {
throw err
})
t.tearDown(() => new Promise((resolve) => server.close(resolve)))
var host = 'localhost'
server.listen(8000, host, function () {
var port = this.address().port
var gyp = {
opts: { cafile: cafile },
version: '42'
}
var url = 'https://' + host + ':' + port
var req = install.test.download(gyp, {}, url)
req.on('response', function (res) {
var body = ''
res.setEncoding('utf8')
res.on('data', function (data) {
body += data
})
res.on('end', function () {
t.strictEqual(body, 'ok')
})
})
})
server.on('clientError', (err) => { throw err })
const host = 'localhost'
await new Promise((resolve) => server.listen(0, host, resolve))
const { port } = server.address()
const gyp = {
opts: { cafile },
version: '42'
}
const url = `https://${host}:${port}`
const res = await install.test.download(gyp, url)
t.strictEqual(await res.text(), 'ok')
})
test('download over http with proxy', function (t) {
test('download over http with proxy', async (t) => {
t.plan(2)
var server = http.createServer(function (req, res) {
t.strictEqual(req.headers['user-agent'],
'node-gyp v42 (node ' + process.version + ')')
const server = http.createServer((_, res) => {
res.end('ok')
pserver.close(function () {
server.close()
})
})
var pserver = http.createServer(function (req, res) {
t.strictEqual(req.headers['user-agent'],
'node-gyp v42 (node ' + process.version + ')')
const pserver = http.createServer((req, res) => {
t.strictEqual(req.headers['user-agent'], `node-gyp v42 (node ${process.version})`)
res.end('proxy ok')
server.close(function () {
pserver.close()
})
})
var host = 'localhost'
server.listen(0, host, function () {
var port = this.address().port
pserver.listen(port + 1, host, function () {
var gyp = {
opts: {
proxy: 'http://' + host + ':' + (port + 1)
},
version: '42'
}
var url = 'http://' + host + ':' + port
var req = install.test.download(gyp, {}, url)
req.on('response', function (res) {
var body = ''
res.setEncoding('utf8')
res.on('data', function (data) {
body += data
})
res.on('end', function () {
t.strictEqual(body, 'proxy ok')
})
})
})
})
t.tearDown(() => Promise.all([
new Promise((resolve) => server.close(resolve)),
new Promise((resolve) => pserver.close(resolve))
]))
const host = 'localhost'
await new Promise((resolve) => server.listen(0, host, resolve))
const { port } = server.address()
await new Promise((resolve) => pserver.listen(port + 1, host, resolve))
const gyp = {
opts: {
proxy: `http://${host}:${port + 1}`,
noproxy: 'bad'
},
version: '42'
}
const url = `http://${host}:${port}`
const res = await install.test.download(gyp, url)
t.strictEqual(await res.text(), 'proxy ok')
})
test('download over http with noproxy', function (t) {
test('download over http with noproxy', async (t) => {
t.plan(2)
var server = http.createServer(function (req, res) {
t.strictEqual(req.headers['user-agent'],
'node-gyp v42 (node ' + process.version + ')')
const server = http.createServer((req, res) => {
t.strictEqual(req.headers['user-agent'], `node-gyp v42 (node ${process.version})`)
res.end('ok')
pserver.close(function () {
server.close()
})
})
var pserver = http.createServer(function (req, res) {
t.strictEqual(req.headers['user-agent'],
'node-gyp v42 (node ' + process.version + ')')
const pserver = http.createServer((_, res) => {
res.end('proxy ok')
server.close(function () {
pserver.close()
})
})
var host = 'localhost'
server.listen(0, host, function () {
var port = this.address().port
pserver.listen(port + 1, host, function () {
var gyp = {
opts: {
proxy: 'http://' + host + ':' + (port + 1),
noproxy: 'localhost'
},
version: '42'
}
var url = 'http://' + host + ':' + port
var req = install.test.download(gyp, {}, url)
req.on('response', function (res) {
var body = ''
res.setEncoding('utf8')
res.on('data', function (data) {
body += data
})
res.on('end', function () {
t.strictEqual(body, 'ok')
})
})
})
})
t.tearDown(() => Promise.all([
new Promise((resolve) => server.close(resolve)),
new Promise((resolve) => pserver.close(resolve))
]))
const host = 'localhost'
await new Promise((resolve) => server.listen(0, host, resolve))
const { port } = server.address()
await new Promise((resolve) => pserver.listen(port + 1, host, resolve))
const gyp = {
opts: {
proxy: `http://${host}:${port + 1}`,
noproxy: host
},
version: '42'
}
const url = `http://${host}:${port}`
const res = await install.test.download(gyp, url)
t.strictEqual(await res.text(), 'ok')
})
test('download with missing cafile', function (t) {
test('download with missing cafile', async (t) => {
t.plan(1)
var gyp = {
const gyp = {
opts: { cafile: 'no.such.file' }
}
try {
install.test.download(gyp, {}, 'http://bad/')
await install.test.download(gyp, {}, 'http://bad/')
} catch (e) {
t.ok(/no.such.file/.test(e.message))
}
})
test('check certificate splitting', function (t) {
var cas = install.test.readCAFile(path.join(__dirname, 'fixtures/ca-bundle.crt'))
test('check certificate splitting', async (t) => {
const cas = await install.test.readCAFile(path.join(__dirname, 'fixtures/ca-bundle.crt'))
t.plan(2)
t.strictEqual(cas.length, 2)
t.notStrictEqual(cas[0], cas[1])
@ -206,7 +158,7 @@ test('check certificate splitting', function (t) {
// only run this test if we are running a version of Node with predictable version path behavior
test('download headers (actual)', function (t) {
test('download headers (actual)', async (t) => {
if (process.env.FAST_TEST ||
process.release.name !== 'node' ||
semver.prerelease(process.version) !== null ||
@ -214,55 +166,42 @@ test('download headers (actual)', function (t) {
return t.skip('Skipping actual download of headers due to test environment configuration')
}
t.plan(17)
t.plan(12)
const expectedDir = path.join(devDir, process.version.replace(/^v/, ''))
rimraf(expectedDir, (err) => {
t.ifError(err)
await util.promisify(rimraf)(expectedDir)
const prog = gyp()
prog.parseArgv([])
prog.devDir = devDir
log.level = 'warn'
install(prog, [], (err) => {
t.ifError(err)
const prog = gyp()
prog.parseArgv([])
prog.devDir = devDir
log.level = 'warn'
await util.promisify(install)(prog, [])
fs.readFile(path.join(expectedDir, 'installVersion'), 'utf8', (err, data) => {
t.ifError(err)
t.strictEqual(data, '9\n', 'correct installVersion')
})
const data = await fs.promises.readFile(path.join(expectedDir, 'installVersion'), 'utf8')
t.strictEqual(data, '9\n', 'correct installVersion')
fs.readdir(path.join(expectedDir, 'include/node'), (err, list) => {
t.ifError(err)
const list = await fs.promises.readdir(path.join(expectedDir, 'include/node'))
t.ok(list.includes('common.gypi'))
t.ok(list.includes('config.gypi'))
t.ok(list.includes('node.h'))
t.ok(list.includes('node_version.h'))
t.ok(list.includes('openssl'))
t.ok(list.includes('uv'))
t.ok(list.includes('uv.h'))
t.ok(list.includes('v8-platform.h'))
t.ok(list.includes('v8.h'))
t.ok(list.includes('zlib.h'))
t.ok(list.includes('common.gypi'))
t.ok(list.includes('config.gypi'))
t.ok(list.includes('node.h'))
t.ok(list.includes('node_version.h'))
t.ok(list.includes('openssl'))
t.ok(list.includes('uv'))
t.ok(list.includes('uv.h'))
t.ok(list.includes('v8-platform.h'))
t.ok(list.includes('v8.h'))
t.ok(list.includes('zlib.h'))
})
const lines = (await fs.promises.readFile(path.join(expectedDir, 'include/node/node_version.h'), 'utf8')).split('\n')
fs.readFile(path.join(expectedDir, 'include/node/node_version.h'), 'utf8', (err, contents) => {
t.ifError(err)
// extract the 3 version parts from the defines to build a valid version string and
// and check them against our current env version
const version = ['major', 'minor', 'patch'].reduce((version, type) => {
const re = new RegExp(`^#define\\sNODE_${type.toUpperCase()}_VERSION`)
const line = lines.find((l) => re.test(l))
const i = line ? parseInt(line.replace(/^[^0-9]+([0-9]+).*$/, '$1'), 10) : 'ERROR'
return `${version}${type !== 'major' ? '.' : 'v'}${i}`
}, '')
const lines = contents.split('\n')
// extract the 3 version parts from the defines to build a valid version string and
// and check them against our current env version
const version = ['major', 'minor', 'patch'].reduce((version, type) => {
const re = new RegExp(`^#define\\sNODE_${type.toUpperCase()}_VERSION`)
const line = lines.find((l) => re.test(l))
const i = line ? parseInt(line.replace(/^[^0-9]+([0-9]+).*$/, '$1'), 10) : 'ERROR'
return `${version}${type !== 'major' ? '.' : 'v'}${i}`
}, '')
t.strictEqual(version, process.version)
})
})
})
t.strictEqual(version, process.version)
})

View file

@ -1,38 +1,46 @@
'use strict'
const test = require('tap').test
const install = require('../lib/install').test.install
const { test } = require('tap')
const { test: { install } } = require('../lib/install')
const log = require('npmlog')
require('npmlog').level = 'error' // we expect a warning
log.level = 'error' // we expect a warning
test('EACCES retry once', function (t) {
test('EACCES retry once', async (t) => {
t.plan(3)
var fs = {}
fs.stat = function (path, cb) {
var err = new Error()
err.code = 'EACCES'
cb(err)
t.ok(true)
const fs = {
promises: {
stat (_) {
const err = new Error()
err.code = 'EACCES'
t.ok(true)
throw err
}
}
}
var gyp = {}
gyp.devDir = __dirname
gyp.opts = {}
gyp.opts.ensure = true
gyp.commands = {}
gyp.commands.install = function (argv, cb) {
install(fs, gyp, argv, cb)
}
gyp.commands.remove = function (argv, cb) {
cb()
const Gyp = {
devDir: __dirname,
opts: {
ensure: true
},
commands: {
install (argv, cb) {
install(fs, Gyp, argv).then(cb, cb)
},
remove (_, cb) {
cb()
}
}
}
gyp.commands.install([], function (err) {
try {
await install(fs, Gyp, [])
} catch (err) {
t.ok(true)
if (/"pre" versions of node cannot be installed/.test(err.message)) {
t.ok(true)
t.ok(true)
}
})
}
})