Files
cypress/cli/lib/util.js
Zach Bloomquist c1a345dce2 Improved proxy support (#3531)
* https-proxy: unused file

* server: wrap all https requests that use a proxy

* server: use request lib in ensureUrl if proxy is in use. this makes runs tab work behind a proxy

* electron: pass --proxy-server to app itself, so the embedded github login page works

* cli: first attempt at env vars from windows registry

* cli: api cleanup

* cli: lint

* cli: fix crash on no proxy, add tests

* add desktop-gui watch to terminals.json

* cli: pass along --proxy-source

* electron: pass --proxy-bypass-list too

* server: whitelist proxy* args

* cli: better wording

* desktop-gui: display proxy settings

* extension: force proxy [wip]

* extension: finally, i am victorious over coffeescript

* extension: add -loopback to bypasslist

* extension: revert changes

Revert "extension: force proxy [wip]"

This reverts commit 3ab6ba42a763f25ee65f12eb8b79eb597efc9b11.

* desktop-gui: skip proxysettings if there aren't any

* https-proxy, server: proxy directConnections using https-proxy-agent

* https-agent: pool httpsAgents

* https-proxy: work when they're not on a proxy

* https-proxy: ci - use agent 1.0

* https-proxy: tests

* desktop-gui: hide proxy settings when not using proxy

* https-proxy: pass req through to https-proxy-agent callback

* cli: use get-windows-proxy

* desktop-gui: always show proxy settings

* server: use get-windows-proxy

* electron, server: supply electron proxy config when window launched

* server: fix

* https-proxy: cleanup

* server: clean up ensureUrl

* https-proxy: cleanup

* cli: fix

* cli: fix destructuring

* server: enable ForeverAgent to pool HTTPS/HTTP connections

#3192

* server: updating snapshot

* https-proxy: don't crash, do error if proxy unreachable

* https-proxy:

* get-windows-proxy@1.0.0

* https-proxy: use proxy-from-env to decide on a proxy for a url

* server: fallback to HTTP_PROXY globally if HTTPS_PROXY not set

* server: proxy args test

* cli: add proxy tests

* cli: add test that loadSystemProxySettings is called during download

* cli, server: account for the fact that CI has some proxy vars set

* https-proxy: ""

* cli, https-proxy, server: ""

* desktop-gui: update settings gui

* desktop-gui: cypress tests for proxy settings

* server: strict undefined check

* cli, server: move get-windows-proxy to scope, optionalDeps

* server, cli: use new and improved get-windows-proxy

* cli, server: 1.5.0

* server: re-check for proxy since cli may have failed to load the lib

* server, cli: 1.5.1

* server: NO_PROXY=localhost by default, clean up

* https-proxy: disable Nagle's on proxy sockets

\#3192

* https-proxy: use setNoDelay on upstream, cache https agent

* https-proxy: test basic auth

* https-proxy: add todo: remove this

* server: add custom HTTP(s) Agent implementation w keepalive, tunneling

* server: typescript for agent

* add ts to zunder

* server: more ts

* ts: add missing Agent type declaration

* server: create CombinedAgent

* server: use agent in more places

* ts: more declarations

* server: make script work even if debug port not supplied

* server: begin some testing

* server, ts: agent, tests

* server: test

* server: agent works with websockets now

* server: update snapshot

* server: work out some more bugs with websockets

* server: more websockets

* server: add net_profiler

* https-proxy: fix dangling socket on direct connection

* server: fix potential 'headers have already been sent'

* https-proxy: nab another dangler

* server: update test to expect agent

* https-proxy: fix failing test

* desktop-gui: change on-link

* server: add tests for empty response case

* server: tests

* server: send keep-alive with requests

* server: make net profiler hook on socket.connect

* server: only hook profiler once

* server: update tests, add keep-alive test

* server: only regen headers if needed

* server: move http_overrides into CombinedAgent, make it proxy-proof

for #112

* server: update snapshot

* server: undo

* server: avoid circular dependency

* https-proxy, server: use our Agent instead of https-proxy-agent

* server: add dependency back

* cli: actually use proxy for download

* server, launcher, ts: typescript

* Revert "server, launcher, ts: typescript"

This reverts commit d3f8b8bbb6.

* Revert "Revert "server, launcher, ts: typescript""

This reverts commit 818dfdfd00.

* ts, server: respond to PR

* server, ts: types

* ts: really fix types

* https-proxy, server: export CA from https-proxy

* agent, server, https-proxy: move agent to own package

* agent => networking, move connect into networking

* fix tests

* fix test

* networking: respond to PR changes, add more unit tests

* rename ctx

* networking, ts: add more tests

* server: add ensureUrl tests

* https-proxy: remove https-proxy-agent

* server: use CombinedAgent for API

* server: updates

* add proxy performance tests

* add perf tests to workflow

* circle

* run perf tests with --no-sandbox

* networking, ts: ch-ch-ch-ch-changes

* server, networking: pr changes

* run networking tests in circle

* server: fix performance test

* https-proxy: test that sockets are being closed

* https-proxy: write, not emit

* networking: fix test

* networking: bubble err in connect

* networking: style

* networking: clean p connect error handling

* networking => network

* server: make perf tests really work

* server: really report

* server: use args from browser

* server: use AI to determine max run time

* server: load electron only when needed


Co-authored-by: Brian Mann <brian@cypress.io>
2019-03-31 23:39:10 -04:00

274 lines
6.1 KiB
JavaScript

const _ = require('lodash')
const R = require('ramda')
const os = require('os')
const tty = require('tty')
const path = require('path')
const isCi = require('is-ci')
const execa = require('execa')
const getos = require('getos')
const chalk = require('chalk')
const Promise = require('bluebird')
const cachedir = require('cachedir')
const getWindowsProxy = require('@cypress/get-windows-proxy')
const executable = require('executable')
const supportsColor = require('supports-color')
const isInstalledGlobally = require('is-installed-globally')
const pkg = require(path.join(__dirname, '..', 'package.json'))
const logger = require('./logger')
const debug = require('debug')('cypress:cli')
const getosAsync = Promise.promisify(getos)
const stringify = (val) => {
return _.isObject(val) ? JSON.stringify(val) : val
}
function normalizeModuleOptions (options = {}) {
return _.mapValues(options, stringify)
}
function stdoutLineMatches (expectedLine, stdout) {
const lines = stdout.split('\n').map(R.trim)
const lineMatches = R.equals(expectedLine)
return lines.some(lineMatches)
}
/**
* Prints NODE_OPTIONS using debug() module, but only
* if DEBUG=cypress... is set
*/
function printNodeOptions (log = debug) {
if (!log.enabled) {
return
}
if (process.env.NODE_OPTIONS) {
log('NODE_OPTIONS=%s', process.env.NODE_OPTIONS)
} else {
log('NODE_OPTIONS is not set')
}
}
const util = {
normalizeModuleOptions,
printNodeOptions,
isCi () {
return isCi
},
getEnvOverrides () {
return _
.chain({})
.extend(util.getEnvColors())
.extend(util.getForceTty())
.omitBy(_.isUndefined) // remove undefined values
.mapValues((value) => { // stringify to 1 or 0
return value ? '1' : '0'
})
.value()
},
getForceTty () {
return {
FORCE_STDIN_TTY: util.isTty(process.stdin.fd),
FORCE_STDOUT_TTY: util.isTty(process.stdout.fd),
FORCE_STDERR_TTY: util.isTty(process.stderr.fd),
}
},
getEnvColors () {
const sc = util.supportsColor()
return {
FORCE_COLOR: sc,
DEBUG_COLORS: sc,
MOCHA_COLORS: sc ? true : undefined,
}
},
_getWindowsProxy () {
return getWindowsProxy()
},
loadSystemProxySettings () {
// load user's OS-specific proxy settings in to environment vars
if (!_.isUndefined(process.env.HTTP_PROXY)) {
// user has set proxy explicitly in environment vars, don't mess with it
return
}
if (os.platform() === 'win32') {
const proxy = this._getWindowsProxy()
if (proxy) {
// environment variables are the only way to make request lib use NO_PROXY
process.env.HTTP_PROXY = process.env.HTTPS_PROXY = proxy.httpProxy
process.env.NO_PROXY = process.env.NO_PROXY || proxy.noProxy
}
return 'win32'
}
},
isTty (fd) {
return tty.isatty(fd)
},
supportsColor () {
// if we've been explictly told not to support
// color then turn this off
if (process.env.NO_COLOR) {
return false
}
// https://github.com/cypress-io/cypress/issues/1747
// always return true in CI providers
if (process.env.CI) {
return true
}
// ensure that both stdout and stderr support color
return Boolean(supportsColor.stdout) && Boolean(supportsColor.stderr)
},
cwd () {
return process.cwd()
},
pkgVersion () {
return pkg.version
},
exit (code) {
process.exit(code)
},
logErrorExit1 (err) {
logger.error(err.message)
process.exit(1)
},
titleize (...args) {
// prepend first arg with space
// and pad so that all messages line up
args[0] = _.padEnd(` ${args[0]}`, 24)
// get rid of any falsy values
args = _.compact(args)
return chalk.blue(...args)
},
calculateEta (percent, elapsed) {
// returns the number of seconds remaining
// if we're at 100 already just return 0
if (percent === 100) {
return 0
}
// take the percentage and divide by one
// and multiple that against elapsed
// subtracting what's already elapsed
return elapsed * (1 / (percent / 100)) - elapsed
},
secsRemaining (eta) {
// calculate the seconds reminaing with no decimal places
return (_.isFinite(eta) ? (eta / 1000) : 0).toFixed(0)
},
setTaskTitle (task, title, renderer) {
// only update the renderer title when not running in CI
if (renderer === 'default' && task.title !== title) {
task.title = title
}
},
isInstalledGlobally () {
return isInstalledGlobally
},
isSemver (str) {
return /^(\d+\.)?(\d+\.)?(\*|\d+)$/.test(str)
},
isExecutableAsync (filePath) {
return Promise.resolve(executable(filePath))
},
getOsVersionAsync () {
return Promise.try(() => {
if (os.platform() === 'linux') {
return getosAsync()
.then((osInfo) => {
return [osInfo.dist, osInfo.release].join(' - ')
})
.catch(() => {
return os.release()
})
}
return os.release()
})
},
// attention:
// when passing relative path to NPM post install hook, the current working
// directory is set to the `node_modules/cypress` folder
// the user is probably passing relative path with respect to root package folder
formAbsolutePath (filename) {
if (path.isAbsolute(filename)) {
return filename
}
return path.join(process.cwd(), '..', '..', filename)
},
getEnv (varName) {
const envVar = process.env[varName]
const configVar = process.env[`npm_config_${varName}`]
const packageConfigVar = process.env[`npm_package_config_${varName}`]
if (envVar) {
debug(`Using ${varName} from environment variable`)
return envVar
}
if (configVar) {
debug(`Using ${varName} from npm config`)
return configVar
}
if (packageConfigVar) {
debug(`Using ${varName} from package.json config`)
return packageConfigVar
}
return undefined
},
getCacheDir () {
return cachedir('Cypress')
},
isPostInstall () {
return process.env.npm_lifecycle_event === 'postinstall'
},
exec: execa,
stdoutLineMatches,
}
module.exports = util