1 var path = require('path')
3 var logger = require('./logger')
4 var log = logger.create('config')
5 var helper = require('./helper')
6 var constant = require('./constants')
8 var COFFEE_SCRIPT_AVAILABLE = false
9 var LIVE_SCRIPT_AVAILABLE = false
11 // Coffee is required here to enable config files written in coffee-script.
12 // It's not directly used in this file.
14 require('coffee-script').register()
15 COFFEE_SCRIPT_AVAILABLE = true
18 // LiveScript is required here to enable config files written in LiveScript.
19 // It's not directly used in this file.
22 LIVE_SCRIPT_AVAILABLE = true
25 var Pattern = function (pattern, served, included, watched) {
26 this.pattern = pattern
27 this.served = helper.isDefined(served) ? served : true
28 this.included = helper.isDefined(included) ? included : true
29 this.watched = helper.isDefined(watched) ? watched : true
32 var UrlPattern = function (url) {
33 Pattern.call(this, url, false, true, false)
36 var createPatternObject = function (pattern) {
37 if (pattern && helper.isString(pattern)) {
38 return helper.isUrlAbsolute(pattern) ? new UrlPattern(pattern) : new Pattern(pattern)
41 if (helper.isObject(pattern)) {
42 if (pattern.pattern && helper.isString(pattern.pattern)) {
43 return helper.isUrlAbsolute(pattern.pattern) ?
44 new UrlPattern(pattern.pattern) :
45 new Pattern(pattern.pattern, pattern.served, pattern.included, pattern.watched)
48 log.warn('Invalid pattern %s!\n\tObject is missing "pattern" property.', pattern)
49 return new Pattern(null, false, false, false)
52 log.warn('Invalid pattern %s!\n\tExpected string or object with "pattern" property.', pattern)
53 return new Pattern(null, false, false, false)
56 var normalizeUrlRoot = function (urlRoot) {
57 var normalizedUrlRoot = urlRoot
59 if (normalizedUrlRoot.charAt(0) !== '/') {
60 normalizedUrlRoot = '/' + normalizedUrlRoot
63 if (normalizedUrlRoot.charAt(normalizedUrlRoot.length - 1) !== '/') {
64 normalizedUrlRoot = normalizedUrlRoot + '/'
67 if (normalizedUrlRoot !== urlRoot) {
68 log.warn('urlRoot normalized to "%s"', normalizedUrlRoot)
71 return normalizedUrlRoot
74 var normalizeConfig = function (config, configFilePath) {
75 var basePathResolve = function (relativePath) {
76 if (helper.isUrlAbsolute(relativePath)) {
80 if (!helper.isDefined(config.basePath) || !helper.isDefined(relativePath)) {
83 return path.resolve(config.basePath, relativePath)
86 var createPatternMapper = function (resolve) {
87 return function (objectPattern) {
88 objectPattern.pattern = resolve(objectPattern.pattern)
94 if (helper.isString(configFilePath)) {
96 config.basePath = path.resolve(path.dirname(configFilePath), config.basePath)
98 // always ignore the config file itself
99 config.exclude.push(configFilePath)
101 config.basePath = path.resolve(config.basePath || '.')
104 config.files = config.files.map(createPatternObject).map(createPatternMapper(basePathResolve))
105 config.exclude = config.exclude.map(basePathResolve)
107 // normalize paths on windows
108 config.basePath = helper.normalizeWinPath(config.basePath)
109 config.files = config.files.map(createPatternMapper(helper.normalizeWinPath))
110 config.exclude = config.exclude.map(helper.normalizeWinPath)
113 config.urlRoot = normalizeUrlRoot(config.urlRoot)
115 if (config.proxies && config.proxies.hasOwnProperty(config.urlRoot)) {
116 log.warn('"%s" is proxied, you should probably change urlRoot to avoid conflicts',
120 if (config.singleRun && config.autoWatch) {
121 log.debug('autoWatch set to false, because of singleRun')
122 config.autoWatch = false
125 if (!config.singleRun && config.browserDisconnectTolerance) {
126 log.debug('browserDisconnectTolerance set to 0, because of singleRun')
127 config.browserDisconnectTolerance = 0
130 if (helper.isString(config.reporters)) {
131 config.reporters = config.reporters.split(',')
134 if (config.client && config.client.args && !Array.isArray(config.client.args)) {
135 throw new Error('Invalid configuration: client.args must be an array of strings')
138 var defaultClient = config.defaultClient || {}
139 Object.keys(defaultClient).forEach(function (key) {
140 var option = config.client[key]
141 config.client[key] = helper.isDefined(option) ? option : defaultClient[key]
144 // normalize preprocessors
145 var preprocessors = config.preprocessors || {}
146 var normalizedPreprocessors = config.preprocessors = Object.create(null)
148 Object.keys(preprocessors).forEach(function (pattern) {
149 var normalizedPattern = helper.normalizeWinPath(basePathResolve(pattern))
151 normalizedPreprocessors[normalizedPattern] = helper.isString(preprocessors[pattern]) ?
152 [preprocessors[pattern]] : preprocessors[pattern]
155 // define custom launchers/preprocessors/reporters - create an inlined plugin
156 var module = Object.create(null)
157 var hasSomeInlinedPlugin = false
158 var types = ['launcher', 'preprocessor', 'reporter']
160 types.forEach(function (type) {
161 var definitions = config['custom' + helper.ucFirst(type) + 's'] || {}
163 Object.keys(definitions).forEach(function (name) {
164 var definition = definitions[name]
166 if (!helper.isObject(definition)) {
167 return log.warn('Can not define %s %s. Definition has to be an object.', type, name)
170 if (!helper.isString(definition.base)) {
171 return log.warn('Can not define %s %s. Missing base %s.', type, name, type)
174 var token = type + ':' + definition.base
176 args: ['value', definition]
179 module[type + ':' + name] = ['factory', function (injector) {
180 return injector.createChild([locals], [token]).get(token)
182 hasSomeInlinedPlugin = true
186 if (hasSomeInlinedPlugin) {
187 config.plugins.push(module)
193 var Config = function () {
196 this.LOG_DISABLE = constant.LOG_DISABLE
197 this.LOG_ERROR = constant.LOG_ERROR
198 this.LOG_WARN = constant.LOG_WARN
199 this.LOG_INFO = constant.LOG_INFO
200 this.LOG_DEBUG = constant.LOG_DEBUG
202 this.set = function (newConfig) {
203 Object.keys(newConfig).forEach(function (key) {
204 config[key] = newConfig[key]
210 this.port = constant.DEFAULT_PORT
211 this.hostname = constant.DEFAULT_HOSTNAME
215 this.logLevel = constant.LOG_INFO
217 this.autoWatch = true
218 this.autoWatchBatchDelay = 250
219 this.usePolling = process.platform === 'darwin' || process.platform === 'linux'
220 this.reporters = ['progress']
221 this.singleRun = false
223 this.captureTimeout = 60000
225 this.proxyValidateSSL = true
226 this.preprocessors = {}
228 this.reportSlowerThan = 0
229 this.loggers = [constant.CONSOLE_APPENDER]
230 this.transports = ['websocket', 'flashsocket', 'xhr-polling', 'jsonp-polling']
231 this.plugins = ['karma-*']
232 this.defaultClient = this.client = {
237 this.browserDisconnectTimeout = 2000
238 this.browserDisconnectTolerance = 0
239 this.browserNoActivityTimeout = 10000
242 var CONFIG_SYNTAX_HELP = ' module.exports = function(config) {\n' +
244 ' // your config\n' +
248 var parseConfig = function (configFilePath, cliOptions) {
250 if (configFilePath) {
251 log.debug('Loading config %s', configFilePath)
254 configModule = require(configFilePath)
256 if (e.code === 'MODULE_NOT_FOUND' && e.message.indexOf(configFilePath) !== -1) {
257 log.error('File %s does not exist!', configFilePath)
259 log.error('Invalid config file!\n ' + e.stack)
261 var extension = path.extname(configFilePath)
262 if (extension === '.coffee' && !COFFEE_SCRIPT_AVAILABLE) {
263 log.error('You need to install CoffeeScript.\n' +
264 ' npm install coffee-script --save-dev')
265 } else if (extension === '.ls' && !LIVE_SCRIPT_AVAILABLE) {
266 log.error('You need to install LiveScript.\n' +
267 ' npm install LiveScript --save-dev')
270 return process.exit(1)
272 if (!helper.isFunction(configModule)) {
273 log.error('Config file must export a function!\n' + CONFIG_SYNTAX_HELP)
274 return process.exit(1)
277 log.debug('No config file specified.')
278 // if no config file path is passed, we define a dummy config module.
279 configModule = function () {}
282 var config = new Config()
287 log.error('Error in config file!\n', e)
288 return process.exit(1)
291 // merge the config from config file and cliOptions (precendense)
292 config.set(cliOptions)
294 // configure the logger as soon as we can
295 logger.setup(config.logLevel, config.colors, config.loggers)
297 return normalizeConfig(config, configFilePath)
301 exports.parseConfig = parseConfig
302 exports.Pattern = Pattern
303 exports.createPatternObject = createPatternObject
304 exports.Config = Config