1 | 'use strict'
|
---|
2 |
|
---|
3 | const path = require('path')
|
---|
4 | const assert = require('assert')
|
---|
5 |
|
---|
6 | const logger = require('./logger')
|
---|
7 | const log = logger.create('config')
|
---|
8 | const helper = require('./helper')
|
---|
9 | const constant = require('./constants')
|
---|
10 |
|
---|
11 | const _ = require('lodash')
|
---|
12 |
|
---|
13 | let COFFEE_SCRIPT_AVAILABLE = false
|
---|
14 | let LIVE_SCRIPT_AVAILABLE = false
|
---|
15 | let TYPE_SCRIPT_AVAILABLE = false
|
---|
16 |
|
---|
17 | try {
|
---|
18 | require('coffeescript').register()
|
---|
19 | COFFEE_SCRIPT_AVAILABLE = true
|
---|
20 | } catch (e) {}
|
---|
21 |
|
---|
22 | // LiveScript is required here to enable config files written in LiveScript.
|
---|
23 | // It's not directly used in this file.
|
---|
24 | try {
|
---|
25 | require('LiveScript')
|
---|
26 | LIVE_SCRIPT_AVAILABLE = true
|
---|
27 | } catch (e) {}
|
---|
28 |
|
---|
29 | try {
|
---|
30 | require('ts-node')
|
---|
31 | TYPE_SCRIPT_AVAILABLE = true
|
---|
32 | } catch (e) {}
|
---|
33 |
|
---|
34 | class Pattern {
|
---|
35 | constructor (pattern, served, included, watched, nocache, type, isBinary) {
|
---|
36 | this.pattern = pattern
|
---|
37 | this.served = helper.isDefined(served) ? served : true
|
---|
38 | this.included = helper.isDefined(included) ? included : true
|
---|
39 | this.watched = helper.isDefined(watched) ? watched : true
|
---|
40 | this.nocache = helper.isDefined(nocache) ? nocache : false
|
---|
41 | this.weight = helper.mmPatternWeight(pattern)
|
---|
42 | this.type = type
|
---|
43 | this.isBinary = isBinary
|
---|
44 | }
|
---|
45 |
|
---|
46 | compare (other) {
|
---|
47 | return helper.mmComparePatternWeights(this.weight, other.weight)
|
---|
48 | }
|
---|
49 | }
|
---|
50 |
|
---|
51 | class UrlPattern extends Pattern {
|
---|
52 | constructor (url, type) {
|
---|
53 | super(url, false, true, false, false, type)
|
---|
54 | }
|
---|
55 | }
|
---|
56 |
|
---|
57 | function createPatternObject (pattern) {
|
---|
58 | if (pattern && helper.isString(pattern)) {
|
---|
59 | return helper.isUrlAbsolute(pattern)
|
---|
60 | ? new UrlPattern(pattern)
|
---|
61 | : new Pattern(pattern)
|
---|
62 | } else if (helper.isObject(pattern) && pattern.pattern && helper.isString(pattern.pattern)) {
|
---|
63 | return helper.isUrlAbsolute(pattern.pattern)
|
---|
64 | ? new UrlPattern(pattern.pattern, pattern.type)
|
---|
65 | : new Pattern(pattern.pattern, pattern.served, pattern.included, pattern.watched, pattern.nocache, pattern.type)
|
---|
66 | } else {
|
---|
67 | log.warn(`Invalid pattern ${pattern}!\n\tExpected string or object with "pattern" property.`)
|
---|
68 | return new Pattern(null, false, false, false, false)
|
---|
69 | }
|
---|
70 | }
|
---|
71 |
|
---|
72 | function normalizeUrl (url) {
|
---|
73 | if (!url.startsWith('/')) {
|
---|
74 | url = `/${url}`
|
---|
75 | }
|
---|
76 |
|
---|
77 | if (!url.endsWith('/')) {
|
---|
78 | url = url + '/'
|
---|
79 | }
|
---|
80 |
|
---|
81 | return url
|
---|
82 | }
|
---|
83 |
|
---|
84 | function normalizeUrlRoot (urlRoot) {
|
---|
85 | const normalizedUrlRoot = normalizeUrl(urlRoot)
|
---|
86 |
|
---|
87 | if (normalizedUrlRoot !== urlRoot) {
|
---|
88 | log.warn(`urlRoot normalized to "${normalizedUrlRoot}"`)
|
---|
89 | }
|
---|
90 |
|
---|
91 | return normalizedUrlRoot
|
---|
92 | }
|
---|
93 |
|
---|
94 | function normalizeProxyPath (proxyPath) {
|
---|
95 | const normalizedProxyPath = normalizeUrl(proxyPath)
|
---|
96 |
|
---|
97 | if (normalizedProxyPath !== proxyPath) {
|
---|
98 | log.warn(`proxyPath normalized to "${normalizedProxyPath}"`)
|
---|
99 | }
|
---|
100 |
|
---|
101 | return normalizedProxyPath
|
---|
102 | }
|
---|
103 |
|
---|
104 | function normalizeConfig (config, configFilePath) {
|
---|
105 | function basePathResolve (relativePath) {
|
---|
106 | if (helper.isUrlAbsolute(relativePath)) {
|
---|
107 | return relativePath
|
---|
108 | } else if (helper.isDefined(config.basePath) && helper.isDefined(relativePath)) {
|
---|
109 | return path.resolve(config.basePath, relativePath)
|
---|
110 | } else {
|
---|
111 | return ''
|
---|
112 | }
|
---|
113 | }
|
---|
114 |
|
---|
115 | function createPatternMapper (resolve) {
|
---|
116 | return (objectPattern) => Object.assign(objectPattern, { pattern: resolve(objectPattern.pattern) })
|
---|
117 | }
|
---|
118 |
|
---|
119 | if (helper.isString(configFilePath)) {
|
---|
120 | config.basePath = path.resolve(path.dirname(configFilePath), config.basePath) // resolve basePath
|
---|
121 | config.exclude.push(configFilePath) // always ignore the config file itself
|
---|
122 | } else {
|
---|
123 | config.basePath = path.resolve(config.basePath || '.')
|
---|
124 | }
|
---|
125 |
|
---|
126 | config.files = config.files.map(createPatternObject).map(createPatternMapper(basePathResolve))
|
---|
127 | config.exclude = config.exclude.map(basePathResolve)
|
---|
128 | config.customContextFile = config.customContextFile && basePathResolve(config.customContextFile)
|
---|
129 | config.customDebugFile = config.customDebugFile && basePathResolve(config.customDebugFile)
|
---|
130 | config.customClientContextFile = config.customClientContextFile && basePathResolve(config.customClientContextFile)
|
---|
131 |
|
---|
132 | // normalize paths on windows
|
---|
133 | config.basePath = helper.normalizeWinPath(config.basePath)
|
---|
134 | config.files = config.files.map(createPatternMapper(helper.normalizeWinPath))
|
---|
135 | config.exclude = config.exclude.map(helper.normalizeWinPath)
|
---|
136 | config.customContextFile = helper.normalizeWinPath(config.customContextFile)
|
---|
137 | config.customDebugFile = helper.normalizeWinPath(config.customDebugFile)
|
---|
138 | config.customClientContextFile = helper.normalizeWinPath(config.customClientContextFile)
|
---|
139 |
|
---|
140 | // normalize urlRoot
|
---|
141 | config.urlRoot = normalizeUrlRoot(config.urlRoot)
|
---|
142 |
|
---|
143 | // normalize and default upstream proxy settings if given
|
---|
144 | if (config.upstreamProxy) {
|
---|
145 | const proxy = config.upstreamProxy
|
---|
146 | proxy.path = helper.isDefined(proxy.path) ? normalizeProxyPath(proxy.path) : '/'
|
---|
147 | proxy.hostname = helper.isDefined(proxy.hostname) ? proxy.hostname : 'localhost'
|
---|
148 | proxy.port = helper.isDefined(proxy.port) ? proxy.port : 9875
|
---|
149 |
|
---|
150 | // force protocol to end with ':'
|
---|
151 | proxy.protocol = (proxy.protocol || 'http').split(':')[0] + ':'
|
---|
152 | if (proxy.protocol.match(/https?:/) === null) {
|
---|
153 | log.warn(`"${proxy.protocol}" is not a supported upstream proxy protocol, defaulting to "http:"`)
|
---|
154 | proxy.protocol = 'http:'
|
---|
155 | }
|
---|
156 | }
|
---|
157 |
|
---|
158 | // force protocol to end with ':'
|
---|
159 | config.protocol = (config.protocol || 'http').split(':')[0] + ':'
|
---|
160 | if (config.protocol.match(/https?:/) === null) {
|
---|
161 | log.warn(`"${config.protocol}" is not a supported protocol, defaulting to "http:"`)
|
---|
162 | config.protocol = 'http:'
|
---|
163 | }
|
---|
164 |
|
---|
165 | if (config.proxies && Object.prototype.hasOwnProperty.call(config.proxies, config.urlRoot)) {
|
---|
166 | log.warn(`"${config.urlRoot}" is proxied, you should probably change urlRoot to avoid conflicts`)
|
---|
167 | }
|
---|
168 |
|
---|
169 | if (config.singleRun && config.autoWatch) {
|
---|
170 | log.debug('autoWatch set to false, because of singleRun')
|
---|
171 | config.autoWatch = false
|
---|
172 | }
|
---|
173 |
|
---|
174 | if (config.runInParent) {
|
---|
175 | log.debug('useIframe set to false, because using runInParent')
|
---|
176 | config.useIframe = false
|
---|
177 | }
|
---|
178 |
|
---|
179 | if (!config.singleRun && !config.useIframe && config.runInParent) {
|
---|
180 | log.debug('singleRun set to true, because using runInParent')
|
---|
181 | config.singleRun = true
|
---|
182 | }
|
---|
183 |
|
---|
184 | if (helper.isString(config.reporters)) {
|
---|
185 | config.reporters = config.reporters.split(',')
|
---|
186 | }
|
---|
187 |
|
---|
188 | if (config.client && config.client.args) {
|
---|
189 | assert(Array.isArray(config.client.args), 'Invalid configuration: client.args must be an array of strings')
|
---|
190 | }
|
---|
191 |
|
---|
192 | if (config.browsers) {
|
---|
193 | assert(Array.isArray(config.browsers), 'Invalid configuration: browsers option must be an array')
|
---|
194 | }
|
---|
195 |
|
---|
196 | if (config.formatError) {
|
---|
197 | assert(helper.isFunction(config.formatError), 'Invalid configuration: formatError option must be a function.')
|
---|
198 | }
|
---|
199 |
|
---|
200 | if (config.processKillTimeout) {
|
---|
201 | assert(helper.isNumber(config.processKillTimeout), 'Invalid configuration: processKillTimeout option must be a number.')
|
---|
202 | }
|
---|
203 |
|
---|
204 | if (config.browserSocketTimeout) {
|
---|
205 | assert(helper.isNumber(config.browserSocketTimeout), 'Invalid configuration: browserSocketTimeout option must be a number.')
|
---|
206 | }
|
---|
207 |
|
---|
208 | if (config.pingTimeout) {
|
---|
209 | assert(helper.isNumber(config.pingTimeout), 'Invalid configuration: pingTimeout option must be a number.')
|
---|
210 | }
|
---|
211 |
|
---|
212 | const defaultClient = config.defaultClient || {}
|
---|
213 | Object.keys(defaultClient).forEach(function (key) {
|
---|
214 | const option = config.client[key]
|
---|
215 | config.client[key] = helper.isDefined(option) ? option : defaultClient[key]
|
---|
216 | })
|
---|
217 |
|
---|
218 | // normalize preprocessors
|
---|
219 | const preprocessors = config.preprocessors || {}
|
---|
220 | const normalizedPreprocessors = config.preprocessors = Object.create(null)
|
---|
221 |
|
---|
222 | Object.keys(preprocessors).forEach(function (pattern) {
|
---|
223 | const normalizedPattern = helper.normalizeWinPath(basePathResolve(pattern))
|
---|
224 |
|
---|
225 | normalizedPreprocessors[normalizedPattern] = helper.isString(preprocessors[pattern])
|
---|
226 | ? [preprocessors[pattern]] : preprocessors[pattern]
|
---|
227 | })
|
---|
228 |
|
---|
229 | // define custom launchers/preprocessors/reporters - create a new plugin
|
---|
230 | const module = Object.create(null)
|
---|
231 | let hasSomeInlinedPlugin = false
|
---|
232 | const types = ['launcher', 'preprocessor', 'reporter']
|
---|
233 |
|
---|
234 | types.forEach(function (type) {
|
---|
235 | const definitions = config[`custom${helper.ucFirst(type)}s`] || {}
|
---|
236 |
|
---|
237 | Object.keys(definitions).forEach(function (name) {
|
---|
238 | const definition = definitions[name]
|
---|
239 |
|
---|
240 | if (!helper.isObject(definition)) {
|
---|
241 | return log.warn(`Can not define ${type} ${name}. Definition has to be an object.`)
|
---|
242 | }
|
---|
243 |
|
---|
244 | if (!helper.isString(definition.base)) {
|
---|
245 | return log.warn(`Can not define ${type} ${name}. Missing base ${type}.`)
|
---|
246 | }
|
---|
247 |
|
---|
248 | const token = type + ':' + definition.base
|
---|
249 | const locals = {
|
---|
250 | args: ['value', definition]
|
---|
251 | }
|
---|
252 |
|
---|
253 | module[type + ':' + name] = ['factory', function (injector) {
|
---|
254 | const plugin = injector.createChild([locals], [token]).get(token)
|
---|
255 | if (type === 'launcher' && helper.isDefined(definition.displayName)) {
|
---|
256 | plugin.displayName = definition.displayName
|
---|
257 | }
|
---|
258 | return plugin
|
---|
259 | }]
|
---|
260 | hasSomeInlinedPlugin = true
|
---|
261 | })
|
---|
262 | })
|
---|
263 |
|
---|
264 | if (hasSomeInlinedPlugin) {
|
---|
265 | config.plugins.push(module)
|
---|
266 | }
|
---|
267 |
|
---|
268 | return config
|
---|
269 | }
|
---|
270 |
|
---|
271 | /**
|
---|
272 | * @class
|
---|
273 | */
|
---|
274 | class Config {
|
---|
275 | constructor () {
|
---|
276 | this.LOG_DISABLE = constant.LOG_DISABLE
|
---|
277 | this.LOG_ERROR = constant.LOG_ERROR
|
---|
278 | this.LOG_WARN = constant.LOG_WARN
|
---|
279 | this.LOG_INFO = constant.LOG_INFO
|
---|
280 | this.LOG_DEBUG = constant.LOG_DEBUG
|
---|
281 |
|
---|
282 | // DEFAULT CONFIG
|
---|
283 | this.frameworks = []
|
---|
284 | this.protocol = 'http:'
|
---|
285 | this.port = constant.DEFAULT_PORT
|
---|
286 | this.listenAddress = constant.DEFAULT_LISTEN_ADDR
|
---|
287 | this.hostname = constant.DEFAULT_HOSTNAME
|
---|
288 | this.httpsServerConfig = {}
|
---|
289 | this.basePath = ''
|
---|
290 | this.files = []
|
---|
291 | this.browserConsoleLogOptions = {
|
---|
292 | level: 'debug',
|
---|
293 | format: '%b %T: %m',
|
---|
294 | terminal: true
|
---|
295 | }
|
---|
296 | this.customContextFile = null
|
---|
297 | this.customDebugFile = null
|
---|
298 | this.customClientContextFile = null
|
---|
299 | this.exclude = []
|
---|
300 | this.logLevel = constant.LOG_INFO
|
---|
301 | this.colors = true
|
---|
302 | this.autoWatch = true
|
---|
303 | this.autoWatchBatchDelay = 250
|
---|
304 | this.restartOnFileChange = false
|
---|
305 | this.usePolling = process.platform === 'linux'
|
---|
306 | this.reporters = ['progress']
|
---|
307 | this.singleRun = false
|
---|
308 | this.browsers = []
|
---|
309 | this.captureTimeout = 60000
|
---|
310 | this.pingTimeout = 5000
|
---|
311 | this.proxies = {}
|
---|
312 | this.proxyValidateSSL = true
|
---|
313 | this.preprocessors = {}
|
---|
314 | this.preprocessor_priority = {}
|
---|
315 | this.urlRoot = '/'
|
---|
316 | this.upstreamProxy = undefined
|
---|
317 | this.reportSlowerThan = 0
|
---|
318 | this.loggers = [constant.CONSOLE_APPENDER]
|
---|
319 | this.transports = ['polling', 'websocket']
|
---|
320 | this.forceJSONP = false
|
---|
321 | this.plugins = ['karma-*']
|
---|
322 | this.defaultClient = this.client = {
|
---|
323 | args: [],
|
---|
324 | useIframe: true,
|
---|
325 | runInParent: false,
|
---|
326 | captureConsole: true,
|
---|
327 | clearContext: true
|
---|
328 | }
|
---|
329 | this.browserDisconnectTimeout = 2000
|
---|
330 | this.browserDisconnectTolerance = 0
|
---|
331 | this.browserNoActivityTimeout = 30000
|
---|
332 | this.processKillTimeout = 2000
|
---|
333 | this.concurrency = Infinity
|
---|
334 | this.failOnEmptyTestSuite = true
|
---|
335 | this.retryLimit = 2
|
---|
336 | this.detached = false
|
---|
337 | this.crossOriginAttribute = true
|
---|
338 | this.browserSocketTimeout = 20000
|
---|
339 | }
|
---|
340 |
|
---|
341 | set (newConfig) {
|
---|
342 | _.mergeWith(this, newConfig, (obj, src) => {
|
---|
343 | // Overwrite arrays to keep consistent with #283
|
---|
344 | if (Array.isArray(src)) {
|
---|
345 | return src
|
---|
346 | }
|
---|
347 | })
|
---|
348 | }
|
---|
349 | }
|
---|
350 |
|
---|
351 | const CONFIG_SYNTAX_HELP = ' module.exports = function(config) {\n' +
|
---|
352 | ' config.set({\n' +
|
---|
353 | ' // your config\n' +
|
---|
354 | ' });\n' +
|
---|
355 | ' };\n'
|
---|
356 |
|
---|
357 | /**
|
---|
358 | * Retrieve a parsed and finalized Karma `Config` instance. This `karmaConfig`
|
---|
359 | * object may be used to configure public API methods such a `Server`,
|
---|
360 | * `runner.run`, and `stopper.stop`.
|
---|
361 | *
|
---|
362 | * @param {?string} [configFilePath=null]
|
---|
363 | * A string representing a file system path pointing to the config file
|
---|
364 | * whose default export is a function that will be used to set Karma
|
---|
365 | * configuration options. This function will be passed an instance of the
|
---|
366 | * `Config` class as its first argument. If this option is not provided,
|
---|
367 | * then only the options provided by the `cliOptions` argument will be
|
---|
368 | * set.
|
---|
369 | * @param {Object} cliOptions
|
---|
370 | * An object whose values will take priority over options set in the
|
---|
371 | * config file. The config object passed to function exported by the
|
---|
372 | * config file will already have these options applied. Any changes the
|
---|
373 | * config file makes to these options will effectively be ignored in the
|
---|
374 | * final configuration.
|
---|
375 | *
|
---|
376 | * `cliOptions` all the same options as the config file and is applied
|
---|
377 | * using the same `config.set()` method.
|
---|
378 | * @param {Object} parseOptions
|
---|
379 | * @param {boolean} [parseOptions.promiseConfig=false]
|
---|
380 | * When `true`, a promise that resolves to a `Config` object will be
|
---|
381 | * returned. This also allows the function exported by config files (if
|
---|
382 | * provided) to be asynchronous by returning a promise. Resolving this
|
---|
383 | * promise indicates that all async activity has completed. The resolution
|
---|
384 | * value itself is ignored, all configuration must be done with
|
---|
385 | * `config.set`.
|
---|
386 | * @param {boolean} [parseOptions.throwErrors=false]
|
---|
387 | * When `true`, process exiting on critical failures will be disabled. In
|
---|
388 | * The error will be thrown as an exception. If
|
---|
389 | * `parseOptions.promiseConfig` is also `true`, then the error will
|
---|
390 | * instead be used as the promise's reject reason.
|
---|
391 | * @returns {Config|Promise<Config>}
|
---|
392 | */
|
---|
393 | function parseConfig (configFilePath, cliOptions, parseOptions) {
|
---|
394 | const promiseConfig = parseOptions && parseOptions.promiseConfig === true
|
---|
395 | const throwErrors = parseOptions && parseOptions.throwErrors === true
|
---|
396 | const shouldSetupLoggerEarly = promiseConfig
|
---|
397 | if (shouldSetupLoggerEarly) {
|
---|
398 | // `setupFromConfig` provides defaults for `colors` and `logLevel`.
|
---|
399 | // `setup` provides defaults for `appenders`
|
---|
400 | // The first argument MUST BE an object
|
---|
401 | logger.setupFromConfig({})
|
---|
402 | }
|
---|
403 | function fail () {
|
---|
404 | log.error(...arguments)
|
---|
405 | if (throwErrors) {
|
---|
406 | const errorMessage = Array.from(arguments).join(' ')
|
---|
407 | const err = new Error(errorMessage)
|
---|
408 | if (promiseConfig) {
|
---|
409 | return Promise.reject(err)
|
---|
410 | }
|
---|
411 | throw err
|
---|
412 | } else {
|
---|
413 | const warningMessage =
|
---|
414 | 'The `parseConfig()` function historically called `process.exit(1)`' +
|
---|
415 | ' when it failed. This behavior is now deprecated and function will' +
|
---|
416 | ' throw an error in the next major release. To suppress this warning' +
|
---|
417 | ' pass `throwErrors: true` as a third argument to opt-in into the new' +
|
---|
418 | ' behavior and adjust your code to respond to the exception' +
|
---|
419 | ' accordingly.' +
|
---|
420 | ' Example: `parseConfig(path, cliOptions, { throwErrors: true })`'
|
---|
421 | log.warn(warningMessage)
|
---|
422 | process.exit(1)
|
---|
423 | }
|
---|
424 | }
|
---|
425 |
|
---|
426 | let configModule
|
---|
427 | if (configFilePath) {
|
---|
428 | try {
|
---|
429 | if (path.extname(configFilePath) === '.ts' && TYPE_SCRIPT_AVAILABLE) {
|
---|
430 | require('ts-node').register()
|
---|
431 | }
|
---|
432 | configModule = require(configFilePath)
|
---|
433 | if (typeof configModule === 'object' && typeof configModule.default !== 'undefined') {
|
---|
434 | configModule = configModule.default
|
---|
435 | }
|
---|
436 | } catch (e) {
|
---|
437 | const extension = path.extname(configFilePath)
|
---|
438 | if (extension === '.coffee' && !COFFEE_SCRIPT_AVAILABLE) {
|
---|
439 | log.error('You need to install CoffeeScript.\n npm install coffeescript --save-dev')
|
---|
440 | } else if (extension === '.ls' && !LIVE_SCRIPT_AVAILABLE) {
|
---|
441 | log.error('You need to install LiveScript.\n npm install LiveScript --save-dev')
|
---|
442 | } else if (extension === '.ts' && !TYPE_SCRIPT_AVAILABLE) {
|
---|
443 | log.error('You need to install TypeScript.\n npm install typescript ts-node --save-dev')
|
---|
444 | }
|
---|
445 | return fail('Error in config file!\n ' + e.stack || e)
|
---|
446 | }
|
---|
447 | if (!helper.isFunction(configModule)) {
|
---|
448 | return fail('Config file must export a function!\n' + CONFIG_SYNTAX_HELP)
|
---|
449 | }
|
---|
450 | } else {
|
---|
451 | configModule = () => {} // if no config file path is passed, we define a dummy config module.
|
---|
452 | }
|
---|
453 |
|
---|
454 | const config = new Config()
|
---|
455 |
|
---|
456 | // save and reset hostname and listenAddress so we can detect if the user
|
---|
457 | // changed them
|
---|
458 | const defaultHostname = config.hostname
|
---|
459 | config.hostname = null
|
---|
460 | const defaultListenAddress = config.listenAddress
|
---|
461 | config.listenAddress = null
|
---|
462 |
|
---|
463 | // add the user's configuration in
|
---|
464 | config.set(cliOptions)
|
---|
465 |
|
---|
466 | let configModuleReturn
|
---|
467 | try {
|
---|
468 | configModuleReturn = configModule(config)
|
---|
469 | } catch (e) {
|
---|
470 | return fail('Error in config file!\n', e)
|
---|
471 | }
|
---|
472 | function finalizeConfig (config) {
|
---|
473 | // merge the config from config file and cliOptions (precedence)
|
---|
474 | config.set(cliOptions)
|
---|
475 |
|
---|
476 | // if the user changed listenAddress, but didn't set a hostname, warn them
|
---|
477 | if (config.hostname === null && config.listenAddress !== null) {
|
---|
478 | log.warn(`ListenAddress was set to ${config.listenAddress} but hostname was left as the default: ` +
|
---|
479 | `${defaultHostname}. If your browsers fail to connect, consider changing the hostname option.`)
|
---|
480 | }
|
---|
481 | // restore values that weren't overwritten by the user
|
---|
482 | if (config.hostname === null) {
|
---|
483 | config.hostname = defaultHostname
|
---|
484 | }
|
---|
485 | if (config.listenAddress === null) {
|
---|
486 | config.listenAddress = defaultListenAddress
|
---|
487 | }
|
---|
488 |
|
---|
489 | // configure the logger as soon as we can
|
---|
490 | logger.setup(config.logLevel, config.colors, config.loggers)
|
---|
491 |
|
---|
492 | log.debug(configFilePath ? `Loading config ${configFilePath}` : 'No config file specified.')
|
---|
493 |
|
---|
494 | return normalizeConfig(config, configFilePath)
|
---|
495 | }
|
---|
496 |
|
---|
497 | /**
|
---|
498 | * Return value is a function or (non-null) object that has a `then` method.
|
---|
499 | *
|
---|
500 | * @type {boolean}
|
---|
501 | * @see {@link https://promisesaplus.com/}
|
---|
502 | */
|
---|
503 | const returnIsThenable = (
|
---|
504 | (
|
---|
505 | (configModuleReturn != null && typeof configModuleReturn === 'object') ||
|
---|
506 | typeof configModuleReturn === 'function'
|
---|
507 | ) && typeof configModuleReturn.then === 'function'
|
---|
508 | )
|
---|
509 | if (returnIsThenable) {
|
---|
510 | if (promiseConfig !== true) {
|
---|
511 | const errorMessage =
|
---|
512 | 'The `parseOptions.promiseConfig` option must be set to `true` to ' +
|
---|
513 | 'enable promise return values from configuration files. ' +
|
---|
514 | 'Example: `parseConfig(path, cliOptions, { promiseConfig: true })`'
|
---|
515 | return fail(errorMessage)
|
---|
516 | }
|
---|
517 | return configModuleReturn.then(
|
---|
518 | function onKarmaConfigModuleFulfilled (/* ignoredResolutionValue */) {
|
---|
519 | return finalizeConfig(config)
|
---|
520 | },
|
---|
521 | function onKarmaConfigModuleRejected (reason) {
|
---|
522 | return fail('Error in config file!\n', reason)
|
---|
523 | }
|
---|
524 | )
|
---|
525 | } else {
|
---|
526 | if (promiseConfig) {
|
---|
527 | try {
|
---|
528 | return Promise.resolve(finalizeConfig(config))
|
---|
529 | } catch (exception) {
|
---|
530 | return Promise.reject(exception)
|
---|
531 | }
|
---|
532 | } else {
|
---|
533 | return finalizeConfig(config)
|
---|
534 | }
|
---|
535 | }
|
---|
536 | }
|
---|
537 |
|
---|
538 | // PUBLIC API
|
---|
539 | exports.parseConfig = parseConfig
|
---|
540 | exports.Pattern = Pattern
|
---|
541 | exports.createPatternObject = createPatternObject
|
---|
542 | exports.Config = Config
|
---|