UNPKG

24.9 kBJavaScriptView Raw
1"use strict";
2// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
3// See LICENSE in the project root for license information.
4Object.defineProperty(exports, "__esModule", { value: true });
5const colors = require("colors");
6const path = require("path");
7// eslint-disable-next-line
8const prettyTime = require('pretty-hrtime');
9const state = require("./State");
10const config_1 = require("./config");
11const index_1 = require("./index");
12const WROTE_ERROR_KEY = '__gulpCoreBuildWroteError';
13let wiredUpErrorHandling = false;
14let duringFastExit = false;
15// eslint-disable-next-line @typescript-eslint/no-explicit-any
16const globalInstance = global;
17const localCache = globalInstance.__loggingCache = globalInstance.__loggingCache || {
18 warnings: [],
19 errors: [],
20 testsRun: 0,
21 subTasksRun: 0,
22 testsPassed: 0,
23 testsFailed: 0,
24 testsFlakyFailed: 0,
25 testsSkipped: 0,
26 taskRun: 0,
27 taskErrors: 0,
28 coverageResults: 0,
29 coveragePass: 0,
30 coverageTotal: 0,
31 totalTaskHrTime: undefined,
32 totalTaskSrc: 0,
33 wroteSummary: false,
34 writingSummary: false,
35 writeSummaryCallbacks: [],
36 exitCode: 0,
37 writeSummaryLogs: [],
38 errorAndWarningSuppressions: [],
39 gulp: undefined,
40 gulpErrorCallback: undefined,
41 gulpStopCallback: undefined,
42 shouldLogErrorsDuringSummary: false,
43 shouldLogWarningsDuringSummary: false
44};
45if (!localCache.start) {
46 localCache.start = process.hrtime();
47}
48function isVerbose() {
49 return config_1.getFlagValue('verbose');
50}
51// eslint-disable-next-line @typescript-eslint/no-explicit-any
52function formatError(e) {
53 if (!e.err) {
54 if (isVerbose()) {
55 return e.message + '\r\n' + e.stack;
56 }
57 else {
58 return e.message;
59 }
60 }
61 // PluginError
62 if (typeof e.err.showStack === 'boolean') {
63 return e.err.toString() + (e.err.stack && isVerbose() ? '\r\n' + e.err.stack : '');
64 }
65 // normal error
66 if (e.err.stack) {
67 if (isVerbose()) {
68 return e.err.stack;
69 }
70 else {
71 return e.err.message;
72 }
73 }
74 // unknown (string, number, etc.)
75 if (typeof (Error) === 'undefined') {
76 if (isVerbose()) {
77 return e.message + '\r\n' + e.stack;
78 }
79 else {
80 return e.message;
81 }
82 }
83 else {
84 let output = String(e.err);
85 try {
86 output = JSON.stringify(e.err);
87 }
88 catch (e) {
89 // Do nothing
90 }
91 if (isVerbose()) {
92 return new Error(output).stack;
93 }
94 else {
95 return new Error(output).message;
96 }
97 }
98}
99function afterStreamFlushed(streamName, callback) {
100 if (duringFastExit) {
101 callback();
102 }
103 else {
104 const stream = process[streamName];
105 const outputWritten = stream.write('');
106 if (outputWritten) {
107 setTimeout(() => {
108 callback();
109 }, 250);
110 }
111 else {
112 stream.once('drain', () => {
113 setTimeout(() => {
114 callback();
115 }, 250);
116 });
117 }
118 }
119}
120function afterStreamsFlushed(callback) {
121 afterStreamFlushed('stdout', () => {
122 afterStreamFlushed('stderr', () => {
123 callback();
124 });
125 });
126}
127function writeSummary(callback) {
128 localCache.writeSummaryCallbacks.push(callback);
129 if (!localCache.writingSummary) {
130 localCache.writingSummary = true;
131 // flush the log
132 afterStreamsFlushed(() => {
133 const shouldRelogIssues = config_1.getFlagValue('relogIssues');
134 log(colors.magenta('==================[ Finished ]=================='));
135 const warnings = getWarnings();
136 if (shouldRelogIssues) {
137 for (let x = 0; x < warnings.length; x++) {
138 console.error(colors.yellow(warnings[x]));
139 }
140 }
141 if (shouldRelogIssues && (localCache.taskErrors > 0 || getErrors().length)) {
142 const errors = getErrors();
143 for (let x = 0; x < errors.length; x++) {
144 console.error(colors.red(errors[x]));
145 }
146 }
147 afterStreamsFlushed(() => {
148 for (const writeSummaryString of localCache.writeSummaryLogs) {
149 log(writeSummaryString);
150 }
151 const totalDuration = process.hrtime(getStart());
152 const name = state.builtPackage.name || 'with unknown name';
153 const version = state.builtPackage.version || 'unknown';
154 log(`Project ${name} version:`, colors.yellow(version));
155 log('Build tools version:', colors.yellow(state.coreBuildPackage.version || ''));
156 log('Node version:', colors.yellow(process.version));
157 // log('Create tasks duration:', colors.yellow(prettyTime(localCache.taskCreationTime)));
158 // log('Read src tasks duration:', colors.yellow(prettyTime(localCache.totalTaskHrTime)));
159 log('Total duration:', colors.yellow(prettyTime(totalDuration)));
160 // log(`Tasks run: ${colors.yellow(localCache.taskRun + '')} ` +
161 // `Subtasks run: ${colors.yellow(localCache.subTasksRun + '')}`);
162 if (localCache.testsRun > 0) {
163 log('Tests results -', 'Passed:', colors.green(localCache.testsPassed + ''), 'Failed:', colors.red(localCache.testsFailed + ''),
164 // 'Flaky:', colors.yellow(localCache.testsFlakyFailed + ''),
165 'Skipped:', colors.yellow(localCache.testsSkipped + ''));
166 }
167 if (localCache.coverageResults > 0) {
168 log('Coverage results -', 'Passed:', colors.green(localCache.coveragePass + ''), 'Failed:', colors.red((localCache.coverageResults - localCache.coveragePass) + ''), 'Avg. Cov.:', colors.yellow(Math.floor(localCache.coverageTotal / localCache.coverageResults) + '%'));
169 }
170 if (getWarnings().length) {
171 log('Task warnings:', colors.yellow(getWarnings().length.toString()));
172 }
173 let totalErrors = 0;
174 if (localCache.taskErrors > 0 || getErrors().length) {
175 totalErrors = (localCache.taskErrors + getErrors().length);
176 log('Task errors:', colors.red(totalErrors + ''));
177 }
178 localCache.wroteSummary = true;
179 const callbacks = localCache.writeSummaryCallbacks;
180 localCache.writeSummaryCallbacks = [];
181 for (const writeSummaryCallback of callbacks) {
182 writeSummaryCallback();
183 }
184 });
185 });
186 }
187 else if (localCache.wroteSummary) {
188 const callbacks = localCache.writeSummaryCallbacks;
189 localCache.writeSummaryCallbacks = [];
190 for (const writeSummaryCallback of callbacks) {
191 writeSummaryCallback();
192 }
193 }
194}
195// eslint-disable-next-line @typescript-eslint/no-explicit-any
196function _writeTaskError(e) {
197 if (!e || !(e.err && e.err[WROTE_ERROR_KEY])) {
198 writeError(e);
199 localCache.taskErrors++;
200 }
201}
202function exitProcess(errorCode) {
203 if (!localCache.watchMode) {
204 process.stdout.write('', () => {
205 process.exit(errorCode);
206 });
207 }
208}
209function wireUpProcessErrorHandling(shouldWarningsFailBuild) {
210 if (!wiredUpErrorHandling) {
211 wiredUpErrorHandling = true;
212 let wroteToStdErr = false;
213 if (shouldWarningsFailBuild) {
214 const oldStdErr = process.stderr.write;
215 process.stderr.write = function (text) {
216 if (text.toString()) {
217 wroteToStdErr = true;
218 return oldStdErr.apply(process.stderr, arguments);
219 }
220 return true;
221 };
222 }
223 process.on('exit', (code) => {
224 duringFastExit = true;
225 if (!global['dontWatchExit']) { // eslint-disable-line dot-notation
226 if (!localCache.wroteSummary) {
227 localCache.wroteSummary = true;
228 console.log('About to exit with code:', code);
229 console.error('Process terminated before summary could be written, possible error in async code not ' +
230 'continuing!');
231 console.log('Trying to exit with exit code 1');
232 exitProcess(1);
233 }
234 else {
235 if (localCache.exitCode !== 0) {
236 console.log(`Exiting with exit code: ${localCache.exitCode}`);
237 exitProcess(localCache.exitCode);
238 }
239 else if (wroteToStdErr) {
240 console.error(`The build failed because a task wrote output to stderr.`);
241 console.log(`Exiting with exit code: 1`);
242 exitProcess(1);
243 }
244 }
245 }
246 });
247 process.on('uncaughtException', (err) => {
248 console.error(err);
249 _writeTaskError(err);
250 writeSummary(() => {
251 exitProcess(1);
252 if (localCache.gulpErrorCallback) {
253 localCache.gulpErrorCallback(err);
254 }
255 });
256 });
257 }
258}
259function markErrorAsWritten(err) {
260 try {
261 err[WROTE_ERROR_KEY] = true;
262 }
263 catch (e) {
264 // Do Nothing
265 }
266}
267/**
268 * Adds a message to be displayed in the summary after execution is complete.
269 * @param value - the message to display
270 * @public
271 */
272function logSummary(value) {
273 localCache.writeSummaryLogs.push(value);
274}
275exports.logSummary = logSummary;
276/**
277 * Log a message to the console
278 * @param args - the messages to log to the console
279 * @public
280 */
281function log(...args) {
282 const currentTime = new Date();
283 const timestamp = colors.gray([padTimePart(currentTime.getHours()),
284 padTimePart(currentTime.getMinutes()),
285 padTimePart(currentTime.getSeconds())]
286 .join(':'));
287 console.log(`[${timestamp}] ${args.join('')}`);
288}
289exports.log = log;
290function padTimePart(timepart) {
291 return timepart >= 10 ? timepart.toString(10) : `0${timepart.toString(10)}`;
292}
293/**
294 * Resets the state of the logging cache
295 * @public
296 */
297function reset() {
298 localCache.start = process.hrtime();
299 localCache.warnings = [];
300 localCache.errors = [];
301 localCache.coverageResults = 0;
302 localCache.coveragePass = 0;
303 localCache.coverageTotal = 0;
304 localCache.taskRun = 0;
305 localCache.subTasksRun = 0;
306 localCache.taskErrors = 0;
307 localCache.totalTaskHrTime = undefined;
308 localCache.totalTaskSrc = 0;
309 localCache.wroteSummary = false;
310 localCache.writingSummary = false;
311 localCache.writeSummaryCallbacks = [];
312 localCache.testsRun = 0;
313 localCache.testsPassed = 0;
314 localCache.testsFailed = 0;
315 localCache.testsFlakyFailed = 0;
316 localCache.testsSkipped = 0;
317 localCache.writeSummaryLogs = [];
318}
319exports.reset = reset;
320/**
321 * The result of a functional test run
322 * @public
323 */
324var TestResultState;
325(function (TestResultState) {
326 TestResultState[TestResultState["Passed"] = 0] = "Passed";
327 TestResultState[TestResultState["Failed"] = 1] = "Failed";
328 TestResultState[TestResultState["FlakyFailed"] = 2] = "FlakyFailed";
329 TestResultState[TestResultState["Skipped"] = 3] = "Skipped";
330})(TestResultState = exports.TestResultState || (exports.TestResultState = {}));
331/**
332 * Store a single functional test run's information
333 * @param name - the name of the test
334 * @param result - the result of the test
335 * @param duration - the length of time it took for the test to execute
336 * @public
337 */
338function functionalTestRun(name, result, duration) {
339 localCache.testsRun++;
340 switch (result) {
341 case TestResultState.Failed:
342 localCache.testsFailed++;
343 break;
344 case TestResultState.Passed:
345 localCache.testsPassed++;
346 break;
347 case TestResultState.FlakyFailed:
348 localCache.testsFlakyFailed++;
349 break;
350 case TestResultState.Skipped:
351 localCache.testsSkipped++;
352 break;
353 }
354}
355exports.functionalTestRun = functionalTestRun;
356/** @public */
357function endTaskSrc(taskName, startHrtime, fileCount) {
358 localCache.totalTaskSrc++;
359 const taskDuration = process.hrtime(startHrtime);
360 if (!localCache.totalTaskHrTime) {
361 localCache.totalTaskHrTime = taskDuration;
362 }
363 else {
364 localCache.totalTaskHrTime[0] += taskDuration[0];
365 const nanoSecTotal = taskDuration[1] + localCache.totalTaskHrTime[1];
366 if (nanoSecTotal > 1e9) {
367 localCache.totalTaskHrTime[0]++;
368 localCache.totalTaskHrTime[1] = nanoSecTotal - 1e9;
369 }
370 else {
371 localCache.totalTaskHrTime[1] = nanoSecTotal;
372 }
373 }
374 log(taskName, 'read src task duration:', colors.yellow(prettyTime(taskDuration)), `- ${fileCount} files`);
375}
376exports.endTaskSrc = endTaskSrc;
377/**
378 * Store coverage information, potentially logging an error if the coverage is below the threshold
379 * @param coverage - the coverage of the file as a percentage
380 * @param threshold - the minimum coverage for the file as a percentage, an error will be logged if coverage is below
381 * the threshold
382 * @param filePath - the path to the file whose coverage is being measured
383 * @public
384 */
385function coverageData(coverage, threshold, filePath) {
386 localCache.coverageResults++;
387 if (coverage < threshold) {
388 error('Coverage:', Math.floor(coverage) + '% (<' + threshold + '%) -', filePath);
389 }
390 else {
391 localCache.coveragePass++;
392 }
393 localCache.coverageTotal += coverage;
394}
395exports.coverageData = coverageData;
396// eslint-disable-next-line no-control-regex
397const colorCodeRegex = /\x1B[[(?);]{0,2}(;?\d)*./g;
398/**
399 * Adds a suppression for an error or warning
400 * @param suppression - the error or warning as a string or Regular Expression
401 * @public
402 */
403function addSuppression(suppression) {
404 if (typeof suppression === 'string') {
405 suppression = normalizeMessage(suppression);
406 }
407 localCache.errorAndWarningSuppressions.push(suppression);
408 if (index_1.getConfig().verbose) {
409 logSummary(`${colors.yellow('Suppressing')} - ${suppression.toString()}`);
410 }
411}
412exports.addSuppression = addSuppression;
413/**
414 * Logs a warning. It will be logged to standard error and cause the build to fail
415 * if buildConfig.shouldWarningsFailBuild is true, otherwise it will be logged to standard output.
416 * @param message - the warning description
417 * @public
418 */
419function warn(...args) {
420 args.splice(0, 0, 'Warning -');
421 const stringMessage = normalizeMessage(args.join(' '));
422 if (!messageIsSuppressed(stringMessage)) {
423 localCache.warnings.push(stringMessage);
424 log(colors.yellow.apply(undefined, args));
425 }
426}
427exports.warn = warn;
428/**
429 * Logs an error to standard error and causes the build to fail.
430 * @param message - the error description
431 * @public
432 */
433function error(...args) {
434 args.splice(0, 0, 'Error -');
435 const stringMessage = normalizeMessage(args.join(' '));
436 if (!messageIsSuppressed(stringMessage)) {
437 localCache.errors.push(stringMessage);
438 log(colors.red.apply(undefined, args));
439 }
440}
441exports.error = error;
442/**
443 * Logs a message about a particular file
444 * @param write - the function which will write message
445 * @param taskName - the name of the task which is doing the logging
446 * @param filePath - the path to the file which encountered an issue
447 * @param line - the line in the file which had an issue
448 * @param column - the column in the file which had an issue
449 * @param errorCode - the custom error code representing this error
450 * @param message - a description of the error
451 * @public
452 */
453function fileLog(write, taskName, filePath, line, column, errorCode, message) {
454 if (!filePath) {
455 filePath = '<undefined path>';
456 }
457 else if (path.isAbsolute(filePath)) {
458 filePath = path.relative(process.cwd(), filePath);
459 }
460 write(`${colors.cyan(taskName)} - ${filePath}(${line},${column}): error ${errorCode}: ${message}`);
461}
462exports.fileLog = fileLog;
463/**
464 * Logs a warning regarding a specific file.
465 * @param filePath - the path to the file which encountered an issue
466 * @param line - the line in the file which had an issue
467 * @param column - the column in the file which had an issue
468 * @param warningCode - the custom warning code representing this warning
469 * @param message - a description of the warning
470 * @public
471 */
472function fileWarning(taskName, filePath, line, column, errorCode, message) {
473 fileLog(warn, taskName, filePath, line, column, errorCode, message);
474}
475exports.fileWarning = fileWarning;
476/**
477 * Logs an error regarding a specific file to standard error and causes the build to fail.
478 * @param filePath - the path to the file which encountered an issue
479 * @param line - the line in the file which had an issue
480 * @param column - the column in the file which had an issue
481 * @param errorCode - the custom error code representing this error
482 * @param message - a description of the error
483 * @public
484 */
485function fileError(taskName, filePath, line, column, errorCode, message) {
486 fileLog(error, taskName, filePath, line, column, errorCode, message);
487}
488exports.fileError = fileError;
489/**
490 * Logs a message to standard output if the verbose flag is specified.
491 * @param args - the messages to log when in verbose mode
492 * @public
493 */
494function verbose(...args) {
495 if (config_1.getFlagValue('verbose')) {
496 log.apply(undefined, args);
497 }
498}
499exports.verbose = verbose;
500/** @public */
501// eslint-disable-next-line @typescript-eslint/no-explicit-any
502function generateGulpError(err) {
503 if (isVerbose()) {
504 return err;
505 }
506 else {
507 // eslint-disable-next-line @typescript-eslint/no-explicit-any
508 const output = {
509 showStack: false,
510 toString: () => {
511 return '';
512 }
513 };
514 markErrorAsWritten(output);
515 return output;
516 }
517}
518exports.generateGulpError = generateGulpError;
519/**
520 * Logs an error to standard error and causes the build to fail.
521 * @param e - the error (can be a string or Error object)
522 * @public
523 */
524// eslint-disable-next-line @typescript-eslint/no-explicit-any
525function writeError(e) {
526 if (e) {
527 if (!e[WROTE_ERROR_KEY]) {
528 if (e.err) {
529 if (!e.err[WROTE_ERROR_KEY]) {
530 const msg = formatError(e);
531 const time = prettyTime(e.hrDuration);
532 error('\'' + colors.cyan(e.task) + '\'', colors.red(e.subTask ? 'sub task errored after' : 'errored after'), colors.magenta(time), '\r\n', msg || '');
533 markErrorAsWritten(e.err[WROTE_ERROR_KEY]);
534 }
535 }
536 else if (e.fileName) {
537 // This is probably a plugin error
538 if (isVerbose()) {
539 error(e.message, '\r\n', e.plugin + ': \'' + colors.yellow(e.fileName) + '\':' + e.lineNumber, '\r\n', e.stack);
540 }
541 else {
542 error(e.message, '\r\n', e.plugin + ': \'' + colors.yellow(e.fileName) + '\':' + e.lineNumber);
543 }
544 }
545 else {
546 if (isVerbose()) {
547 error('Unknown', '\r\n', colors.red(e.message), '\r\n', e.stack);
548 }
549 else {
550 error('Unknown', '\r\n', colors.red(e.message));
551 }
552 }
553 markErrorAsWritten(e);
554 }
555 }
556 else {
557 error('Unknown Error Object');
558 }
559}
560exports.writeError = writeError;
561/**
562 * Returns the list of warnings which have been logged
563 * @public
564 */
565function getWarnings() {
566 return localCache.warnings;
567}
568exports.getWarnings = getWarnings;
569/**
570 * Returns the list of errors which have been logged
571 * @public
572 */
573function getErrors() {
574 return localCache.errors;
575}
576exports.getErrors = getErrors;
577/** @public */
578function getStart() {
579 return localCache.start;
580}
581exports.getStart = getStart;
582/**
583 * @public
584 */
585function setWatchMode() {
586 localCache.watchMode = true;
587}
588exports.setWatchMode = setWatchMode;
589/**
590 * @public
591 */
592function getWatchMode() {
593 return localCache.watchMode;
594}
595exports.getWatchMode = getWatchMode;
596/**
597 * @public
598 */
599function setExitCode(exitCode) {
600 localCache.exitCode = exitCode;
601}
602exports.setExitCode = setExitCode;
603/**
604 * @public
605 */
606function logStartSubtask(name) {
607 log(`Starting subtask '${colors.cyan(name)}'...`);
608 localCache.subTasksRun++;
609}
610exports.logStartSubtask = logStartSubtask;
611/**
612 * @public
613 */
614function logEndSubtask(name, startTime, errorObject) {
615 const duration = process.hrtime(startTime);
616 if (name) {
617 if (!errorObject) {
618 const durationString = prettyTime(duration);
619 log(`Finished subtask '${colors.cyan(name)}' after ${colors.magenta(durationString)}`);
620 }
621 else {
622 writeError({
623 err: errorObject,
624 task: name,
625 subTask: true,
626 hrDuration: duration
627 });
628 }
629 }
630}
631exports.logEndSubtask = logEndSubtask;
632/**
633 * @public
634 */
635function initialize(gulp, config, gulpErrorCallback, gulpStopCallback) {
636 // This will add logging to the gulp execution
637 localCache.gulp = gulp;
638 wireUpProcessErrorHandling(config.shouldWarningsFailBuild);
639 localCache.gulpErrorCallback = gulpErrorCallback || (() => {
640 // Do Nothing
641 });
642 localCache.gulpStopCallback = gulpStopCallback || (() => {
643 // Do Nothing
644 });
645 // eslint-disable-next-line @typescript-eslint/no-explicit-any
646 gulp.on('start', (err) => {
647 log('Starting gulp');
648 });
649 // eslint-disable-next-line @typescript-eslint/no-explicit-any
650 gulp.on('stop', (err) => {
651 writeSummary(() => {
652 // error if we have any errors
653 if (localCache.taskErrors > 0 ||
654 (getWarnings().length && config.shouldWarningsFailBuild) ||
655 getErrors().length ||
656 localCache.testsFailed > 0) {
657 exitProcess(1);
658 }
659 if (localCache.gulpStopCallback) {
660 localCache.gulpStopCallback(err);
661 }
662 exitProcess(0);
663 });
664 });
665 // eslint-disable-next-line @typescript-eslint/no-explicit-any
666 gulp.on('err', (err) => {
667 _writeTaskError(err);
668 writeSummary(() => {
669 exitProcess(1);
670 if (localCache.gulpErrorCallback) {
671 localCache.gulpErrorCallback(err);
672 }
673 });
674 });
675 // eslint-disable-next-line @typescript-eslint/no-explicit-any
676 gulp.on('task_start', (e) => {
677 if (localCache.fromRunGulp) {
678 log('Starting', '\'' + colors.cyan(e.task) + '\'...');
679 }
680 localCache.taskRun++;
681 });
682 // eslint-disable-next-line @typescript-eslint/no-explicit-any
683 gulp.on('task_stop', (e) => {
684 const time = prettyTime(e.hrDuration);
685 if (localCache.fromRunGulp) {
686 log('Finished', '\'' + colors.cyan(e.task) + '\'', 'after', colors.magenta(time));
687 }
688 });
689 // eslint-disable-next-line @typescript-eslint/no-explicit-any
690 gulp.on('task_err', (err) => {
691 _writeTaskError(err);
692 writeSummary(() => {
693 exitProcess(1);
694 });
695 });
696 // eslint-disable-next-line @typescript-eslint/no-explicit-any
697 gulp.on('task_not_found', (err) => {
698 log(colors.red('Task \'' + err.task + '\' is not in your gulpfile'));
699 log('Please check the documentation for proper gulpfile formatting');
700 exitProcess(1);
701 });
702}
703exports.initialize = initialize;
704/**
705 * @public
706 */
707function markTaskCreationTime() {
708 localCache.taskCreationTime = process.hrtime(getStart());
709}
710exports.markTaskCreationTime = markTaskCreationTime;
711function messageIsSuppressed(message) {
712 for (const suppression of localCache.errorAndWarningSuppressions) {
713 if (typeof suppression === 'string' && message === suppression) {
714 return true;
715 }
716 else if (suppression instanceof RegExp && message.match(suppression)) {
717 return true;
718 }
719 }
720 return false;
721}
722function normalizeMessage(message) {
723 return message
724 .replace(colorCodeRegex, '') // remove colors
725 .replace(/\r\n/g, '\n') // normalize newline
726 .replace(/\\/g, '/'); // normalize slashes
727}
728//# sourceMappingURL=logging.js.map
\No newline at end of file