Ideally it should be straightforward to use Serverless Framework programmatically. Additionally clean separation of concerns will help significantly the maintenance and will allow to integrate Components engine into the repository.
Improvements towards separation of Packaging and Deployment phases has been moved to #8499
Proposed solutionStep by step let's seclude CLI params and service config resolution logic from a core and layout a new processing flow as follows:
-v
, output version info and abort-c, --config
CLI params), into plain not normalized in any way JSON structure). (If there's parsing error and it's not a CLI help request, crash with meaningful error)file
, self
and strToBool
variable sources (but only those not depending on other resolvers)provider
and eventual provider.stage
properties are fully resolved. (If there's a validation error and it's not a CLI help request, crash with meaningful error on provider
being not resolved. Show deprecation and warning notice on provider.stage
not being resolved).env
files (If there's parsing error and it's not a CLI help request, crash with meaningful error)env
and remaining file
, self
and strToBool
variable sources (but only those not depending on other resolvers)plugins
property is fully resolved.Additionally to have Framework CLI totally programatic, #1720 has to be addressed.
Implementation specPreliminary notes:
runServerless
variant (assuming they're applicable for that)test/unit
folder--verbose
option and improve general help outputError
class (but presents simplified logic due to more generic approach). Place handler logic in lib/cli/handle-error.js
(and ensure some tests for it)uncaughtExceptions
are handled with same error handlerlogError
utilityserverless.onExitPromise
)
try/catch
clause in promise accessible at lib/cli/execution-span.js
(module should export an unresolved promise and method (to be used internally) for resolving it)lib/cli/execution-span.js
promise to serverless.executionSpan
(as it can be valuable for a plugins). Remove it's so far counterpart serverless.onExitPromise
and in places it was used, refer to either promise exported by lib/cli/execution-span.js
or serverless.executionSpan
analytics.sendPending()
to top of the try/catch clause-v, --version
CLI params handling
sls -v [...]
or sls --version [...]
Generate simple version output which follows one implemented internally in CLI
class. Place handling logic in lib/cli/eventually-output-version-info.js
(and ensure some tests for it)-v, --version
option handling and recognition from CLI.js classlib/cli/resolve-service-config-path.js
and resemble logic we have now here: const getConfigFilePath = async (servicePath, options = {}) => { if (options.config) { const customPath = path.join(servicePath, options.config); return fileExists(customPath).then(exists => { return exists ? customPath : null; }); } const jsonPath = path.join(servicePath, 'serverless.json'); const ymlPath = path.join(servicePath, 'serverless.yml'); const yamlPath = path.join(servicePath, 'serverless.yaml'); const jsPath = path.join(servicePath, 'serverless.js'); const tsPath = path.join(servicePath, 'serverless.ts'); const [jsonExists, ymlExists, yamlExists, jsExists, tsExists] = await Promise.all([ fileExists(jsonPath), fileExists(ymlPath), fileExists(yamlPath), fileExists(jsPath), fileExists(tsPath), ]); if (yamlExists) { return yamlPath; } else if (ymlExists) { return ymlPath; } else if (jsonExists) { return jsonPath; } else if (jsExists) { return jsPath; } else if (tsExists) { return tsPath; } return null; };Serverless
constructor on config.serviceConfigPath
and assign it to serviceConfigPath
propertyconfig.servicePath
from serviceConfigPath
findServicePath
utilgetConfigFilePath
usage with resolveServiceConfigPath
at interactive CLI setupgetServerlessConfigFilePath
usage with serverless.serviceConfigPath
and remove this util entirely (in interactive CLI setup simply override serverless.serviceConfigPath
instead)getConfigFilePath
lib/service-config/read-source.js
, and resemble logic we have here: getServerlessConfigFilePath(serverless).then(configFilePath => { if (!configFilePath) return null; const fileExtension = path.extname(configFilePath); const isJSOrTsConfigFile = fileExtension === '.js' || fileExtension === '.ts'; return (isJSOrTsConfigFile ? handleJsOrTsConfigFile(configFilePath) : readFile(configFilePath) ).then(config => { if (_.isPlainObject(config)) return config; throw new ServerlessError( `${path.basename(configFilePath)} must export plain object`, 'INVALID_CONFIG_OBJECT_TYPE' ); }); }) . if readServiceConfigSource
crashes expose the error only if it's not CLI help request, otherwise behave as we're not in service context.Serverless
constructor on config.serviceConfigSource
and assign it to serviceConfigSource
propertyServervless.js
class methods, replace this.pluginManager.serverlessConfigFile
references with this.serviceConfigSource
serverless.serviceConfigSource
with help of readServiceConfigSource
module.pluginManager.loadConfigFile
method and pluginManager.serverlessConfigFile
propertyFor that we would need to Implement new variables resolver with following modules:
lib/variables/resolve-variables-map.js
Function that takes serviceConfig
as an input. Traverses it's properties and returns map of all properties which use variable syntax. Result map should expose all information needed for complete variables resolution without a need of repeated property parsing.
After we will fully override variable resolution that's currently in a framework (point 5.2), function should be configured to also override all serviceConfig
properties which depend on variables with null
values (technically we move all information to variables map, and remove it from serviceConfig
. It's to ensure that eventual further serviceConfig
processing in case of variable resolution errors is not affected by unresolved properties content)
Expected format of result map
const sep = "\0"; const exampleResultMap = { [`custom${sep}creds`]: { raw: '${file(../config.${opt:stage, self:provider.stage, "dev"}.json):CREDS}_${self:custom.foo}', meta: [ // Start from last to first // If vars are part of a string, provide start and end locations // and unconditionally coerce result to string { start: 71, end: 89, sources: [{ source: 'self', address: { raw: 'custom.foo' } }] }, { start: 0, end: 70, sources: [ { source: 'file', param: { raw: '../config.${opt:stage, self:provider.stage, "dev"}.json', meta: [ { start: 10, end: 50, sources: [ { source: 'opt', address: { raw: 'stage' } }, { source: 'self', address: { raw: 'provider.stage' } }, { raw: 'dev' }, ], }, ], }, address: { raw: 'CREDS' }, }, ], }, ], }, [`layers${sep}hello${sep}path`]: { raw: '${self:custom.layerPath}', // If property value is entirely constructed with var // No start/end points need to be configured // In such case we also support any result type (no string coercion) variables: [{ sources: [{ source: 'self', address: { raw: 'custom.layerPath' } }] }], }, };
Note: In case of resolution from external files, new added content will need to have eventual variables resolved through same util
lib/variables/resolve-variables.js
Function that takes serviceConfig
, variablesMap
and variablesResolvers
as an input.
variablesResolvers
is expected to be a simple map with source type as a key (e.g. self,
fileetc.) and function that takes
serviceConfig` and eeventual param configured for resolver as arguments. Function may return result sync way or async via returned promise
There should be attempt to resolve every property.
serviceConfig
object and variable reference removed from variablesMap
.variablesMap
(in future processing, resolution should be reattempted only if fail was caused by A error, in other cases there should be no retry.If there's any fail. Function crashes, and on it's error it should expose errorneusVariableKeys
property with keys to each variable resolutions that failed.
Having above:
file
, self
and strToBool
variable sources (but only those not depending on other resolvers). If it fails ignore any not supported source errors. If there are other errors and it's not a CLI help request, in initial stage, ignore them, but after addressing 5.2 signal them with warning message and show a deprecation that with next major we will fail.provider
and provider.stage
properties are resolved.
provider
property still depends on variable resolution, crash with meaningful error, that we cannot accept given form of configurationprovider.stage
property still depends on variable resolution. Show warning and deprecation, stating that it's not recommend to use variables at this property and that we will fail on that with next major.env
files
Follow up with resolution of environment variables from .env
files (currently being implemented at #8413)
As in 2.1 step, attempt to resolve file
, self
, strToBool
and env
variable sources (but only those not depending on other resolvers). If it fails ignore any not supported source errors. If there are other errors and it's not a CLI help request in initial stage, ignore them, but after addressing 5.2 signal them with warning message and show a deprecation that with next major we will fail.
plugins
property is fully resolved.
Inspect variables map, if plugins
property still depends on variable resolution, crash with meaningful error, that we cannot accept given form of configuration
lib/cli/is-help-command.js
(it should follow cli.isHelpRequest
logic but also recognize --help-components
) and adapt it in internals:
isHelpCommand
and pass it to Serverless
constructor in config.isHelpCommand
and internally assign it to isHelpCommand
propertyserverless.cli.isHelpRequest
usage with serverless.isHelpCommand
pluginManager.cliOptions.help
usage with serverless.isHelpCommand
serverless.cli.isHelpRequest
implementationHandling of those commands ideally should be totally secluded from Framework engine, still to not impose too timetaking refactor at this step let's simply mark them, to make further processing possible (having that addressed, let's open an issue calling for their seclusion)
plugin
, login
, logout
or dashboard
pass to Serverless
constructor a shouldMutePluginInitializationErrors: true
option, and internally assign t to _shouldMutePluginInitializationErrors
propertypluginManager.resolveServicePlugins()
Rely on serverles._shouldMutePluginInitializationErrors
and remove pluginManager.pluginIndependentCommands
property.Serverless
instance
(this will most likely lay out naturally and should not require any code changes)
Follow up with construction of Serverless
instance and invoke of serverless.init()
lib/cli/help/options.js
. It should take our common command configuration object, and resemble logic we have at cli.displayCommandOptions()
lib/cli/help/interactive.js
. It should be a function that accepts an interactiveCLI command configuration and resembles logic we have at `cli.generateInteactiveCliHelp()lib/cli/help/framework.js
. It should be a function that accepts a loadedPlugins
and resembles logic we have at cli.generateMainHelp()
(note we should have CLI: Remove help --verbose option and improve general help output #8497 addressed at this point)lib/cli/help/command.js
. It should be a function that accepts commandName
and command
arguments, and:
cli.displayCommandUsage()
logic, and refer to already implemented lib/cli/help/options.js
InteractiveCli
plugin, run lib/cli/help/interactive.js
with its comand and abortlib/cli/help/framework.js
with serverless.cli.loadedCommands
serverless.cli.loadedCommands
lib/cli/help/command.js
with resolved commancli.displayHelp
callCLI
classresolveCliInput
validateServerlessConfigDependency
and assignDefaultOptions
) as pursued in pluginManager.invoke
are taken care of. Ideally if it's generalized, so can be also used to validate Components CLI inputlib/cli/parse-params.js
commands
and options
to serverless.run()
method. In context serverless.run()
assign those properties onprocessedInput
property
processedInput
, that happens in serverless.init()
. Still let's override there processedInput
with getter that exposes a deprecation message if property is accessed at initialization phase (having that we will remove it next major)initialize
lifecycle hook (it's first lifecycle event propagated unconditionally). Access CLI options from serverless.processedInput
(and treat it as read only)pluginManager.validateOptions
so it's eventual errors do not refer to CLI params (this method will now be effective only for programmatic usage)pluginManager.validateServerlessConfigDependency
so it's eventual errors do not refer to CLI usage (e.g. we should refer to service context and not to service directory)pluginManger.convertShortcutsIntoOptions
as Framework will already be populated with resolved shortcutsAs in 2.1 step, attempt to resolve all variable sources which do not depend on config properties.
If it fails ignore any not supported source errors. If there are other errors in initial stage, ignore them, but after addressing 5.2 signal them with warning message and show a deprecation that with next major we will fail.
5.2 Resolve all remaining variables in service configAs in 2.1 step, attempt to resolve all remaining variables.
If it fails signal them with warning message and show a deprecation that with next major we will fail. Additionally:
null
Remove all variable resolution logic from Framework core
6.0 Run lifecycle events for CLI command(this will most likely lay out naturally and should not require any code changes)
Follow up with serverless.run()
test/unit
folder--verbose
option and improve general help output-v
, --version
CLI params handlingRetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4