UNPKG

@atomist/sdm

Version:

Atomist Software Delivery Machine SDK

105 lines (88 loc) 3.76 kB
/* * Copyright © 2020 Atomist, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { logger } from "@atomist/automation-client/lib/util/logger"; import stripAnsi from "strip-ansi"; import { InterpretLog } from "../../../spi/log/InterpretedLog"; export const NpmLogInterpreter: InterpretLog = log => { if (!log) { return undefined; } const lines = removeBlanksFromEnd(removeNpmFooter(log.split("\n").map(stripAnsi).map(stripLogPrefix))); const defaultMessage = lastOccurrenceOf(/^ERROR:/, lines) || "Error"; const defaultLines = lines.slice(-15); const recognizedInterpretation: RecognizedLog = recognizeMochaTest(lines) || recognizeNpmRunError(lines) || {}; const relevantLines: string[] = recognizedInterpretation.relevantLines || defaultLines; return { message: recognizedInterpretation.message || defaultMessage, relevantPart: "```\n" + relevantLines.join("\n") + "\n```", }; }; const LogPrefix = /^.*\[(info |error|warn |debug)\] /; const NpmFooterPrefix = /^npm ERR!/; const StackTraceLine = /^\W*at /; const BeginMochaFailingTests = /^\W*\d* failing$/; interface RecognizedLog { message?: string; relevantLines?: string[]; } function recognizeNpmRunError(lines: string[]): RecognizedLog { const reversedLines = lines.slice().reverse(); const lastBreakBeforeCommand = findTwoBlankLinesIndex(reversedLines); if (lastBreakBeforeCommand < 0) { return undefined; } return { relevantLines: lines.slice(-Math.min(lastBreakBeforeCommand, 20)) }; } function recognizeMochaTest(lines: string[]): RecognizedLog | undefined { const begin = lines.findIndex(s => BeginMochaFailingTests.test(s)); if (begin < 0) { logger.debug("No mocha test detected"); return undefined; } logger.debug("Mocha test detected, beginning at %d: %s", begin, lines[begin]); const fromBeginning = lines.slice(begin); const end = findTwoBlankLinesIndex(fromBeginning) || fromBeginning.length; const fromFailingCountToTwoBlankLines = fromBeginning.slice(0, end); return { message: "Tests: " + lines[begin].trim(), relevantLines: fromFailingCountToTwoBlankLines.filter(s => !StackTraceLine.test(s)), }; } function stripLogPrefix(line: string): string { return line.replace(LogPrefix, ""); } function removeNpmFooter(lines: string[]): string[] { const npmErr = "npm ERR! This is probably not a problem with npm. There is likely additional logging output above."; if (lines.includes(npmErr)) { logger.info("Filtering npm error footer"); return lines.filter(s => !NpmFooterPrefix.test(s)); } return lines; } function findTwoBlankLinesIndex(lines: string[]): number { return lines.findIndex((s, i) => s === "" && lines[i + 1] === ""); } function lastOccurrenceOf(re: RegExp, lines: string[]): string { const reversedLines = lines.slice().reverse(); // is there a better way tto make a copy? reverse() is in-place. >:-( return reversedLines.find(s => re.test(s)); } function removeBlanksFromEnd(lines: string[]): string[] { let w = lines.length - 1; while (lines[w].trim() === "") { w--; } return lines.slice(0, w + 1); }