source: trip-planner-front/node_modules/karma/lib/config.js@ 6c1585f

Last change on this file since 6c1585f was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 18.4 KB
Line 
1'use strict'
2
3const path = require('path')
4const assert = require('assert')
5
6const logger = require('./logger')
7const log = logger.create('config')
8const helper = require('./helper')
9const constant = require('./constants')
10
11const _ = require('lodash')
12
13let COFFEE_SCRIPT_AVAILABLE = false
14let LIVE_SCRIPT_AVAILABLE = false
15let TYPE_SCRIPT_AVAILABLE = false
16
17try {
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.
24try {
25 require('LiveScript')
26 LIVE_SCRIPT_AVAILABLE = true
27} catch (e) {}
28
29try {
30 require('ts-node')
31 TYPE_SCRIPT_AVAILABLE = true
32} catch (e) {}
33
34class 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
51class UrlPattern extends Pattern {
52 constructor (url, type) {
53 super(url, false, true, false, false, type)
54 }
55}
56
57function 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
72function 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
84function 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
94function 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
104function 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 */
274class 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
351const 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 */
393function 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
539exports.parseConfig = parseConfig
540exports.Pattern = Pattern
541exports.createPatternObject = createPatternObject
542exports.Config = Config
Note: See TracBrowser for help on using the repository browser.