mirror of
https://github.com/serverless/serverless.git
synced 2025-12-08 19:46:03 +00:00
286 lines
11 KiB
JavaScript
Executable File
286 lines
11 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
'use strict';
|
|
|
|
require('essentials');
|
|
|
|
// global graceful-fs patch
|
|
// https://github.com/isaacs/node-graceful-fs#global-patching
|
|
require('graceful-fs').gracefulify(require('fs'));
|
|
|
|
if (require('../lib/utils/tabCompletion/isSupported') && process.argv[2] === 'completion') {
|
|
require('../lib/utils/autocomplete')();
|
|
return;
|
|
}
|
|
|
|
const handleError = require('../lib/cli/handle-error');
|
|
|
|
let serverless;
|
|
|
|
process.once('uncaughtException', (error) =>
|
|
handleError(error, {
|
|
isUncaughtException: true,
|
|
isLocallyInstalled: serverless && serverless.isLocallyInstalled,
|
|
isInvokedByGlobalInstallation: serverless && serverless.isInvokedByGlobalInstallation,
|
|
})
|
|
);
|
|
|
|
const processSpanPromise = (async () => {
|
|
try {
|
|
const wait = require('timers-ext/promise/sleep');
|
|
await wait(); // Ensure access to "processSpanPromise"
|
|
|
|
// Propagate (in a background) eventual pending analytics requests
|
|
require('../lib/utils/analytics').sendPending({
|
|
serverlessExecutionSpan: processSpanPromise,
|
|
});
|
|
|
|
const { commands, options, isHelpRequest } = require('../lib/cli/resolve-input')();
|
|
|
|
// If version number request, show it and abort
|
|
if (options.version) {
|
|
await require('../lib/cli/list-version')();
|
|
return;
|
|
}
|
|
|
|
const ServerlessError = require('../lib/serverless-error');
|
|
const commandsSchema = require('../lib/cli/commands-schema');
|
|
const command = commands.join(' ');
|
|
const commandSchema = commandsSchema.get(command);
|
|
if (commandSchema && commandSchema.isHidden && commandSchema.noSupportNotice) {
|
|
throw new ServerlessError(
|
|
`Cannot run \`${command}\` command: ${commandSchema.noSupportNotice}`
|
|
);
|
|
}
|
|
|
|
const uuid = require('uuid');
|
|
const _ = require('lodash');
|
|
const Serverless = require('../lib/Serverless');
|
|
|
|
let configurationPath = null;
|
|
let configuration = null;
|
|
let variablesMeta;
|
|
|
|
if (!commandSchema || commandSchema.serviceDependencyMode) {
|
|
const resolveConfigurationPath = require('../lib/cli/resolve-configuration-path');
|
|
const readConfiguration = require('../lib/configuration/read');
|
|
const logDeprecation = require('../lib/utils/logDeprecation');
|
|
|
|
// Resolve eventual service configuration path
|
|
configurationPath = await resolveConfigurationPath();
|
|
|
|
// If service configuration file is found, load its content
|
|
configuration = configurationPath
|
|
? await (async () => {
|
|
try {
|
|
return await readConfiguration(configurationPath);
|
|
} catch (error) {
|
|
// Configuration syntax error should not prevent help from being displayed
|
|
// (if possible configuration should be read for help request as registered
|
|
// plugins may introduce new commands to be listed in help output)
|
|
if (isHelpRequest) return null;
|
|
throw error;
|
|
}
|
|
})()
|
|
: null;
|
|
|
|
if (configuration) {
|
|
const path = require('path');
|
|
const resolveVariables = require('../lib/configuration/variables/resolve');
|
|
const humanizePropertyPathKeys = require('../lib/configuration/variables/humanize-property-path-keys');
|
|
const eventuallyReportVariableResolutionErrors = require('../lib/configuration/variables/eventually-report-resolution-errors');
|
|
let resolverConfiguration;
|
|
let hasVariableResolutionFailed;
|
|
|
|
await (async () => {
|
|
if (_.get(configuration.provider, 'variableSyntax')) {
|
|
if (isHelpRequest) return;
|
|
if (configuration.variablesResolutionMode) {
|
|
throw new ServerlessError(
|
|
`Cannot resolve ${path.basename(
|
|
configurationPath
|
|
)}: "variableSyntax" is not supported with new variables resolver. ` +
|
|
'Please drop this setting'
|
|
);
|
|
}
|
|
logDeprecation(
|
|
'NEW_VARIABLES_RESOLVER',
|
|
'Serverless Framework was enhanced with a new variables resolver ' +
|
|
'which doesn\'t recognize "provider.variableSyntax" setting.' +
|
|
"Starting with a new major it will be the only resolver that's used." +
|
|
'. Drop setting from a configuration to adapt to it',
|
|
{ serviceConfig: configuration }
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Resolve eventual configuration variables (from provider agnostic sources)
|
|
|
|
// As we have not yet loaded .env files, we may have incomplete process.env state
|
|
// It is taken into account in below resolver configuration
|
|
const resolveVariablesMeta = require('../lib/configuration/variables/resolve-meta');
|
|
variablesMeta = resolveVariablesMeta(configuration);
|
|
if (!variablesMeta.size) return;
|
|
|
|
if (variablesMeta.has('variablesResolutionMode')) {
|
|
variablesMeta = null;
|
|
if (isHelpRequest) return;
|
|
throw new ServerlessError(
|
|
`Cannot resolve ${path.basename(
|
|
configurationPath
|
|
)}: "variablesResolutionMode" is not accessible ` +
|
|
'(configured behind variables which cannot be resolved at this stage)'
|
|
);
|
|
}
|
|
|
|
resolverConfiguration = {
|
|
servicePath: process.cwd(),
|
|
configuration,
|
|
variablesMeta,
|
|
sources: {
|
|
env: require('../lib/configuration/variables/sources/env'),
|
|
file: require('../lib/configuration/variables/sources/file'),
|
|
opt: require('../lib/configuration/variables/sources/opt'),
|
|
self: require('../lib/configuration/variables/sources/self'),
|
|
strToBool: require('../lib/configuration/variables/sources/str-to-bool'),
|
|
},
|
|
options,
|
|
fulfilledSources: new Set(['file', 'self', 'strToBool']),
|
|
};
|
|
await resolveVariables(resolverConfiguration);
|
|
|
|
hasVariableResolutionFailed = eventuallyReportVariableResolutionErrors(
|
|
configurationPath,
|
|
configuration,
|
|
variablesMeta
|
|
);
|
|
|
|
if (isHelpRequest) return;
|
|
|
|
// There are few configuration properties, which have to be resolved at this point
|
|
// to move forward. Report errors if that's not the case
|
|
if (variablesMeta.has('provider')) {
|
|
throw new ServerlessError(
|
|
`Cannot resolve ${path.basename(
|
|
configurationPath
|
|
)}: "provider" section is not accessible ` +
|
|
'(configured behind variables which cannot be resolved at this stage)'
|
|
);
|
|
}
|
|
if (!hasVariableResolutionFailed && variablesMeta.has('provider\0stage')) {
|
|
if (configuration.variablesResolutionMode) {
|
|
throw new ServerlessError(
|
|
`Cannot resolve ${path.basename(
|
|
configurationPath
|
|
)}: "provider.stage" property is not accessible ` +
|
|
'(configured behind variables which cannot be resolved at this stage)'
|
|
);
|
|
}
|
|
logDeprecation(
|
|
'NEW_VARIABLES_RESOLVER',
|
|
'"provider.stage" is not accessible ' +
|
|
'(configured behind variables which cannot be resolved at this stage).\n' +
|
|
'Starting with next major release, ' +
|
|
'this will be communicated with a thrown error.\n' +
|
|
'Set "variablesResolutionMode: 20210219" in your service config, ' +
|
|
'to adapt to this behavior now',
|
|
{ serviceConfig: configuration }
|
|
);
|
|
// Hack to not duplicate the warning with similar deprecation
|
|
logDeprecation.triggeredDeprecations.add('VARIABLES_ERROR_ON_UNRESOLVED');
|
|
}
|
|
|
|
if (variablesMeta.has('useDotenv')) {
|
|
throw new ServerlessError(
|
|
`Cannot resolve ${path.basename(configurationPath)}: "useDotenv" is not accessible ` +
|
|
'(configured behind variables which cannot be resolved at this stage)'
|
|
);
|
|
}
|
|
})();
|
|
|
|
// Load eventual environment variables from .env files
|
|
await require('../lib/cli/conditionally-load-dotenv')(options, configuration);
|
|
|
|
if (variablesMeta && variablesMeta.size) {
|
|
if (!hasVariableResolutionFailed) {
|
|
// Resolve eventually still not resolved configuration variables
|
|
// (now "env" source is assumed as complete)
|
|
|
|
resolverConfiguration.fulfilledSources.add('env');
|
|
await resolveVariables(resolverConfiguration);
|
|
hasVariableResolutionFailed = eventuallyReportVariableResolutionErrors(
|
|
configurationPath,
|
|
configuration,
|
|
variablesMeta
|
|
);
|
|
}
|
|
|
|
if (variablesMeta.size) {
|
|
// At this point we expect "plugins" to be fully resolved to move forward.
|
|
// Report error if that's not the case
|
|
for (const propertyPath of variablesMeta.keys()) {
|
|
if (propertyPath === 'plugins' || propertyPath.startsWith('plugins\0')) {
|
|
throw new ServerlessError(
|
|
`Cannot resolve ${path.basename(configurationPath)}: "${humanizePropertyPathKeys(
|
|
propertyPath.split('\0')
|
|
)}" property is not accessible ` +
|
|
'(configured behind variables which cannot be resolved at this stage)'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
serverless = new Serverless({
|
|
configuration,
|
|
configurationPath: configuration && configurationPath,
|
|
isConfigurationResolved: Boolean(variablesMeta && !variablesMeta.size),
|
|
commands,
|
|
options,
|
|
});
|
|
|
|
try {
|
|
serverless.onExitPromise = processSpanPromise;
|
|
serverless.invocationId = uuid.v4();
|
|
await serverless.init();
|
|
if (serverless.invokedInstance) {
|
|
serverless.invokedInstance.invocationId = serverless.invocationId;
|
|
serverless = serverless.invokedInstance;
|
|
}
|
|
await serverless.run();
|
|
} catch (error) {
|
|
// If Enterprise Plugin, capture error
|
|
let enterpriseErrorHandler = null;
|
|
serverless.pluginManager.plugins.forEach((p) => {
|
|
if (p.enterprise && p.enterprise.errorHandler) {
|
|
enterpriseErrorHandler = p.enterprise.errorHandler;
|
|
}
|
|
});
|
|
if (!enterpriseErrorHandler) throw error;
|
|
try {
|
|
await enterpriseErrorHandler(error, serverless.invocationId);
|
|
} catch (enterpriseErrorHandlerError) {
|
|
const log = require('@serverless/utils/log');
|
|
const tokenizeException = require('../lib/utils/tokenize-exception');
|
|
const exceptionTokens = tokenizeException(enterpriseErrorHandlerError);
|
|
log(
|
|
`Publication to Serverless Dashboard errored with:\n${' '.repeat('Serverless: '.length)}${
|
|
exceptionTokens.isUserError || !exceptionTokens.stack
|
|
? exceptionTokens.message
|
|
: exceptionTokens.stack
|
|
}`,
|
|
{ color: 'orange' }
|
|
);
|
|
}
|
|
throw error;
|
|
}
|
|
} catch (error) {
|
|
handleError(error, {
|
|
isLocallyInstalled: serverless && serverless.isLocallyInstalled,
|
|
isInvokedByGlobalInstallation: serverless && serverless.isInvokedByGlobalInstallation,
|
|
});
|
|
}
|
|
})();
|