'use strict'; const path = require('path'); const isObject = require('type/object/is'); const chalk = require('chalk'); const isStandaloneExecutable = require('../utils/isStandaloneExecutable'); const resolveLocalServerlessPath = require('./resolve-local-serverless-path'); const slsVersion = require('./../../package').version; const sfeVersion = require('@serverless/dashboard-plugin/package.json').version; const { platformClientVersion } = require('@serverless/dashboard-plugin'); const ServerlessError = require('../serverless-error'); const tokenizeException = require('../utils/tokenize-exception'); const logDeprecation = require('../utils/logDeprecation'); const resolveIsLocallyInstalled = require('../utils/is-locally-installed'); const isTelemetryDisabled = require('../utils/telemetry/areDisabled'); const { storeLocally: storeTelemetryLocally, send: sendTelemetry } = require('../utils/telemetry'); const generateTelemetryPayload = require('../utils/telemetry/generatePayload'); const resolveErrorLocation = require('../utils/telemetry/resolve-error-location'); const resolveInput = require('./resolve-input'); const consoleLog = (message) => process.stdout.write(`${message}\n`); const writeMessage = (title, message) => { let line = ''; while (line.length < 56 - title.length) { line = `${line}-`; } process.stdout.write(' \n'); consoleLog(chalk.yellow(` ${title} ${line}`)); consoleLog(' '); if (message) { consoleLog(` ${message.split('\n').join('\n ')}`); } consoleLog(' '); }; const isErrorCodeNormative = RegExp.prototype.test.bind(/^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$/); module.exports = async (exception, options = {}) => { if (!isObject(options)) options = {}; // Due to the fact that the handler can be invoked via fallback, we need to support both `serverless` // and `isLocallyInstalled` + `isInvokedByGlobalInstallation` properties // TODO: Support for these properties should be removed with next major const { isUncaughtException, isLocallyInstalled: passedIsLocallyInstalled, isInvokedByGlobalInstallation: passedIsInvokedByGlobalInstallation, serverless, hasTelemetryBeenReported, commandUsage, } = options; let { command, options: cliOptions, commandSchema, serviceDir, configuration } = options; const isLocallyInstalled = serverless ? serverless.isLocallyInstalled : passedIsLocallyInstalled; const isInvokedByGlobalInstallation = serverless ? serverless.isInvokedByGlobalInstallation : passedIsInvokedByGlobalInstallation; // If provided serverless instance is a local fallback, and we're not in context of it // Pass error handling to this local fallback implementation if (isInvokedByGlobalInstallation && !resolveIsLocallyInstalled()) { const localServerlessPath = resolveLocalServerlessPath(); try { // Attempt to use error handler from local version // TODO: Remove local version fallback with next major (when its moved to the top of the process) try { require(path.resolve(localServerlessPath, 'lib/cli/handle-error'))(exception, { serverless, isLocallyInstalled, isUncaughtException, command, options: cliOptions, commandSchema, serviceDir, configuration, hasTelemetryBeenReported, commandUsage, }); return; } catch (error) { // Pass and attempt to use old-style error handler from local version // Ugly mock of serverless below is used to ensure that Framework version will be logged with `(local)` suffix require(path.resolve(localServerlessPath, 'lib/classes/Error')).logError(exception, { serverless: serverless || { isLocallyInstalled }, forceExit: isUncaughtException, }); return; } } catch (err) { // This is just a fallback as for most (all?) versions it shouldn't happen throw new ServerlessError( 'Could not resolve path to locally installed error handler.', 'INVALID_LOCAL_SERVERLESS_ERROR_HANDLER' ); } } const exceptionTokens = tokenizeException(exception); const isUserError = !isUncaughtException && exceptionTokens.isUserError; writeMessage( exceptionTokens.title, exceptionTokens.stack && (!isUserError || process.env.SLS_DEBUG) ? exceptionTokens.stack : exceptionTokens.message ); if (!isUserError && !process.env.SLS_DEBUG) { const debugInfo = [ ' ', ' For debugging logs, run again after setting the', ' "SLS_DEBUG=*" environment variable.', ].join(''); consoleLog(chalk.red(debugInfo)); consoleLog(' '); } const platform = process.platform; const nodeVersion = process.version.replace(/^[v|V]/, ''); consoleLog(chalk.yellow(' Get Support --------------------------------------------')); consoleLog(`${chalk.yellow(' Docs: ')}docs.serverless.com`); consoleLog(`${chalk.yellow(' Bugs: ')}github.com/serverless/serverless/issues`); consoleLog(`${chalk.yellow(' Issues: ')}forum.serverless.com`); consoleLog(' '); consoleLog(chalk.yellow(' Your Environment Information ---------------------------')); consoleLog(chalk.yellow(` Operating System: ${platform}`)); consoleLog(chalk.yellow(` Node Version: ${nodeVersion}`)); const installationModePostfix = await (async () => { if (isStandaloneExecutable) return ' (standalone)'; if (isLocallyInstalled != null) return isLocallyInstalled ? ' (local)' : ''; return resolveIsLocallyInstalled() ? ' (local)' : ''; })(); consoleLog( chalk.yellow(` Framework Version: ${slsVersion}${installationModePostfix}`) ); consoleLog(chalk.yellow(` Plugin Version: ${sfeVersion}`)); consoleLog(chalk.yellow(` SDK Version: ${platformClientVersion}`)); const componentsVersion = (() => { try { return require('@serverless/components/package').version; } catch (error) { return 'Unavailable'; } })(); consoleLog(chalk.yellow(` Components Version: ${componentsVersion}`)); consoleLog(' '); logDeprecation.printSummary(); if ( !isTelemetryDisabled && hasTelemetryBeenReported === false && (serverless ? serverless.isTelemetryReportedExternally : true) ) { if (command == null) { // We're in local fallback and older global didn't pass CLI command data, resolve it here if (serverless) { configuration = serverless.configurationInput; serviceDir = serverless.serviceDir; } const commandsSchema = configuration ? require('./commands-schema/resolve-final')(serverless.pluginManager.externalPlugins, { providerName: serverless.service.provider.name, configuration, }) : require('./commands-schema/aws-service'); ({ command, options: cliOptions, commandSchema } = resolveInput(commandsSchema)); } if (commandSchema) { // Report only for recognized commands const telemetryPayload = generateTelemetryPayload({ command, options: cliOptions, commandSchema, serviceDir, configuration, serverless, commandUsage, }); const failureReason = { kind: isUserError ? 'user' : 'programmer', code: exception.code, }; if (!isUserError || !exception.code || !isErrorCodeNormative(exception.code)) { failureReason.location = resolveErrorLocation(exceptionTokens); } storeTelemetryLocally({ ...telemetryPayload, failureReason, outcome: 'failure' }); await sendTelemetry(); } } process.exitCode = 1; if (isUncaughtException) process.exit(); };