Comparing version 6.2.0 to 6.3.0
@@ -10,2 +10,3 @@ 'use strict' | ||
const constant = require('./constants') | ||
const cfg = require('./config') | ||
@@ -281,23 +282,46 @@ function processArgs (argv, options, fs, path) { | ||
exports.run = () => { | ||
const config = exports.process() | ||
exports.run = async () => { | ||
const cliOptions = exports.process() | ||
const cmd = cliOptions.cmd // prevent config from changing the command | ||
const cmdNeedsConfig = cmd === 'start' || cmd === 'run' || cmd === 'stop' | ||
if (cmdNeedsConfig) { | ||
let config | ||
try { | ||
config = await cfg.parseConfig( | ||
cliOptions.configFile, | ||
cliOptions, | ||
{ | ||
promiseConfig: true, | ||
throwErrors: true | ||
} | ||
) | ||
} catch (karmaConfigException) { | ||
// The reject reason/exception isn't used to log a message since | ||
// parseConfig already calls a configured logger method with an almost | ||
// identical message. | ||
switch (config.cmd) { | ||
case 'start': | ||
new Server(config).start() | ||
break | ||
case 'run': | ||
require('./runner') | ||
.run(config) | ||
.on('progress', printRunnerProgress) | ||
break | ||
case 'stop': | ||
require('./stopper').stop(config) | ||
break | ||
case 'init': | ||
require('./init').init(config) | ||
break | ||
case 'completion': | ||
require('./completion').completion(config) | ||
break | ||
// The `run` function is a private application, not a public API. We don't | ||
// need to worry about process.exit vs throw vs promise rejection here. | ||
process.exit(1) | ||
} | ||
switch (cmd) { | ||
case 'start': { | ||
const server = new Server(config) | ||
await server.start() | ||
return server | ||
} | ||
case 'run': | ||
return require('./runner') | ||
.run(config) | ||
.on('progress', printRunnerProgress) | ||
case 'stop': | ||
return require('./stopper').stop(config) | ||
} | ||
} else { | ||
switch (cmd) { | ||
case 'init': | ||
return require('./init').init(cliOptions) | ||
case 'completion': | ||
return require('./completion').completion(cliOptions) | ||
} | ||
} | ||
@@ -304,0 +328,0 @@ } |
@@ -271,2 +271,5 @@ 'use strict' | ||
/** | ||
* @class | ||
*/ | ||
class Config { | ||
@@ -355,8 +358,57 @@ constructor () { | ||
/** | ||
* Retrieve a parsed and finalized Karma `Config` instance. This `karmaConfig` | ||
* object may be used to configure public API methods such a `Server`, | ||
* `runner.run`, and `stopper.stop`. | ||
* | ||
* @param {?string} [configFilePath=null] | ||
* A string representing a file system path pointing to the config file | ||
* whose default export is a function that will be used to set Karma | ||
* configuration options. This function will be passed an instance of the | ||
* `Config` class as its first argument. If this option is not provided, | ||
* then only the options provided by the `cliOptions` argument will be | ||
* set. | ||
* @param {Object} cliOptions | ||
* An object whose values will take priority over options set in the | ||
* config file. The config object passed to function exported by the | ||
* config file will already have these options applied. Any changes the | ||
* config file makes to these options will effectively be ignored in the | ||
* final configuration. | ||
* | ||
* `cliOptions` all the same options as the config file and is applied | ||
* using the same `config.set()` method. | ||
* @param {Object} parseOptions | ||
* @param {boolean} [parseOptions.promiseConfig=false] | ||
* When `true`, a promise that resolves to a `Config` object will be | ||
* returned. This also allows the function exported by config files (if | ||
* provided) to be asynchronous by returning a promise. Resolving this | ||
* promise indicates that all async activity has completed. The resolution | ||
* value itself is ignored, all configuration must be done with | ||
* `config.set`. | ||
* @param {boolean} [parseOptions.throwErrors=false] | ||
* When `true`, process exiting on critical failures will be disabled. In | ||
* The error will be thrown as an exception. If | ||
* `parseOptions.promiseConfig` is also `true`, then the error will | ||
* instead be used as the promise's reject reason. | ||
* @returns {Config|Promise<Config>} | ||
*/ | ||
function parseConfig (configFilePath, cliOptions, parseOptions) { | ||
const promiseConfig = parseOptions && parseOptions.promiseConfig === true | ||
const throwErrors = parseOptions && parseOptions.throwErrors === true | ||
const shouldSetupLoggerEarly = promiseConfig | ||
if (shouldSetupLoggerEarly) { | ||
// `setupFromConfig` provides defaults for `colors` and `logLevel`. | ||
// `setup` provides defaults for `appenders` | ||
// The first argument MUST BE an object | ||
logger.setupFromConfig({}) | ||
} | ||
function fail () { | ||
log.error(...arguments) | ||
if (parseOptions && parseOptions.throwErrors === true) { | ||
if (throwErrors) { | ||
const errorMessage = Array.from(arguments).join(' ') | ||
throw new Error(errorMessage) | ||
const err = new Error(errorMessage) | ||
if (promiseConfig) { | ||
return Promise.reject(err) | ||
} | ||
throw err | ||
} else { | ||
@@ -416,30 +468,72 @@ const warningMessage = | ||
let configModuleReturn | ||
try { | ||
configModule(config) | ||
configModuleReturn = configModule(config) | ||
} catch (e) { | ||
return fail('Error in config file!\n', e) | ||
} | ||
function finalizeConfig (config) { | ||
// merge the config from config file and cliOptions (precedence) | ||
config.set(cliOptions) | ||
// merge the config from config file and cliOptions (precedence) | ||
config.set(cliOptions) | ||
// if the user changed listenAddress, but didn't set a hostname, warn them | ||
if (config.hostname === null && config.listenAddress !== null) { | ||
log.warn(`ListenAddress was set to ${config.listenAddress} but hostname was left as the default: ` + | ||
// if the user changed listenAddress, but didn't set a hostname, warn them | ||
if (config.hostname === null && config.listenAddress !== null) { | ||
log.warn(`ListenAddress was set to ${config.listenAddress} but hostname was left as the default: ` + | ||
`${defaultHostname}. If your browsers fail to connect, consider changing the hostname option.`) | ||
} | ||
// restore values that weren't overwritten by the user | ||
if (config.hostname === null) { | ||
config.hostname = defaultHostname | ||
} | ||
if (config.listenAddress === null) { | ||
config.listenAddress = defaultListenAddress | ||
} | ||
} | ||
// restore values that weren't overwritten by the user | ||
if (config.hostname === null) { | ||
config.hostname = defaultHostname | ||
} | ||
if (config.listenAddress === null) { | ||
config.listenAddress = defaultListenAddress | ||
} | ||
// configure the logger as soon as we can | ||
logger.setup(config.logLevel, config.colors, config.loggers) | ||
// configure the logger as soon as we can | ||
logger.setup(config.logLevel, config.colors, config.loggers) | ||
log.debug(configFilePath ? `Loading config ${configFilePath}` : 'No config file specified.') | ||
log.debug(configFilePath ? `Loading config ${configFilePath}` : 'No config file specified.') | ||
return normalizeConfig(config, configFilePath) | ||
return normalizeConfig(config, configFilePath) | ||
} | ||
/** | ||
* Return value is a function or (non-null) object that has a `then` method. | ||
* | ||
* @type {boolean} | ||
* @see {@link https://promisesaplus.com/} | ||
*/ | ||
const returnIsThenable = ( | ||
( | ||
(configModuleReturn != null && typeof configModuleReturn === 'object') || | ||
typeof configModuleReturn === 'function' | ||
) && typeof configModuleReturn.then === 'function' | ||
) | ||
if (returnIsThenable) { | ||
if (promiseConfig !== true) { | ||
const errorMessage = | ||
'The `parseOptions.promiseConfig` option must be set to `true` to ' + | ||
'enable promise return values from configuration files. ' + | ||
'Example: `parseConfig(path, cliOptions, { promiseConfig: true })`' | ||
return fail(errorMessage) | ||
} | ||
return configModuleReturn.then( | ||
function onKarmaConfigModuleFulfilled (/* ignoredResolutionValue */) { | ||
return finalizeConfig(config) | ||
}, | ||
function onKarmaConfigModuleRejected (reason) { | ||
return fail('Error in config file!\n', reason) | ||
} | ||
) | ||
} else { | ||
if (promiseConfig) { | ||
try { | ||
return Promise.resolve(finalizeConfig(config)) | ||
} catch (exception) { | ||
return Promise.reject(exception) | ||
} | ||
} else { | ||
return finalizeConfig(config) | ||
} | ||
} | ||
} | ||
@@ -446,0 +540,0 @@ |
@@ -35,10 +35,35 @@ 'use strict' | ||
// TODO(vojta): read config file (port, host, urlRoot) | ||
function run (config, done) { | ||
config = config || {} | ||
logger.setupFromConfig(config) | ||
function run (cliOptionsOrConfig, done) { | ||
cliOptionsOrConfig = cliOptionsOrConfig || {} | ||
done = helper.isFunction(done) ? done : process.exit | ||
config = cfg.parseConfig(config.configFile, config) | ||
let config | ||
if (cliOptionsOrConfig instanceof cfg.Config) { | ||
config = cliOptionsOrConfig | ||
} else { | ||
logger.setupFromConfig({ | ||
colors: cliOptionsOrConfig.colors, | ||
logLevel: cliOptionsOrConfig.logLevel | ||
}) | ||
const deprecatedCliOptionsMessage = | ||
'Passing raw CLI options to `runner(config, done)` is deprecated. Use ' + | ||
'`parseConfig(configFilePath, cliOptions, {promiseConfig: true, throwErrors: true})` ' + | ||
'to prepare a processed `Config` instance and pass that as the ' + | ||
'`config` argument instead.' | ||
log.warn(deprecatedCliOptionsMessage) | ||
try { | ||
config = cfg.parseConfig( | ||
cliOptionsOrConfig.configFile, | ||
cliOptionsOrConfig, | ||
{ | ||
promiseConfig: false, | ||
throwErrors: true | ||
} | ||
) | ||
} catch (parseConfigError) { | ||
// TODO: change how `done` falls back to exit in next major version | ||
// SEE: https://github.com/karma-runner/karma/pull/3635#discussion_r565399378 | ||
done(1) | ||
} | ||
} | ||
let exitCode = 1 | ||
@@ -45,0 +70,0 @@ const emitter = new EventEmitter() |
@@ -58,18 +58,39 @@ 'use strict' | ||
class Server extends KarmaEventEmitter { | ||
constructor (cliOptions, done) { | ||
constructor (cliOptionsOrConfig, done) { | ||
super() | ||
logger.setupFromConfig(cliOptions) | ||
cliOptionsOrConfig = cliOptionsOrConfig || {} | ||
this.log = logger.create('karma-server') | ||
done = helper.isFunction(done) ? done : process.exit | ||
this.loadErrors = [] | ||
let config | ||
try { | ||
config = cfg.parseConfig(cliOptions.configFile, cliOptions, { throwErrors: true }) | ||
} catch (parseConfigError) { | ||
// TODO: change how `done` falls back to exit in next major version | ||
// SEE: https://github.com/karma-runner/karma/pull/3635#discussion_r565399378 | ||
(done || process.exit)(1) | ||
return | ||
if (cliOptionsOrConfig instanceof cfg.Config) { | ||
config = cliOptionsOrConfig | ||
} else { | ||
logger.setupFromConfig({ | ||
colors: cliOptionsOrConfig.colors, | ||
logLevel: cliOptionsOrConfig.logLevel | ||
}) | ||
const deprecatedCliOptionsMessage = | ||
'Passing raw CLI options to `new Server(config, done)` is ' + | ||
'deprecated. Use ' + | ||
'`parseConfig(configFilePath, cliOptions, {promiseConfig: true, throwErrors: true})` ' + | ||
'to prepare a processed `Config` instance and pass that as the ' + | ||
'`config` argument instead.' | ||
this.log.warn(deprecatedCliOptionsMessage) | ||
try { | ||
config = cfg.parseConfig( | ||
cliOptionsOrConfig.configFile, | ||
cliOptionsOrConfig, | ||
{ | ||
promiseConfig: false, | ||
throwErrors: true | ||
} | ||
) | ||
} catch (parseConfigError) { | ||
// TODO: change how `done` falls back to exit in next major version | ||
// SEE: https://github.com/karma-runner/karma/pull/3635#discussion_r565399378 | ||
done(1) | ||
return | ||
} | ||
} | ||
@@ -76,0 +97,0 @@ |
@@ -6,9 +6,37 @@ const http = require('http') | ||
exports.stop = function (config, done) { | ||
config = config || {} | ||
logger.setupFromConfig(config) | ||
exports.stop = function (cliOptionsOrConfig, done) { | ||
cliOptionsOrConfig = cliOptionsOrConfig || {} | ||
const log = logger.create('stopper') | ||
done = helper.isFunction(done) ? done : process.exit | ||
config = cfg.parseConfig(config.configFile, config) | ||
let config | ||
if (cliOptionsOrConfig instanceof cfg.Config) { | ||
config = cliOptionsOrConfig | ||
} else { | ||
logger.setupFromConfig({ | ||
colors: cliOptionsOrConfig.colors, | ||
logLevel: cliOptionsOrConfig.logLevel | ||
}) | ||
const deprecatedCliOptionsMessage = | ||
'Passing raw CLI options to `stopper(config, done)` is deprecated. Use ' + | ||
'`parseConfig(configFilePath, cliOptions, {promiseConfig: true, throwErrors: true})` ' + | ||
'to prepare a processed `Config` instance and pass that as the ' + | ||
'`config` argument instead.' | ||
log.warn(deprecatedCliOptionsMessage) | ||
try { | ||
config = cfg.parseConfig( | ||
cliOptionsOrConfig.configFile, | ||
cliOptionsOrConfig, | ||
{ | ||
promiseConfig: false, | ||
throwErrors: true | ||
} | ||
) | ||
} catch (parseConfigError) { | ||
// TODO: change how `done` falls back to exit in next major version | ||
// SEE: https://github.com/karma-runner/karma/pull/3635#discussion_r565399378 | ||
done(1) | ||
} | ||
} | ||
const request = http.request({ | ||
@@ -15,0 +43,0 @@ hostname: config.hostname, |
@@ -297,2 +297,3 @@ { | ||
"Nick Payne <nick@kurai.co.uk>", | ||
"Nick Petruzzelli <code.npetruzzelli@gmail.com>", | ||
"Nick Petruzzelli <npetruzzelli@users.noreply.github.com>", | ||
@@ -493,3 +494,3 @@ "Nick Williams <mr.nicksta@gmail.com>", | ||
}, | ||
"version": "6.2.0", | ||
"version": "6.3.0", | ||
"license": "MIT", | ||
@@ -496,0 +497,0 @@ "husky": { |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
521450
10598