ava 3.7.1 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -45,6 +45,9 @@ export interface Assertions {
45
45
  /** Assert that `actual` is [deeply equal](https://github.com/concordancejs/concordance#comparison-details) to `expected`. */
46
46
  deepEqual: DeepEqualAssertion;
47
47
 
48
+ /** Assert that `actual` is like `expected`. */
49
+ like: LikeAssertion;
50
+
48
51
  /** Fail the test. */
49
52
  fail: FailAssertion;
50
53
 
@@ -125,6 +128,14 @@ export interface DeepEqualAssertion {
125
128
  skip(actual: any, expected: any, message?: string): void;
126
129
  }
127
130
 
131
+ export interface LikeAssertion {
132
+ /** Assert that `value` is like `selector`. */
133
+ (value: any, selector: Record<string, unknown>, message?: string): void;
134
+
135
+ /** Skip this assertion. */
136
+ skip(value: any, selector: any, message?: string): void;
137
+ }
138
+
128
139
  export interface FailAssertion {
129
140
  /** Fail the test. */
130
141
  (message?: string): void;
@@ -313,6 +324,7 @@ export interface ExecutionContext<Context = unknown> extends Assertions {
313
324
 
314
325
  log: LogFn;
315
326
  plan: PlanFn;
327
+ teardown: TeardownFn;
316
328
  timeout: TimeoutFn;
317
329
  try: TryFn<Context>;
318
330
  }
@@ -344,6 +356,11 @@ export interface TimeoutFn {
344
356
  (ms: number): void;
345
357
  }
346
358
 
359
+ export interface TeardownFn {
360
+ /** Declare a function to be run after the test has ended. */
361
+ (fn: () => void): void;
362
+ }
363
+
347
364
  export interface TryFn<Context = unknown> {
348
365
  /**
349
366
  * Attempt to run some assertions. The result must be explicitly committed or discarded or else
@@ -416,7 +433,7 @@ export interface CbExecutionContext<Context = unknown> extends ExecutionContext<
416
433
  end(error?: any): void;
417
434
  }
418
435
 
419
- export type ImplementationResult = PromiseLike<void> | Subscribable | void;
436
+ export type ImplementationResult = PromiseLike<void> | Subscribable | void; // eslint-disable-line @typescript-eslint/no-invalid-void-type
420
437
  export type Implementation<Context = unknown> = (t: ExecutionContext<Context>) => ImplementationResult;
421
438
  export type CbImplementation<Context = unknown> = (t: CbExecutionContext<Context>) => ImplementationResult;
422
439
 
package/lib/api.js CHANGED
@@ -16,6 +16,7 @@ const isCi = require('./is-ci');
16
16
  const RunStatus = require('./run-status');
17
17
  const fork = require('./fork');
18
18
  const serializeError = require('./serialize-error');
19
+ const {getApplicableLineNumbers} = require('./line-numbers');
19
20
 
20
21
  function resolveModules(modules) {
21
22
  return arrify(modules).map(name => {
@@ -118,7 +119,11 @@ class Api extends Emittery {
118
119
  if (filter.length === 0) {
119
120
  selectedFiles = testFiles;
120
121
  } else {
121
- selectedFiles = globs.applyTestFileFilter({cwd: this.options.projectDir, filter, testFiles});
122
+ selectedFiles = globs.applyTestFileFilter({
123
+ cwd: this.options.projectDir,
124
+ filter: filter.map(({pattern}) => pattern),
125
+ testFiles
126
+ });
122
127
  }
123
128
  }
124
129
  } catch (error) {
@@ -209,9 +214,11 @@ class Api extends Emittery {
209
214
  return;
210
215
  }
211
216
 
217
+ const lineNumbers = getApplicableLineNumbers(globs.normalizeFileForMatching(apiOptions.projectDir, file), filter);
212
218
  const options = {
213
219
  ...apiOptions,
214
220
  providerStates,
221
+ lineNumbers,
215
222
  recordNewSnapshots: !isCi,
216
223
  // If we're looking for matches, run every single test process in exclusive-only mode
217
224
  runOnlyExclusive: apiOptions.match.length > 0 || runtimeOptions.runOnlyExclusive === true
@@ -223,7 +230,7 @@ class Api extends Emittery {
223
230
  }
224
231
 
225
232
  const worker = fork(file, options, apiOptions.nodeArguments);
226
- runStatus.observeWorker(worker, file);
233
+ runStatus.observeWorker(worker, file, {selectingLines: lineNumbers.length > 0});
227
234
 
228
235
  pendingWorkers.add(worker);
229
236
  worker.promise.then(() => {
package/lib/assert.js CHANGED
@@ -3,11 +3,11 @@ const concordance = require('concordance');
3
3
  const isError = require('is-error');
4
4
  const isPromise = require('is-promise');
5
5
  const concordanceOptions = require('./concordance-options').default;
6
- const concordanceDiffOptions = require('./concordance-options').diff;
6
+ const {CIRCULAR_SELECTOR, isLikeSelector, selectComparable} = require('./like-selector');
7
7
  const snapshotManager = require('./snapshot-manager');
8
8
 
9
9
  function formatDescriptorDiff(actualDescriptor, expectedDescriptor, options) {
10
- options = {...options, ...concordanceDiffOptions};
10
+ options = {...options, ...concordanceOptions};
11
11
  return {
12
12
  label: 'Difference:',
13
13
  formatted: concordance.diffDescriptors(actualDescriptor, expectedDescriptor, options)
@@ -242,7 +242,8 @@ class Assertions {
242
242
  fail = notImplemented,
243
243
  skip = notImplemented,
244
244
  compareWithSnapshot = notImplemented,
245
- powerAssert
245
+ powerAssert,
246
+ experiments = {}
246
247
  } = {}) {
247
248
  const withSkip = assertionFn => {
248
249
  assertionFn.skip = skip;
@@ -387,6 +388,61 @@ class Assertions {
387
388
  }
388
389
  });
389
390
 
391
+ this.like = withSkip((actual, selector, message) => {
392
+ if (!experiments.likeAssertion) {
393
+ fail(new AssertionError({
394
+ assertion: 'like',
395
+ improperUsage: true,
396
+ message: 'You must enable the `likeAssertion` experiment in order to use `t.like()`'
397
+ }));
398
+ return;
399
+ }
400
+
401
+ if (!checkMessage('like', message)) {
402
+ return;
403
+ }
404
+
405
+ if (!isLikeSelector(selector)) {
406
+ fail(new AssertionError({
407
+ assertion: 'like',
408
+ improperUsage: true,
409
+ message: '`t.like()` selector must be a non-empty object',
410
+ values: [formatWithLabel('Called with:', selector)]
411
+ }));
412
+ return;
413
+ }
414
+
415
+ let comparable;
416
+ try {
417
+ comparable = selectComparable(actual, selector);
418
+ } catch (error) {
419
+ if (error === CIRCULAR_SELECTOR) {
420
+ fail(new AssertionError({
421
+ assertion: 'like',
422
+ improperUsage: true,
423
+ message: '`t.like()` selector must not contain circular references',
424
+ values: [formatWithLabel('Called with:', selector)]
425
+ }));
426
+ return;
427
+ }
428
+
429
+ throw error;
430
+ }
431
+
432
+ const result = concordance.compare(comparable, selector, concordanceOptions);
433
+ if (result.pass) {
434
+ pass();
435
+ } else {
436
+ const actualDescriptor = result.actual || concordance.describe(comparable, concordanceOptions);
437
+ const expectedDescriptor = result.expected || concordance.describe(selector, concordanceOptions);
438
+ fail(new AssertionError({
439
+ assertion: 'like',
440
+ message,
441
+ values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)]
442
+ }));
443
+ }
444
+ });
445
+
390
446
  this.throws = withSkip((...args) => {
391
447
  // Since arrow functions do not support 'arguments', we are using rest
392
448
  // operator, so we can determine the total number of arguments passed
@@ -491,16 +547,16 @@ class Assertions {
491
547
  savedError,
492
548
  values: [formatWithLabel(`${wasReturned ? 'Returned promise' : 'Promise'} resolved with:`, value)]
493
549
  });
494
- }, reason => {
550
+ }, error => {
495
551
  assertExpectations({
496
552
  assertion: 'throwsAsync',
497
- actual: reason,
553
+ actual: error,
498
554
  expectations,
499
555
  message,
500
556
  prefix: `${wasReturned ? 'Returned promise' : 'Promise'} rejected with`,
501
557
  savedError
502
558
  });
503
- return reason;
559
+ return error;
504
560
  });
505
561
 
506
562
  pending(intermediate);
package/lib/cli.js CHANGED
@@ -120,7 +120,7 @@ exports.run = async () => { // eslint-disable-line complexity
120
120
  })
121
121
  .command('* [<pattern>...]', 'Run tests', yargs => yargs.options(FLAGS).positional('pattern', {
122
122
  array: true,
123
- describe: 'Glob patterns to select what test files to run. Leave empty if you want AVA to run all test files instead',
123
+ describe: 'Glob patterns to select what test files to run. Leave empty if you want AVA to run all test files instead. Add a colon and specify line numbers of specific tests to run',
124
124
  type: 'string'
125
125
  }))
126
126
  .command(
@@ -143,7 +143,7 @@ exports.run = async () => { // eslint-disable-line complexity
143
143
  }
144
144
  }).positional('pattern', {
145
145
  demand: true,
146
- describe: 'Glob patterns to select a single test file to debug',
146
+ describe: 'Glob patterns to select a single test file to debug. Add a colon and specify line numbers of specific tests to run',
147
147
  type: 'string'
148
148
  }),
149
149
  argv => {
@@ -163,6 +163,7 @@ exports.run = async () => { // eslint-disable-line complexity
163
163
  })
164
164
  .example('$0')
165
165
  .example('$0 test.js')
166
+ .example('$0 test.js:4,7-9')
166
167
  .help();
167
168
 
168
169
  const combined = {...conf};
@@ -258,14 +259,14 @@ exports.run = async () => { // eslint-disable-line complexity
258
259
 
259
260
  const ciParallelVars = require('ci-parallel-vars');
260
261
  const Api = require('./api');
261
- const VerboseReporter = require('./reporters/verbose');
262
- const MiniReporter = require('./reporters/mini');
262
+ const DefaultReporter = require('./reporters/default');
263
263
  const TapReporter = require('./reporters/tap');
264
264
  const Watcher = require('./watcher');
265
265
  const normalizeExtensions = require('./extensions');
266
- const {normalizeGlobs, normalizePatterns} = require('./globs');
266
+ const {normalizeGlobs, normalizePattern} = require('./globs');
267
267
  const normalizeNodeArguments = require('./node-arguments');
268
268
  const validateEnvironmentVariables = require('./environment-variables');
269
+ const {splitPatternAndLineNumbers} = require('./line-numbers');
269
270
  const providerManager = require('./provider-manager');
270
271
 
271
272
  let pkg;
@@ -349,7 +350,12 @@ exports.run = async () => { // eslint-disable-line complexity
349
350
  const match = combined.match === '' ? [] : arrify(combined.match);
350
351
 
351
352
  const input = debug ? debug.files : (argv.pattern || []);
352
- const filter = normalizePatterns(input.map(fileOrPattern => path.relative(projectDir, path.resolve(process.cwd(), fileOrPattern))));
353
+ const filter = input
354
+ .map(pattern => splitPatternAndLineNumbers(pattern))
355
+ .map(({pattern, ...rest}) => ({
356
+ pattern: normalizePattern(path.relative(projectDir, path.resolve(process.cwd(), pattern))),
357
+ ...rest
358
+ }));
353
359
 
354
360
  const api = new Api({
355
361
  cacheEnabled: combined.cache !== false,
@@ -384,25 +390,32 @@ exports.run = async () => { // eslint-disable-line complexity
384
390
  reportStream: process.stdout,
385
391
  stdStream: process.stderr
386
392
  });
387
- } else if (debug !== null || combined.verbose || isCi || !process.stdout.isTTY) {
388
- reporter = new VerboseReporter({
389
- projectDir,
390
- reportStream: process.stdout,
391
- stdStream: process.stderr,
392
- watching: combined.watch
393
- });
394
393
  } else {
395
- reporter = new MiniReporter({
394
+ reporter = new DefaultReporter({
396
395
  projectDir,
397
396
  reportStream: process.stdout,
398
397
  stdStream: process.stderr,
399
- watching: combined.watch
398
+ watching: combined.watch,
399
+ verbose: debug !== null || combined.verbose || isCi || !process.stdout.isTTY
400
400
  });
401
401
  }
402
402
 
403
403
  api.on('run', plan => {
404
404
  reporter.startRun(plan);
405
405
 
406
+ if (process.env.AVA_EMIT_RUN_STATUS_OVER_IPC === 'I\'ll find a payphone baby / Take some time to talk to you') {
407
+ if (process.versions.node >= '12.16.0') {
408
+ plan.status.on('stateChange', evt => {
409
+ process.send(evt);
410
+ });
411
+ } else {
412
+ const v8 = require('v8');
413
+ plan.status.on('stateChange', evt => {
414
+ process.send([...v8.serialize(evt)]);
415
+ });
416
+ }
417
+ }
418
+
406
419
  plan.status.on('stateChange', evt => {
407
420
  if (evt.type === 'interrupt') {
408
421
  reporter.endRun();
@@ -19,7 +19,7 @@ module.exports = (source, options = {}) => {
19
19
  let contents;
20
20
  try {
21
21
  contents = fs.readFileSync(file, 'utf8');
22
- } catch (_) {
22
+ } catch {
23
23
  return null;
24
24
  }
25
25
 
@@ -135,5 +135,4 @@ exports.default = {
135
135
  theme
136
136
  };
137
137
 
138
- exports.diff = {maxDepth: 1, plugins, theme};
139
138
  exports.snapshotManager = {plugins, theme: plainTheme};
package/lib/fork.js CHANGED
@@ -48,7 +48,9 @@ module.exports = (file, options, execArgv = process.execArgv) => {
48
48
  let forcedExit = false;
49
49
  const send = evt => {
50
50
  if (subprocess.connected && !finished && !forcedExit) {
51
- subprocess.send({ava: evt});
51
+ subprocess.send({ava: evt}, () => {
52
+ // Disregard errors.
53
+ });
52
54
  }
53
55
  };
54
56
 
package/lib/globs.js CHANGED
@@ -4,7 +4,7 @@ const globby = require('globby');
4
4
  const ignoreByDefault = require('ignore-by-default');
5
5
  const picomatch = require('picomatch');
6
6
  const slash = require('slash');
7
- const {levels: providerLevels} = require('./provider-manager');
7
+ const providerManager = require('./provider-manager');
8
8
 
9
9
  const defaultIgnorePatterns = [...ignoreByDefault.directories(), '**/node_modules'];
10
10
  const defaultPicomatchIgnorePatterns = [
@@ -23,23 +23,27 @@ const defaultIgnoredByWatcherPatterns = [
23
23
 
24
24
  const buildExtensionPattern = extensions => extensions.length === 1 ? extensions[0] : `{${extensions.join(',')}}`;
25
25
 
26
- function normalizePatterns(patterns) {
26
+ function normalizePattern(pattern) {
27
27
  // Always use `/` in patterns, harmonizing matching across platforms
28
28
  if (process.platform === 'win32') {
29
- patterns = patterns.map(pattern => slash(pattern));
29
+ pattern = slash(pattern);
30
30
  }
31
31
 
32
- return patterns.map(pattern => {
33
- if (pattern.startsWith('./')) {
34
- return pattern.slice(2);
35
- }
32
+ if (pattern.startsWith('./')) {
33
+ return pattern.slice(2);
34
+ }
36
35
 
37
- if (pattern.startsWith('!./')) {
38
- return `!${pattern.slice(3)}`;
39
- }
36
+ if (pattern.startsWith('!./')) {
37
+ return `!${pattern.slice(3)}`;
38
+ }
40
39
 
41
- return pattern;
42
- });
40
+ return pattern;
41
+ }
42
+
43
+ exports.normalizePattern = normalizePattern;
44
+
45
+ function normalizePatterns(patterns) {
46
+ return patterns.map(pattern => normalizePattern(pattern));
43
47
  }
44
48
 
45
49
  exports.normalizePatterns = normalizePatterns;
@@ -85,7 +89,7 @@ function normalizeGlobs({extensions, files: filePatterns, ignoredByWatcher: igno
85
89
  }
86
90
 
87
91
  for (const {level, main} of providers) {
88
- if (level >= providerLevels.pathRewrites) {
92
+ if (level >= providerManager.levels.pathRewrites) {
89
93
  ({filePatterns, ignoredByWatcherPatterns} = main.updateGlobs({filePatterns, ignoredByWatcherPatterns}));
90
94
  }
91
95
  }
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+ function isLikeSelector(selector) {
3
+ return selector !== null &&
4
+ typeof selector === 'object' &&
5
+ Reflect.getPrototypeOf(selector) === Object.prototype &&
6
+ Reflect.ownKeys(selector).length > 0;
7
+ }
8
+
9
+ exports.isLikeSelector = isLikeSelector;
10
+
11
+ const CIRCULAR_SELECTOR = new Error('Encountered a circular selector');
12
+ exports.CIRCULAR_SELECTOR = CIRCULAR_SELECTOR;
13
+
14
+ function selectComparable(lhs, selector, circular = new Set()) {
15
+ if (circular.has(selector)) {
16
+ throw CIRCULAR_SELECTOR;
17
+ }
18
+
19
+ circular.add(selector);
20
+
21
+ if (lhs === null || typeof lhs !== 'object') {
22
+ return lhs;
23
+ }
24
+
25
+ const comparable = {};
26
+ for (const [key, rhs] of Object.entries(selector)) {
27
+ if (isLikeSelector(rhs)) {
28
+ comparable[key] = selectComparable(Reflect.get(lhs, key), rhs, circular);
29
+ } else {
30
+ comparable[key] = Reflect.get(lhs, key);
31
+ }
32
+ }
33
+
34
+ return comparable;
35
+ }
36
+
37
+ exports.selectComparable = selectComparable;
@@ -0,0 +1,64 @@
1
+ 'use strict';
2
+
3
+ const picomatch = require('picomatch');
4
+ const flatten = require('lodash/flatten');
5
+
6
+ const NUMBER_REGEX = /^\d+$/;
7
+ const RANGE_REGEX = /^(?<startGroup>\d+)-(?<endGroup>\d+)$/;
8
+ const LINE_NUMBERS_REGEX = /^(?:\d+(?:-\d+)?,?)+$/;
9
+ const DELIMITER = ':';
10
+
11
+ const distinctArray = array => [...new Set(array)];
12
+ const sortNumbersAscending = array => {
13
+ const sorted = [...array];
14
+ sorted.sort((a, b) => a - b);
15
+ return sorted;
16
+ };
17
+
18
+ const parseNumber = string => Number.parseInt(string, 10);
19
+ const removeAllWhitespace = string => string.replace(/\s/g, '');
20
+ const range = (start, end) => new Array(end - start + 1).fill(start).map((element, index) => element + index);
21
+
22
+ const parseLineNumbers = suffix => sortNumbersAscending(distinctArray(flatten(
23
+ suffix.split(',').map(part => {
24
+ if (NUMBER_REGEX.test(part)) {
25
+ return parseNumber(part);
26
+ }
27
+
28
+ const {groups: {startGroup, endGroup}} = RANGE_REGEX.exec(part);
29
+ const start = parseNumber(startGroup);
30
+ const end = parseNumber(endGroup);
31
+
32
+ if (start > end) {
33
+ return range(end, start);
34
+ }
35
+
36
+ return range(start, end);
37
+ })
38
+ )));
39
+
40
+ function splitPatternAndLineNumbers(pattern) {
41
+ const parts = pattern.split(DELIMITER);
42
+ if (parts.length === 1) {
43
+ return {pattern, lineNumbers: null};
44
+ }
45
+
46
+ const suffix = removeAllWhitespace(parts.pop());
47
+ if (!LINE_NUMBERS_REGEX.test(suffix)) {
48
+ return {pattern, lineNumbers: null};
49
+ }
50
+
51
+ return {pattern: parts.join(DELIMITER), lineNumbers: parseLineNumbers(suffix)};
52
+ }
53
+
54
+ exports.splitPatternAndLineNumbers = splitPatternAndLineNumbers;
55
+
56
+ function getApplicableLineNumbers(normalizedFilePath, filter) {
57
+ return sortNumbersAscending(distinctArray(flatten(
58
+ filter
59
+ .filter(({pattern, lineNumbers}) => lineNumbers && picomatch.isMatch(normalizedFilePath, pattern))
60
+ .map(({lineNumbers}) => lineNumbers)
61
+ )));
62
+ }
63
+
64
+ exports.getApplicableLineNumbers = getApplicableLineNumbers;
@@ -7,7 +7,7 @@ const pkgConf = require('pkg-conf');
7
7
 
8
8
  const NO_SUCH_FILE = Symbol('no ava.config.js file');
9
9
  const MISSING_DEFAULT_EXPORT = Symbol('missing default export');
10
- const EXPERIMENTS = new Set();
10
+ const EXPERIMENTS = new Set(['likeAssertion', 'reverseTeardowns']);
11
11
 
12
12
  // *Very* rudimentary support for loading ava.config.js files containing an `export default` statement.
13
13
  const evaluateJsConfig = configFile => {
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+ const StackUtils = require('stack-utils');
3
+
4
+ const stackUtils = new StackUtils({
5
+ ignoredPackages: [
6
+ '@ava/babel',
7
+ '@ava/require-precompiled',
8
+ '@ava/typescript',
9
+ 'append-transform',
10
+ 'ava',
11
+ 'empower-core',
12
+ 'esm',
13
+ 'nyc'
14
+ ],
15
+ internals: [
16
+ // AVA internals, which ignoredPackages don't ignore when we run our own unit tests.
17
+ /\/ava\/(?:lib\/|lib\/worker\/)?[\w-]+\.js:\d+:\d+\)?$/,
18
+ // Only ignore Node.js internals that really are not useful for debugging.
19
+ ...StackUtils.nodeInternals().filter(regexp => !/\(internal/.test(regexp.source)),
20
+ /\(internal\/process\/task_queues\.js:\d+:\d+\)$/,
21
+ /\(internal\/modules\/cjs\/.+?\.js:\d+:\d+\)$/,
22
+ /async Promise\.all \(index/,
23
+ /new Promise \(<anonymous>\)/
24
+ ]
25
+ });
26
+
27
+ /*
28
+ * Given a string value of the format generated for the `stack` property of a
29
+ * V8 error object, return a string that contains only stack frame information
30
+ * for frames that have relevance to the consumer.
31
+ *
32
+ * For example, given the following string value:
33
+ *
34
+ * ```
35
+ * Error
36
+ * at inner (/home/ava/ex.js:7:12)
37
+ * at /home/ava/ex.js:12:5
38
+ * at outer (/home/ava/ex.js:13:4)
39
+ * at Object.<anonymous> (/home/ava/ex.js:14:3)
40
+ * at Module._compile (module.js:570:32)
41
+ * at Object.Module._extensions..js (module.js:579:10)
42
+ * at Module.load (module.js:487:32)
43
+ * at tryModuleLoad (module.js:446:12)
44
+ * at Function.Module._load (module.js:438:3)
45
+ * at Module.runMain (module.js:604:10)
46
+ * ```
47
+ *
48
+ * ...this function returns the following string value:
49
+ *
50
+ * ```
51
+ * inner (/home/ava/ex.js:7:12)
52
+ * /home/ava/ex.js:12:5
53
+ * outer (/home/ava/ex.js:13:4)
54
+ * Object.<anonymous> (/home/ava/ex.js:14:3)
55
+ * Module._compile (module.js:570:32)
56
+ * Object.Module._extensions..js (module.js:579:10)
57
+ * Module.load (module.js:487:32)
58
+ * tryModuleLoad (module.js:446:12)
59
+ * Function.Module._load (module.js:438:3)
60
+ * Module.runMain (module.js:604:10)
61
+ * ```
62
+ */
63
+ module.exports = stack => {
64
+ if (!stack) {
65
+ return [];
66
+ }
67
+
68
+ return stackUtils.clean(stack)
69
+ .trim()
70
+ .split('\n')
71
+ .map(line => line.trim())
72
+ .filter(line => line !== '');
73
+ };
@@ -11,6 +11,7 @@ module.exports = {
11
11
  duration: chalk.gray.dim,
12
12
  errorSource: chalk.gray,
13
13
  errorStack: chalk.gray,
14
+ errorStackInternal: chalk.gray.dim,
14
15
  stack: chalk.red,
15
16
  information: chalk.magenta
16
17
  };