UNPKG

25 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 // eslint-disable-next-line dot-notation
226 if (!global['dontWatchExit']) {
227 if (!localCache.wroteSummary) {
228 localCache.wroteSummary = true;
229 console.log('About to exit with code:', code);
230 console.error('Process terminated before summary could be written, possible error in async code not ' +
231 'continuing!');
232 console.log('Trying to exit with exit code 1');
233 exitProcess(1);
234 }
235 else {
236 if (localCache.exitCode !== 0) {
237 console.log(`Exiting with exit code: ${localCache.exitCode}`);
238 exitProcess(localCache.exitCode);
239 }
240 else if (wroteToStdErr) {
241 console.error(`The build failed because a task wrote output to stderr.`);
242 console.log(`Exiting with exit code: 1`);
243 exitProcess(1);
244 }
245 }
246 }
247 });
248 process.on('uncaughtException', (err) => {
249 console.error(err);
250 _writeTaskError(err);
251 writeSummary(() => {
252 exitProcess(1);
253 if (localCache.gulpErrorCallback) {
254 localCache.gulpErrorCallback(err);
255 }
256 });
257 });
258 }
259}
260function markErrorAsWritten(err) {
261 try {
262 err[WROTE_ERROR_KEY] = true;
263 }
264 catch (e) {
265 // Do Nothing
266 }
267}
268/**
269 * Adds a message to be displayed in the summary after execution is complete.
270 * @param value - the message to display
271 * @public
272 */
273function logSummary(value) {
274 localCache.writeSummaryLogs.push(value);
275}
276exports.logSummary = logSummary;
277/**
278 * Log a message to the console
279 * @param args - the messages to log to the console
280 * @public
281 */
282function log(...args) {
283 const currentTime = new Date();
284 const timestamp = colors.gray([
285 padTimePart(currentTime.getHours()),
286 padTimePart(currentTime.getMinutes()),
287 padTimePart(currentTime.getSeconds())
288 ].join(':'));
289 console.log(`[${timestamp}] ${args.join('')}`);
290}
291exports.log = log;
292function padTimePart(timepart) {
293 return timepart >= 10 ? timepart.toString(10) : `0${timepart.toString(10)}`;
294}
295/**
296 * Resets the state of the logging cache
297 * @public
298 */
299function reset() {
300 localCache.start = process.hrtime();
301 localCache.warnings = [];
302 localCache.errors = [];
303 localCache.coverageResults = 0;
304 localCache.coveragePass = 0;
305 localCache.coverageTotal = 0;
306 localCache.taskRun = 0;
307 localCache.subTasksRun = 0;
308 localCache.taskErrors = 0;
309 localCache.totalTaskHrTime = undefined;
310 localCache.totalTaskSrc = 0;
311 localCache.wroteSummary = false;
312 localCache.writingSummary = false;
313 localCache.writeSummaryCallbacks = [];
314 localCache.testsRun = 0;
315 localCache.testsPassed = 0;
316 localCache.testsFailed = 0;
317 localCache.testsFlakyFailed = 0;
318 localCache.testsSkipped = 0;
319 localCache.writeSummaryLogs = [];
320}
321exports.reset = reset;
322/**
323 * The result of a functional test run
324 * @public
325 */
326var TestResultState;
327(function (TestResultState) {
328 TestResultState[TestResultState["Passed"] = 0] = "Passed";
329 TestResultState[TestResultState["Failed"] = 1] = "Failed";
330 TestResultState[TestResultState["FlakyFailed"] = 2] = "FlakyFailed";
331 TestResultState[TestResultState["Skipped"] = 3] = "Skipped";
332})(TestResultState = exports.TestResultState || (exports.TestResultState = {}));
333/**
334 * Store a single functional test run's information
335 * @param name - the name of the test
336 * @param result - the result of the test
337 * @param duration - the length of time it took for the test to execute
338 * @public
339 */
340function functionalTestRun(name, result, duration) {
341 localCache.testsRun++;
342 switch (result) {
343 case TestResultState.Failed:
344 localCache.testsFailed++;
345 break;
346 case TestResultState.Passed:
347 localCache.testsPassed++;
348 break;
349 case TestResultState.FlakyFailed:
350 localCache.testsFlakyFailed++;
351 break;
352 case TestResultState.Skipped:
353 localCache.testsSkipped++;
354 break;
355 }
356}
357exports.functionalTestRun = functionalTestRun;
358/** @public */
359function endTaskSrc(taskName, startHrtime, fileCount) {
360 localCache.totalTaskSrc++;
361 const taskDuration = process.hrtime(startHrtime);
362 if (!localCache.totalTaskHrTime) {
363 localCache.totalTaskHrTime = taskDuration;
364 }
365 else {
366 localCache.totalTaskHrTime[0] += taskDuration[0];
367 const nanoSecTotal = taskDuration[1] + localCache.totalTaskHrTime[1];
368 if (nanoSecTotal > 1e9) {
369 localCache.totalTaskHrTime[0]++;
370 localCache.totalTaskHrTime[1] = nanoSecTotal - 1e9;
371 }
372 else {
373 localCache.totalTaskHrTime[1] = nanoSecTotal;
374 }
375 }
376 log(taskName, 'read src task duration:', colors.yellow(prettyTime(taskDuration)), `- ${fileCount} files`);
377}
378exports.endTaskSrc = endTaskSrc;
379/**
380 * Store coverage information, potentially logging an error if the coverage is below the threshold
381 * @param coverage - the coverage of the file as a percentage
382 * @param threshold - the minimum coverage for the file as a percentage, an error will be logged if coverage is below
383 * the threshold
384 * @param filePath - the path to the file whose coverage is being measured
385 * @public
386 */
387function coverageData(coverage, threshold, filePath) {
388 localCache.coverageResults++;
389 if (coverage < threshold) {
390 error('Coverage:', Math.floor(coverage) + '% (<' + threshold + '%) -', filePath);
391 }
392 else {
393 localCache.coveragePass++;
394 }
395 localCache.coverageTotal += coverage;
396}
397exports.coverageData = coverageData;
398// eslint-disable-next-line no-control-regex
399const colorCodeRegex = /\x1B[[(?);]{0,2}(;?\d)*./g;
400/**
401 * Adds a suppression for an error or warning
402 * @param suppression - the error or warning as a string or Regular Expression
403 * @public
404 */
405function addSuppression(suppression) {
406 if (typeof suppression === 'string') {
407 suppression = normalizeMessage(suppression);
408 }
409 localCache.errorAndWarningSuppressions.push(suppression);
410 if (index_1.getConfig().verbose) {
411 logSummary(`${colors.yellow('Suppressing')} - ${suppression.toString()}`);
412 }
413}
414exports.addSuppression = addSuppression;
415/**
416 * Logs a warning. It will be logged to standard error and cause the build to fail
417 * if buildConfig.shouldWarningsFailBuild is true, otherwise it will be logged to standard output.
418 * @param message - the warning description
419 * @public
420 */
421function warn(...args) {
422 args.splice(0, 0, 'Warning -');
423 const stringMessage = normalizeMessage(args.join(' '));
424 if (!messageIsSuppressed(stringMessage)) {
425 localCache.warnings.push(stringMessage);
426 log(colors.yellow.apply(undefined, args));
427 }
428}
429exports.warn = warn;
430/**
431 * Logs an error to standard error and causes the build to fail.
432 * @param message - the error description
433 * @public
434 */
435function error(...args) {
436 args.splice(0, 0, 'Error -');
437 const stringMessage = normalizeMessage(args.join(' '));
438 if (!messageIsSuppressed(stringMessage)) {
439 localCache.errors.push(stringMessage);
440 log(colors.red.apply(undefined, args));
441 }
442}
443exports.error = error;
444/**
445 * Logs a message about a particular file
446 * @param write - the function which will write message
447 * @param taskName - the name of the task which is doing the logging
448 * @param filePath - the path to the file which encountered an issue
449 * @param line - the line in the file which had an issue
450 * @param column - the column in the file which had an issue
451 * @param errorCode - the custom error code representing this error
452 * @param message - a description of the error
453 * @public
454 */
455function fileLog(write, taskName, filePath, line, column, errorCode, message) {
456 if (!filePath) {
457 filePath = '<undefined path>';
458 }
459 else if (path.isAbsolute(filePath)) {
460 filePath = path.relative(process.cwd(), filePath);
461 }
462 write(`${colors.cyan(taskName)} - ${filePath}(${line},${column}): error ${errorCode}: ${message}`);
463}
464exports.fileLog = fileLog;
465/**
466 * Logs a warning regarding a specific file.
467 * @param filePath - the path to the file which encountered an issue
468 * @param line - the line in the file which had an issue
469 * @param column - the column in the file which had an issue
470 * @param warningCode - the custom warning code representing this warning
471 * @param message - a description of the warning
472 * @public
473 */
474function fileWarning(taskName, filePath, line, column, errorCode, message) {
475 fileLog(warn, taskName, filePath, line, column, errorCode, message);
476}
477exports.fileWarning = fileWarning;
478/**
479 * Logs an error regarding a specific file to standard error and causes the build to fail.
480 * @param filePath - the path to the file which encountered an issue
481 * @param line - the line in the file which had an issue
482 * @param column - the column in the file which had an issue
483 * @param errorCode - the custom error code representing this error
484 * @param message - a description of the error
485 * @public
486 */
487function fileError(taskName, filePath, line, column, errorCode, message) {
488 fileLog(error, taskName, filePath, line, column, errorCode, message);
489}
490exports.fileError = fileError;
491/**
492 * Logs a message to standard output if the verbose flag is specified.
493 * @param args - the messages to log when in verbose mode
494 * @public
495 */
496function verbose(...args) {
497 if (config_1.getFlagValue('verbose')) {
498 log.apply(undefined, args);
499 }
500}
501exports.verbose = verbose;
502/** @public */
503// eslint-disable-next-line @typescript-eslint/no-explicit-any
504function generateGulpError(err) {
505 if (isVerbose()) {
506 return err;
507 }
508 else {
509 // eslint-disable-next-line @typescript-eslint/no-explicit-any
510 const output = {
511 showStack: false,
512 toString: () => {
513 return '';
514 }
515 };
516 markErrorAsWritten(output);
517 return output;
518 }
519}
520exports.generateGulpError = generateGulpError;
521/**
522 * Logs an error to standard error and causes the build to fail.
523 * @param e - the error (can be a string or Error object)
524 * @public
525 */
526// eslint-disable-next-line @typescript-eslint/no-explicit-any
527function writeError(e) {
528 if (e) {
529 if (!e[WROTE_ERROR_KEY]) {
530 if (e.err) {
531 if (!e.err[WROTE_ERROR_KEY]) {
532 const msg = formatError(e);
533 const time = prettyTime(e.hrDuration);
534 error("'" + colors.cyan(e.task) + "'", colors.red(e.subTask ? 'sub task errored after' : 'errored after'), colors.magenta(time), '\r\n', msg || '');
535 markErrorAsWritten(e.err[WROTE_ERROR_KEY]);
536 }
537 }
538 else if (e.fileName) {
539 // This is probably a plugin error
540 if (isVerbose()) {
541 error(e.message, '\r\n', e.plugin + ": '" + colors.yellow(e.fileName) + "':" + e.lineNumber, '\r\n', e.stack);
542 }
543 else {
544 error(e.message, '\r\n', e.plugin + ": '" + colors.yellow(e.fileName) + "':" + e.lineNumber);
545 }
546 }
547 else {
548 if (isVerbose()) {
549 error('Unknown', '\r\n', colors.red(e.message), '\r\n', e.stack);
550 }
551 else {
552 error('Unknown', '\r\n', colors.red(e.message));
553 }
554 }
555 markErrorAsWritten(e);
556 }
557 }
558 else {
559 error('Unknown Error Object');
560 }
561}
562exports.writeError = writeError;
563/**
564 * Returns the list of warnings which have been logged
565 * @public
566 */
567function getWarnings() {
568 return localCache.warnings;
569}
570exports.getWarnings = getWarnings;
571/**
572 * Returns the list of errors which have been logged
573 * @public
574 */
575function getErrors() {
576 return localCache.errors;
577}
578exports.getErrors = getErrors;
579/** @public */
580function getStart() {
581 return localCache.start;
582}
583exports.getStart = getStart;
584/**
585 * @public
586 */
587function setWatchMode() {
588 localCache.watchMode = true;
589}
590exports.setWatchMode = setWatchMode;
591/**
592 * @public
593 */
594function getWatchMode() {
595 return localCache.watchMode;
596}
597exports.getWatchMode = getWatchMode;
598/**
599 * @public
600 */
601function setExitCode(exitCode) {
602 localCache.exitCode = exitCode;
603}
604exports.setExitCode = setExitCode;
605/**
606 * @public
607 */
608function logStartSubtask(name) {
609 log(`Starting subtask '${colors.cyan(name)}'...`);
610 localCache.subTasksRun++;
611}
612exports.logStartSubtask = logStartSubtask;
613/**
614 * @public
615 */
616function logEndSubtask(name, startTime, errorObject) {
617 const duration = process.hrtime(startTime);
618 if (name) {
619 if (!errorObject) {
620 const durationString = prettyTime(duration);
621 log(`Finished subtask '${colors.cyan(name)}' after ${colors.magenta(durationString)}`);
622 }
623 else {
624 writeError({
625 err: errorObject,
626 task: name,
627 subTask: true,
628 hrDuration: duration
629 });
630 }
631 }
632}
633exports.logEndSubtask = logEndSubtask;
634/**
635 * @public
636 */
637function initialize(gulp, config, gulpErrorCallback, gulpStopCallback) {
638 // This will add logging to the gulp execution
639 localCache.gulp = gulp;
640 wireUpProcessErrorHandling(config.shouldWarningsFailBuild);
641 localCache.gulpErrorCallback =
642 gulpErrorCallback ||
643 (() => {
644 // Do Nothing
645 });
646 localCache.gulpStopCallback =
647 gulpStopCallback ||
648 (() => {
649 // Do Nothing
650 });
651 // eslint-disable-next-line @typescript-eslint/no-explicit-any
652 gulp.on('start', (err) => {
653 log('Starting gulp');
654 });
655 // eslint-disable-next-line @typescript-eslint/no-explicit-any
656 gulp.on('stop', (err) => {
657 writeSummary(() => {
658 // error if we have any errors
659 if (localCache.taskErrors > 0 ||
660 (getWarnings().length && config.shouldWarningsFailBuild) ||
661 getErrors().length ||
662 localCache.testsFailed > 0) {
663 exitProcess(1);
664 }
665 if (localCache.gulpStopCallback) {
666 localCache.gulpStopCallback(err);
667 }
668 exitProcess(0);
669 });
670 });
671 // eslint-disable-next-line @typescript-eslint/no-explicit-any
672 gulp.on('err', (err) => {
673 _writeTaskError(err);
674 writeSummary(() => {
675 exitProcess(1);
676 if (localCache.gulpErrorCallback) {
677 localCache.gulpErrorCallback(err);
678 }
679 });
680 });
681 // eslint-disable-next-line @typescript-eslint/no-explicit-any
682 gulp.on('task_start', (e) => {
683 if (localCache.fromRunGulp) {
684 log('Starting', "'" + colors.cyan(e.task) + "'...");
685 }
686 localCache.taskRun++;
687 });
688 // eslint-disable-next-line @typescript-eslint/no-explicit-any
689 gulp.on('task_stop', (e) => {
690 const time = prettyTime(e.hrDuration);
691 if (localCache.fromRunGulp) {
692 log('Finished', "'" + colors.cyan(e.task) + "'", 'after', colors.magenta(time));
693 }
694 });
695 // eslint-disable-next-line @typescript-eslint/no-explicit-any
696 gulp.on('task_err', (err) => {
697 _writeTaskError(err);
698 writeSummary(() => {
699 exitProcess(1);
700 });
701 });
702 // eslint-disable-next-line @typescript-eslint/no-explicit-any
703 gulp.on('task_not_found', (err) => {
704 log(colors.red("Task '" + err.task + "' is not in your gulpfile"));
705 log('Please check the documentation for proper gulpfile formatting');
706 exitProcess(1);
707 });
708}
709exports.initialize = initialize;
710/**
711 * @public
712 */
713function markTaskCreationTime() {
714 localCache.taskCreationTime = process.hrtime(getStart());
715}
716exports.markTaskCreationTime = markTaskCreationTime;
717function messageIsSuppressed(message) {
718 for (const suppression of localCache.errorAndWarningSuppressions) {
719 if (typeof suppression === 'string' && message === suppression) {
720 return true;
721 }
722 else if (suppression instanceof RegExp && message.match(suppression)) {
723 return true;
724 }
725 }
726 return false;
727}
728function normalizeMessage(message) {
729 return message
730 .replace(colorCodeRegex, '') // remove colors
731 .replace(/\r\n/g, '\n') // normalize newline
732 .replace(/\\/g, '/'); // normalize slashes
733}
734//# sourceMappingURL=logging.js.map
\No newline at end of file