mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-08 00:00:04 -06:00
763 lines
21 KiB
JavaScript
763 lines
21 KiB
JavaScript
const _ = require('lodash')
|
|
const R = require('ramda')
|
|
const path = require('path')
|
|
const Promise = require('bluebird')
|
|
const deepDiff = require('return-deep-diff')
|
|
|
|
const errors = require('./errors')
|
|
const scaffold = require('./scaffold')
|
|
const fs = require('./util/fs')
|
|
const keys = require('./util/keys')
|
|
const origin = require('./util/origin')
|
|
const coerce = require('./util/coerce')
|
|
const settings = require('./util/settings')
|
|
const debug = require('debug')('cypress:server:config')
|
|
const pathHelpers = require('./util/path_helpers')
|
|
const findSystemNode = require('./util/find_system_node')
|
|
|
|
const { options, breakingOptions } = require('./config_options')
|
|
|
|
const CYPRESS_ENV_PREFIX = 'CYPRESS_'
|
|
const CYPRESS_ENV_PREFIX_LENGTH = 'CYPRESS_'.length
|
|
const CYPRESS_RESERVED_ENV_VARS = [
|
|
'CYPRESS_INTERNAL_ENV',
|
|
]
|
|
const CYPRESS_SPECIAL_ENV_VARS = [
|
|
'CI_KEY',
|
|
'RECORD_KEY',
|
|
]
|
|
|
|
const dashesOrUnderscoresRe = /^(_-)+/
|
|
|
|
// takes an array and creates an index object of [keyKey]: [valueKey]
|
|
const createIndex = (arr, keyKey, valueKey) => {
|
|
return _.reduce(arr, (memo, item) => {
|
|
if (item[valueKey] !== undefined) {
|
|
memo[item[keyKey]] = item[valueKey]
|
|
}
|
|
|
|
return memo
|
|
}, {})
|
|
}
|
|
|
|
const publicConfigKeys = _(options).reject({ isInternal: true }).map('name').value()
|
|
const breakingKeys = _.map(breakingOptions, 'name')
|
|
const folders = _(options).filter({ isFolder: true }).map('name').value()
|
|
const validationRules = createIndex(options, 'name', 'validation')
|
|
const defaultValues = createIndex(options, 'name', 'defaultValue')
|
|
|
|
const isCypressEnvLike = (key) => {
|
|
return _.chain(key)
|
|
.invoke('toUpperCase')
|
|
.startsWith(CYPRESS_ENV_PREFIX)
|
|
.value() &&
|
|
!_.includes(CYPRESS_RESERVED_ENV_VARS, key)
|
|
}
|
|
|
|
const removeEnvPrefix = (key) => {
|
|
return key.slice(CYPRESS_ENV_PREFIX_LENGTH)
|
|
}
|
|
|
|
const convertRelativeToAbsolutePaths = (projectRoot, obj, defaults = {}) => {
|
|
return _.reduce(folders, (memo, folder) => {
|
|
const val = obj[folder]
|
|
|
|
if ((val != null) && (val !== false)) {
|
|
memo[folder] = path.resolve(projectRoot, val)
|
|
}
|
|
|
|
return memo
|
|
}
|
|
, {})
|
|
}
|
|
|
|
const validateNoBreakingConfig = (cfg) => {
|
|
return _.each(breakingOptions, ({ name, errorKey, newName, isWarning }) => {
|
|
if (_.has(cfg, name)) {
|
|
if (isWarning) {
|
|
return errors.warning(errorKey, name, newName)
|
|
}
|
|
|
|
return errors.throw(errorKey, name, newName)
|
|
}
|
|
})
|
|
}
|
|
|
|
const validate = (cfg, onErr) => {
|
|
return _.each(cfg, (value, key) => {
|
|
const validationFn = validationRules[key]
|
|
|
|
// does this key have a validation rule?
|
|
if (validationFn) {
|
|
// and is the value different from the default?
|
|
if (value !== defaultValues[key]) {
|
|
const result = validationFn(key, value)
|
|
|
|
if (result !== true) {
|
|
return onErr(result)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
const validateFile = (file) => {
|
|
return (settings) => {
|
|
return validate(settings, (errMsg) => {
|
|
return errors.throw('SETTINGS_VALIDATION_ERROR', file, errMsg)
|
|
})
|
|
}
|
|
}
|
|
|
|
const hideSpecialVals = function (val, key) {
|
|
if (_.includes(CYPRESS_SPECIAL_ENV_VARS, key)) {
|
|
return keys.hide(val)
|
|
}
|
|
|
|
return val
|
|
}
|
|
|
|
// an object with a few utility methods
|
|
// for easy stubbing from unit tests
|
|
const utils = {
|
|
resolveModule (name) {
|
|
return require.resolve(name)
|
|
},
|
|
|
|
// tries to find support or plugins file
|
|
// returns:
|
|
// false - if the file should not be set
|
|
// string - found filename
|
|
// null - if there is an error finding the file
|
|
discoverModuleFile (options) {
|
|
debug('discover module file %o', options)
|
|
const { filename, isDefault } = options
|
|
|
|
if (!isDefault) {
|
|
// they have it explicitly set, so it should be there
|
|
return fs.pathExists(filename)
|
|
.then((found) => {
|
|
if (found) {
|
|
debug('file exists, assuming it will load')
|
|
|
|
return filename
|
|
}
|
|
|
|
debug('could not find %o', { filename })
|
|
|
|
return null
|
|
})
|
|
}
|
|
|
|
// support or plugins file doesn't exist on disk?
|
|
debug(`support file is default, check if ${path.dirname(filename)} exists`)
|
|
|
|
return fs.pathExists(filename)
|
|
.then((found) => {
|
|
if (found) {
|
|
debug('is there index.ts in the support or plugins folder %s?', filename)
|
|
const tsFilename = path.join(filename, 'index.ts')
|
|
|
|
return fs.pathExists(tsFilename)
|
|
.then((foundTsFile) => {
|
|
if (foundTsFile) {
|
|
debug('found index TS file %s', tsFilename)
|
|
|
|
return tsFilename
|
|
}
|
|
|
|
// if the directory exists, set it to false so it's ignored
|
|
debug('setting support or plugins file to false')
|
|
|
|
return false
|
|
})
|
|
}
|
|
|
|
debug('folder does not exist, set to default index.js')
|
|
|
|
// otherwise, set it up to be scaffolded later
|
|
return path.join(filename, 'index.js')
|
|
})
|
|
},
|
|
}
|
|
|
|
module.exports = {
|
|
utils,
|
|
|
|
getConfigKeys () {
|
|
return publicConfigKeys
|
|
},
|
|
|
|
isValidCypressInternalEnvValue (value) {
|
|
// names of config environments, see "config/app.yml"
|
|
const names = ['development', 'test', 'staging', 'production']
|
|
|
|
return _.includes(names, value)
|
|
},
|
|
|
|
allowed (obj = {}) {
|
|
const propertyNames = publicConfigKeys.concat(breakingKeys)
|
|
|
|
return _.pick(obj, propertyNames)
|
|
},
|
|
|
|
get (projectRoot, options = {}) {
|
|
return Promise.all([
|
|
settings.read(projectRoot, options).then(validateFile('cypress.json')),
|
|
settings.readEnv(projectRoot).then(validateFile('cypress.env.json')),
|
|
])
|
|
.spread((settings, envFile) => {
|
|
return this.set({
|
|
projectName: this.getNameFromRoot(projectRoot),
|
|
projectRoot,
|
|
config: settings,
|
|
envFile,
|
|
options,
|
|
})
|
|
})
|
|
},
|
|
|
|
set (obj = {}) {
|
|
debug('setting config object')
|
|
let { projectRoot, projectName, config, envFile, options } = obj
|
|
|
|
// just force config to be an object
|
|
// so we dont have to do as much
|
|
// work in our tests
|
|
if (config == null) {
|
|
config = {}
|
|
}
|
|
|
|
debug('config is %o', config)
|
|
|
|
// flatten the object's properties
|
|
// into the master config object
|
|
config.envFile = envFile
|
|
config.projectRoot = projectRoot
|
|
config.projectName = projectName
|
|
|
|
return this.mergeDefaults(config, options)
|
|
},
|
|
|
|
mergeDefaults (config = {}, options = {}) {
|
|
const resolved = {}
|
|
|
|
_.extend(config, _.pick(options, 'configFile', 'morgan', 'isTextTerminal', 'socketId', 'report', 'browsers'))
|
|
debug('merged config with options, got %o', config)
|
|
|
|
_
|
|
.chain(this.allowed(options))
|
|
.omit('env')
|
|
.omit('browsers')
|
|
.each((val, key) => {
|
|
resolved[key] = 'cli'
|
|
config[key] = val
|
|
}).value()
|
|
|
|
let url = config.baseUrl
|
|
|
|
if (url) {
|
|
// replace multiple slashes at the end of string to single slash
|
|
// so http://localhost/// will be http://localhost/
|
|
// https://regexr.com/48rvt
|
|
config.baseUrl = url.replace(/\/\/+$/, '/')
|
|
}
|
|
|
|
_.defaults(config, defaultValues)
|
|
|
|
// split out our own app wide env from user env variables
|
|
// and delete envFile
|
|
config.env = this.parseEnv(config, options.env, resolved)
|
|
|
|
config.cypressEnv = process.env['CYPRESS_INTERNAL_ENV']
|
|
debug('using CYPRESS_INTERNAL_ENV %s', config.cypressEnv)
|
|
if (!this.isValidCypressInternalEnvValue(config.cypressEnv)) {
|
|
errors.throw('INVALID_CYPRESS_INTERNAL_ENV', config.cypressEnv)
|
|
}
|
|
|
|
delete config.envFile
|
|
|
|
// when headless
|
|
if (config.isTextTerminal && !process.env.CYPRESS_INTERNAL_FORCE_FILEWATCH) {
|
|
// dont ever watch for file changes
|
|
config.watchForFileChanges = false
|
|
|
|
// and forcibly reset numTestsKeptInMemory
|
|
// to zero
|
|
config.numTestsKeptInMemory = 0
|
|
}
|
|
|
|
config = this.setResolvedConfigValues(config, defaultValues, resolved)
|
|
|
|
if (config.port) {
|
|
config = this.setUrls(config)
|
|
}
|
|
|
|
config = this.setAbsolutePaths(config, defaultValues)
|
|
|
|
config = this.setParentTestsPaths(config)
|
|
|
|
// validate config again here so that we catch
|
|
// configuration errors coming from the CLI overrides
|
|
// or env var overrides
|
|
validate(config, (errMsg) => {
|
|
return errors.throw('CONFIG_VALIDATION_ERROR', errMsg)
|
|
})
|
|
|
|
validateNoBreakingConfig(config)
|
|
|
|
return this.setSupportFileAndFolder(config)
|
|
.then(this.setPluginsFile)
|
|
.then(this.setScaffoldPaths)
|
|
.then(_.partialRight(this.setNodeBinary, options.onWarning))
|
|
},
|
|
|
|
setResolvedConfigValues (config, defaults, resolved) {
|
|
const obj = _.clone(config)
|
|
|
|
obj.resolved = this.resolveConfigValues(config, defaults, resolved)
|
|
debug('resolved config is %o', obj.resolved.browsers)
|
|
|
|
return obj
|
|
},
|
|
|
|
// Given an object "resolvedObj" and a list of overrides in "obj"
|
|
// marks all properties from "obj" inside "resolvedObj" using
|
|
// {value: obj.val, from: "plugin"}
|
|
setPluginResolvedOn (resolvedObj, obj) {
|
|
return _.each(obj, (val, key) => {
|
|
if (_.isObject(val) && !_.isArray(val) && resolvedObj[key]) {
|
|
// recurse setting overrides
|
|
// inside of this nested objected
|
|
return this.setPluginResolvedOn(resolvedObj[key], val)
|
|
}
|
|
|
|
resolvedObj[key] = {
|
|
value: val,
|
|
from: 'plugin',
|
|
}
|
|
})
|
|
},
|
|
|
|
updateWithPluginValues (cfg, overrides) {
|
|
if (!overrides) {
|
|
overrides = {}
|
|
}
|
|
|
|
debug('updateWithPluginValues %o', { cfg, overrides })
|
|
|
|
// make sure every option returned from the plugins file
|
|
// passes our validation functions
|
|
validate(overrides, (errMsg) => {
|
|
if (cfg.pluginsFile && cfg.projectRoot) {
|
|
const relativePluginsPath = path.relative(cfg.projectRoot, cfg.pluginsFile)
|
|
|
|
return errors.throw('PLUGINS_CONFIG_VALIDATION_ERROR', relativePluginsPath, errMsg)
|
|
}
|
|
|
|
return errors.throw('CONFIG_VALIDATION_ERROR', errMsg)
|
|
})
|
|
|
|
let originalResolvedBrowsers = cfg && cfg.resolved && cfg.resolved.browsers && R.clone(cfg.resolved.browsers)
|
|
|
|
if (!originalResolvedBrowsers) {
|
|
// have something to resolve with if plugins return nothing
|
|
originalResolvedBrowsers = {
|
|
value: cfg.browsers,
|
|
from: 'default',
|
|
}
|
|
}
|
|
|
|
const diffs = deepDiff(cfg, overrides, true)
|
|
|
|
debug('config diffs %o', diffs)
|
|
|
|
const userBrowserList = diffs && diffs.browsers && R.clone(diffs.browsers)
|
|
|
|
if (userBrowserList) {
|
|
debug('user browser list %o', userBrowserList)
|
|
}
|
|
|
|
// for each override go through
|
|
// and change the resolved values of cfg
|
|
// to point to the plugin
|
|
if (diffs) {
|
|
debug('resolved config before diffs %o', cfg.resolved)
|
|
this.setPluginResolvedOn(cfg.resolved, diffs)
|
|
debug('resolved config object %o', cfg.resolved)
|
|
}
|
|
|
|
// merge cfg into overrides
|
|
const merged = _.defaultsDeep(diffs, cfg)
|
|
|
|
debug('merged config object %o', merged)
|
|
|
|
// the above _.defaultsDeep combines arrays,
|
|
// if diffs.browsers = [1] and cfg.browsers = [1, 2]
|
|
// then the merged result merged.browsers = [1, 2]
|
|
// which is NOT what we want
|
|
if (Array.isArray(userBrowserList) && userBrowserList.length) {
|
|
merged.browsers = userBrowserList
|
|
merged.resolved.browsers.value = userBrowserList
|
|
}
|
|
|
|
if (overrides.browsers === null) {
|
|
// null breaks everything when merging lists
|
|
debug('replacing null browsers with original list %o', originalResolvedBrowsers)
|
|
merged.browsers = cfg.browsers
|
|
if (originalResolvedBrowsers) {
|
|
merged.resolved.browsers = originalResolvedBrowsers
|
|
}
|
|
}
|
|
|
|
debug('merged plugins config %o', merged)
|
|
|
|
return merged
|
|
},
|
|
|
|
// combines the default configuration object with values specified in the
|
|
// configuration file like "cypress.json". Values in configuration file
|
|
// overwrite the defaults.
|
|
resolveConfigValues (config, defaults, resolved = {}) {
|
|
// pick out only known configuration keys
|
|
return _
|
|
.chain(config)
|
|
.pick(publicConfigKeys)
|
|
.mapValues((val, key) => {
|
|
let r
|
|
const source = (s) => {
|
|
return {
|
|
value: val,
|
|
from: s,
|
|
}
|
|
}
|
|
|
|
r = resolved[key]
|
|
|
|
if (r) {
|
|
if (_.isObject(r)) {
|
|
return r
|
|
}
|
|
|
|
return source(r)
|
|
}
|
|
|
|
if (!(!_.isEqual(config[key], defaults[key]) && key !== 'browsers')) {
|
|
// "browsers" list is special, since it is dynamic by default
|
|
// and can only be ovewritten via plugins file
|
|
return source('default')
|
|
}
|
|
|
|
return source('config')
|
|
}).value()
|
|
},
|
|
|
|
// instead of the built-in Node process, specify a path to 3rd party Node
|
|
setNodeBinary: Promise.method((obj, onWarning) => {
|
|
if (obj.nodeVersion !== 'system') {
|
|
obj.resolvedNodeVersion = process.versions.node
|
|
|
|
return obj
|
|
}
|
|
|
|
return findSystemNode.findNodePathAndVersion()
|
|
.then(({ path, version }) => {
|
|
obj.resolvedNodePath = path
|
|
obj.resolvedNodeVersion = version
|
|
}).catch((err) => {
|
|
onWarning(errors.get('COULD_NOT_FIND_SYSTEM_NODE', process.versions.node))
|
|
obj.resolvedNodeVersion = process.versions.node
|
|
}).return(obj)
|
|
}),
|
|
|
|
setScaffoldPaths (obj) {
|
|
obj = _.clone(obj)
|
|
|
|
obj.integrationExampleName = scaffold.integrationExampleName()
|
|
obj.integrationExamplePath = path.join(obj.integrationFolder, obj.integrationExampleName)
|
|
|
|
debug('set scaffold paths')
|
|
|
|
return scaffold.fileTree(obj)
|
|
.then((fileTree) => {
|
|
debug('got file tree')
|
|
obj.scaffoldedFiles = fileTree
|
|
|
|
return obj
|
|
})
|
|
},
|
|
|
|
// async function
|
|
setSupportFileAndFolder (obj) {
|
|
if (!obj.supportFile) {
|
|
return Promise.resolve(obj)
|
|
}
|
|
|
|
obj = _.clone(obj)
|
|
|
|
// TODO move this logic to find support file into util/path_helpers
|
|
const sf = obj.supportFile
|
|
|
|
debug(`setting support file ${sf}`)
|
|
debug(`for project root ${obj.projectRoot}`)
|
|
|
|
return Promise
|
|
.try(() => {
|
|
// resolve full path with extension
|
|
obj.supportFile = utils.resolveModule(sf)
|
|
|
|
return debug('resolved support file %s', obj.supportFile)
|
|
}).then(() => {
|
|
if (pathHelpers.checkIfResolveChangedRootFolder(obj.supportFile, sf)) {
|
|
debug('require.resolve switched support folder from %s to %s', sf, obj.supportFile)
|
|
// this means the path was probably symlinked, like
|
|
// /tmp/foo -> /private/tmp/foo
|
|
// which can confuse the rest of the code
|
|
// switch it back to "normal" file
|
|
obj.supportFile = path.join(sf, path.basename(obj.supportFile))
|
|
|
|
return fs.pathExists(obj.supportFile)
|
|
.then((found) => {
|
|
if (!found) {
|
|
errors.throw('SUPPORT_FILE_NOT_FOUND', obj.supportFile, obj.configFile || defaultValues.configFile)
|
|
}
|
|
|
|
return debug('switching to found file %s', obj.supportFile)
|
|
})
|
|
}
|
|
}).catch({ code: 'MODULE_NOT_FOUND' }, () => {
|
|
debug('support JS module %s does not load', sf)
|
|
|
|
const loadingDefaultSupportFile = sf === path.resolve(obj.projectRoot, defaultValues.supportFile)
|
|
|
|
return utils.discoverModuleFile({
|
|
filename: sf,
|
|
isDefault: loadingDefaultSupportFile,
|
|
projectRoot: obj.projectRoot,
|
|
})
|
|
.then((result) => {
|
|
if (result === null) {
|
|
const configFile = obj.configFile || defaultValues.configFile
|
|
|
|
return errors.throw('SUPPORT_FILE_NOT_FOUND', path.resolve(obj.projectRoot, sf), configFile)
|
|
}
|
|
|
|
debug('setting support file to %o', { result })
|
|
obj.supportFile = result
|
|
|
|
return obj
|
|
})
|
|
})
|
|
.then(() => {
|
|
if (obj.supportFile) {
|
|
// set config.supportFolder to its directory
|
|
obj.supportFolder = path.dirname(obj.supportFile)
|
|
debug(`set support folder ${obj.supportFolder}`)
|
|
}
|
|
|
|
return obj
|
|
})
|
|
},
|
|
|
|
// set pluginsFile to an absolute path with the following rules:
|
|
// - do nothing if pluginsFile is falsey
|
|
// - look up the absolute path via node, so 'cypress/plugins' can resolve
|
|
// to 'cypress/plugins/index.js' or 'cypress/plugins/index.coffee'
|
|
// - if not found
|
|
// * and the pluginsFile is set to the default
|
|
// - and the path to the pluginsFile directory exists
|
|
// * assume the user doesn't need a pluginsFile, set it to false
|
|
// so it's ignored down the pipeline
|
|
// - and the path to the pluginsFile directory does not exist
|
|
// * set it to cypress/plugins/index.js, it will get scaffolded
|
|
// * and the pluginsFile is NOT set to the default
|
|
// - throw an error, because it should be there if the user
|
|
// explicitly set it
|
|
setPluginsFile: Promise.method((obj) => {
|
|
if (!obj.pluginsFile) {
|
|
return obj
|
|
}
|
|
|
|
obj = _.clone(obj)
|
|
|
|
const {
|
|
pluginsFile,
|
|
} = obj
|
|
|
|
debug(`setting plugins file ${pluginsFile}`)
|
|
debug(`for project root ${obj.projectRoot}`)
|
|
|
|
return Promise
|
|
.try(() => {
|
|
// resolve full path with extension
|
|
obj.pluginsFile = utils.resolveModule(pluginsFile)
|
|
|
|
return debug(`set pluginsFile to ${obj.pluginsFile}`)
|
|
}).catch({ code: 'MODULE_NOT_FOUND' }, () => {
|
|
debug('plugins module does not exist %o', { pluginsFile })
|
|
|
|
const isLoadingDefaultPluginsFile = pluginsFile === path.resolve(obj.projectRoot, defaultValues.pluginsFile)
|
|
|
|
return utils.discoverModuleFile({
|
|
filename: pluginsFile,
|
|
isDefault: isLoadingDefaultPluginsFile,
|
|
projectRoot: obj.projectRoot,
|
|
})
|
|
.then((result) => {
|
|
if (result === null) {
|
|
return errors.throw('PLUGINS_FILE_ERROR', path.resolve(obj.projectRoot, pluginsFile))
|
|
}
|
|
|
|
debug('setting plugins file to %o', { result })
|
|
obj.pluginsFile = result
|
|
|
|
return obj
|
|
})
|
|
}).return(obj)
|
|
}),
|
|
|
|
setParentTestsPaths (obj) {
|
|
// projectRoot: "/path/to/project"
|
|
// integrationFolder: "/path/to/project/cypress/integration"
|
|
// componentFolder: "/path/to/project/cypress/components"
|
|
// parentTestsFolder: "/path/to/project/cypress"
|
|
// parentTestsFolderDisplay: "project/cypress"
|
|
|
|
obj = _.clone(obj)
|
|
|
|
const ptfd = (obj.parentTestsFolder = path.dirname(obj.integrationFolder))
|
|
|
|
const prd = path.dirname(obj.projectRoot != null ? obj.projectRoot : '')
|
|
|
|
obj.parentTestsFolderDisplay = path.relative(prd, ptfd)
|
|
|
|
return obj
|
|
},
|
|
|
|
setAbsolutePaths (obj, defaults) {
|
|
let pr
|
|
|
|
obj = _.clone(obj)
|
|
|
|
// if we have a projectRoot
|
|
pr = obj.projectRoot
|
|
|
|
if (pr) {
|
|
// reset fileServerFolder to be absolute
|
|
// obj.fileServerFolder = path.resolve(pr, obj.fileServerFolder)
|
|
|
|
// and do the same for all the rest
|
|
_.extend(obj, convertRelativeToAbsolutePaths(pr, obj, defaults))
|
|
}
|
|
|
|
return obj
|
|
},
|
|
|
|
setUrls (obj) {
|
|
obj = _.clone(obj)
|
|
|
|
const proxyUrl = `http://localhost:${obj.port}`
|
|
|
|
const rootUrl = obj.baseUrl ?
|
|
origin(obj.baseUrl)
|
|
:
|
|
proxyUrl
|
|
|
|
_.extend(obj, {
|
|
proxyUrl,
|
|
browserUrl: rootUrl + obj.clientRoute,
|
|
reporterUrl: rootUrl + obj.reporterRoute,
|
|
xhrUrl: obj.namespace + obj.xhrRoute,
|
|
})
|
|
|
|
return obj
|
|
},
|
|
|
|
parseEnv (cfg, envCLI, resolved = {}) {
|
|
const envVars = (resolved.env = {})
|
|
|
|
const resolveFrom = (from, obj = {}) => {
|
|
return _.each(obj, (val, key) => {
|
|
return envVars[key] = {
|
|
value: val,
|
|
from,
|
|
}
|
|
})
|
|
}
|
|
|
|
const envCfg = cfg.env != null ? cfg.env : {}
|
|
const envFile = cfg.envFile != null ? cfg.envFile : {}
|
|
let envProc = this.getProcessEnvVars(process.env) || {}
|
|
|
|
envCLI = envCLI != null ? envCLI : {}
|
|
|
|
const matchesConfigKey = function (key) {
|
|
if (_.has(defaultValues, key)) {
|
|
return key
|
|
}
|
|
|
|
key = key.toLowerCase().replace(dashesOrUnderscoresRe, '')
|
|
key = _.camelCase(key)
|
|
|
|
if (_.has(defaultValues, key)) {
|
|
return key
|
|
}
|
|
}
|
|
|
|
const configFromEnv = _.reduce(envProc, (memo, val, key) => {
|
|
let cfgKey
|
|
|
|
cfgKey = matchesConfigKey(key)
|
|
|
|
if (cfgKey) {
|
|
// only change the value if it hasnt been
|
|
// set by the CLI. override default + config
|
|
if (resolved[cfgKey] !== 'cli') {
|
|
cfg[cfgKey] = val
|
|
resolved[cfgKey] = {
|
|
value: val,
|
|
from: 'env',
|
|
}
|
|
}
|
|
|
|
memo.push(key)
|
|
}
|
|
|
|
return memo
|
|
}
|
|
, [])
|
|
|
|
envProc = _.chain(envProc)
|
|
.omit(configFromEnv)
|
|
.mapValues(hideSpecialVals)
|
|
.value()
|
|
|
|
resolveFrom('config', envCfg)
|
|
resolveFrom('envFile', envFile)
|
|
resolveFrom('env', envProc)
|
|
resolveFrom('cli', envCLI)
|
|
|
|
// envCfg is from cypress.json
|
|
// envFile is from cypress.env.json
|
|
// envProc is from process env vars
|
|
// envCLI is from CLI arguments
|
|
return _.extend(envCfg, envFile, envProc, envCLI)
|
|
},
|
|
|
|
getProcessEnvVars (obj = {}) {
|
|
return _.reduce(obj, (memo, value, key) => {
|
|
if (isCypressEnvLike(key)) {
|
|
memo[removeEnvPrefix(key)] = coerce(value)
|
|
}
|
|
|
|
return memo
|
|
}
|
|
, {})
|
|
},
|
|
|
|
getNameFromRoot (root = '') {
|
|
return path.basename(root)
|
|
},
|
|
|
|
}
|