mirror of
https://github.com/serverless/serverless.git
synced 2026-01-25 15:07:39 +00:00
refactor(CLI): Resolve commands and options by schema
This commit is contained in:
parent
f12095a31d
commit
fe663ead50
@ -17,6 +17,16 @@ disabledDeprecations:
|
||||
- '*' # To disable all deprecation messages
|
||||
```
|
||||
|
||||
<a name="CLI_OPTIONS_BEFORE_COMMAND"><div> </div></a>
|
||||
|
||||
## CLI command options should follow command
|
||||
|
||||
Deprecation code: `CLI_OPTIONS_BEFORE_COMMAND`
|
||||
|
||||
Starting with v3.0.0, Serverless will not support putting options before command, e.g. `sls -v deploy` will no longer be recognized as `deploy` command.
|
||||
|
||||
Ensure to always format CLI command as `sls [command..] [options...]`
|
||||
|
||||
<a name="CONFIG_VALIDATION_MODE_DEFAULT"><div> </div></a>
|
||||
|
||||
## `configValidationMode: error` will be new default`
|
||||
|
||||
@ -44,6 +44,7 @@ commands.set('config credentials', {
|
||||
overwrite: {
|
||||
usage: 'Overwrite the existing profile configuration in the credentials file',
|
||||
shortcut: 'o',
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -105,6 +106,7 @@ commands.set('deploy', {
|
||||
options: {
|
||||
'conceal': {
|
||||
usage: 'Hide secrets from the output (e.g. API Gateway key values)',
|
||||
type: 'boolean',
|
||||
},
|
||||
'package': {
|
||||
usage: 'Path of the deployment package',
|
||||
@ -113,9 +115,11 @@ commands.set('deploy', {
|
||||
'verbose': {
|
||||
usage: 'Show all stack events during deployment',
|
||||
shortcut: 'v',
|
||||
type: 'boolean',
|
||||
},
|
||||
'force': {
|
||||
usage: 'Forces a deployment to take place',
|
||||
type: 'boolean',
|
||||
},
|
||||
'function': {
|
||||
usage: "Function name. Deploys a single function (see 'deploy function')",
|
||||
@ -123,6 +127,7 @@ commands.set('deploy', {
|
||||
},
|
||||
'aws-s3-accelerate': {
|
||||
usage: 'Enables S3 Transfer Acceleration making uploading artifacts much faster.',
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -139,10 +144,12 @@ commands.set('deploy function', {
|
||||
},
|
||||
'force': {
|
||||
usage: 'Forces a deployment to take place',
|
||||
type: 'boolean',
|
||||
},
|
||||
'update-config': {
|
||||
usage: 'Updates function configuration, e.g. Timeout or Memory Size without deploying code', // eslint-disable-line max-len
|
||||
shortcut: 'u',
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -183,6 +190,7 @@ commands.set('info', {
|
||||
options: {
|
||||
conceal: {
|
||||
usage: 'Hide secrets from the output (e.g. API Gateway key values)',
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -227,6 +235,7 @@ commands.set('invoke', {
|
||||
log: {
|
||||
usage: 'Trigger logging data output',
|
||||
shortcut: 'l',
|
||||
type: 'boolean',
|
||||
},
|
||||
data: {
|
||||
usage: 'Input data',
|
||||
@ -234,6 +243,7 @@ commands.set('invoke', {
|
||||
},
|
||||
raw: {
|
||||
usage: 'Flag to pass input data as a raw string',
|
||||
type: 'boolean',
|
||||
},
|
||||
context: {
|
||||
usage: 'Context of the service',
|
||||
@ -264,6 +274,7 @@ commands.set('invoke local', {
|
||||
},
|
||||
'raw': {
|
||||
usage: 'Flag to pass input data as a raw string',
|
||||
type: 'boolean',
|
||||
},
|
||||
'context': {
|
||||
usage: 'Context of the service',
|
||||
@ -276,7 +287,7 @@ commands.set('invoke local', {
|
||||
usage: 'Override environment variables. e.g. --env VAR1=val1 --env VAR2=val2',
|
||||
shortcut: 'e',
|
||||
},
|
||||
'docker': { usage: 'Flag to turn on docker use for node/python/ruby/java' },
|
||||
'docker': { usage: 'Flag to turn on docker use for node/python/ruby/java', type: 'boolean' },
|
||||
'docker-arg': {
|
||||
usage: 'Arguments to docker run command. e.g. --docker-arg "-p 9229:9229"',
|
||||
},
|
||||
@ -304,6 +315,7 @@ commands.set('logs', {
|
||||
tail: {
|
||||
usage: 'Tail the log output',
|
||||
shortcut: 't',
|
||||
type: 'boolean',
|
||||
},
|
||||
startTime: {
|
||||
usage:
|
||||
@ -447,6 +459,7 @@ commands.set('remove', {
|
||||
verbose: {
|
||||
usage: 'Show all stack events during deployment',
|
||||
shortcut: 'v',
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -464,6 +477,7 @@ commands.set('rollback', {
|
||||
verbose: {
|
||||
usage: 'Show all stack events during deployment',
|
||||
shortcut: 'v',
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -491,10 +505,12 @@ commands.set('slstats', {
|
||||
enable: {
|
||||
usage: 'Enable stats ("--enable")',
|
||||
shortcut: 'e',
|
||||
type: 'boolean',
|
||||
},
|
||||
disable: {
|
||||
usage: 'Disable stats ("--disable")',
|
||||
shortcut: 'd',
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -507,6 +523,7 @@ commands.set('studio', {
|
||||
autoStage: {
|
||||
usage: 'If specified, generate a random stage. This stage will be removed on exit.',
|
||||
shortcut: 'a',
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -532,6 +549,7 @@ commands.set('upgrade', {
|
||||
options: {
|
||||
major: {
|
||||
usage: 'Enable upgrade to a new major release',
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -561,6 +579,7 @@ const awsServiceOptions = {
|
||||
usage:
|
||||
'Rely on locally resolved AWS credentials instead of loading them from ' +
|
||||
'Dashboard provider settings (applies only to services integrated with Dashboard)',
|
||||
type: 'boolean',
|
||||
},
|
||||
};
|
||||
|
||||
@ -571,8 +590,8 @@ for (const [name, schema] of commands) {
|
||||
if (schema.hasAwsExtension) Object.assign(schema.options, awsServiceOptions);
|
||||
}
|
||||
if (name) {
|
||||
schema.options.help = { usage: 'Show this message', shortcut: 'h' };
|
||||
schema.options.help = { usage: 'Show this message', shortcut: 'h', type: 'boolean' };
|
||||
} else {
|
||||
schema.options['help-interactive'] = { usage: 'Show this message' };
|
||||
schema.options['help-interactive'] = { usage: 'Show this message', type: 'boolean' };
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,42 +4,83 @@
|
||||
|
||||
const memoizee = require('memoizee');
|
||||
const parseArgs = require('./parse-args');
|
||||
const commandsSchema = require('./commands-schema');
|
||||
|
||||
const customSAliasCommands = new Set(['config credentials'], ['config tabcompletion install']);
|
||||
const baseArgsSchema = {
|
||||
boolean: new Set(['help', 'help-interactive', 'use-local-credentials', 'v', 'version']),
|
||||
string: new Set(['app', 'config', 'org', 'stage']),
|
||||
alias: new Map([
|
||||
['c', 'config'],
|
||||
['h', 'help'],
|
||||
]),
|
||||
};
|
||||
|
||||
const resolveArgsSchema = (commandOptionsSchema) => {
|
||||
const options = { boolean: new Set(), string: new Set(), alias: new Map() };
|
||||
for (const [name, optionSchema] of Object.entries(commandOptionsSchema)) {
|
||||
switch (optionSchema.type) {
|
||||
case 'boolean':
|
||||
options.boolean.add(name);
|
||||
break;
|
||||
case 'multiple':
|
||||
options.multiple.add(name);
|
||||
break;
|
||||
default:
|
||||
options.string.add(name);
|
||||
}
|
||||
if (optionSchema.shortcut) options.alias.set(optionSchema.shortcut, name);
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
module.exports = memoizee(() => {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
const baseArgsSchema = {
|
||||
boolean: new Set(['help', 'help-interactive', 'v', 'version']),
|
||||
string: new Set(['app', 'config', 'org', 'stage']),
|
||||
alias: new Map([
|
||||
['c', 'config'],
|
||||
['h', 'help'],
|
||||
]),
|
||||
};
|
||||
|
||||
// Ideally no options should be passed before command (to know what options are supported,
|
||||
// and whether they're boolean or not, we need to know command name upfront).
|
||||
// Still so far we (kind of) supported such notation and we need to maintain it in current major.
|
||||
// Thefore at first resolution stage we use schema that recognizes just some popular options
|
||||
let options = parseArgs(args, baseArgsSchema);
|
||||
|
||||
const command = options._.join(' ');
|
||||
if (!command) {
|
||||
// Ideally we should output version info in whatever context "--version" or "-v" params
|
||||
// are used. Still "-v" is defined also as a "--verbose" alias for some commands.
|
||||
// Support for "--verbose" is expected to go away with
|
||||
// https://github.com/serverless/serverless/issues/1720
|
||||
// Until that's addressed we can recognize "-v" only with no commands
|
||||
baseArgsSchema.boolean.delete('v');
|
||||
baseArgsSchema.alias.set('v', 'version');
|
||||
}
|
||||
if (!customSAliasCommands.has(command)) {
|
||||
// Unfortunately, there are few command for which "-s" aliases different param than "--stage"
|
||||
// This handling ensures we do not break those commands
|
||||
baseArgsSchema.alias.set('s', 'stage');
|
||||
}
|
||||
|
||||
options = parseArgs(args, baseArgsSchema);
|
||||
|
||||
const commands = options._;
|
||||
let commands = options._;
|
||||
delete options._;
|
||||
|
||||
let command = commands.join(' ');
|
||||
|
||||
if (!command) {
|
||||
// Handle eventual special cases, not reflected in commands schema
|
||||
if (options.v) options.version = true;
|
||||
if (options.help || options.version) return { commands, options };
|
||||
}
|
||||
if (command === 'help') return { commands, options };
|
||||
|
||||
// Having command potentially resolved, resolve options again with help of the command schema
|
||||
let commandSchema = commandsSchema.get(command);
|
||||
while (commandSchema) {
|
||||
const resolvedOptions = parseArgs(args, resolveArgsSchema(commandSchema.options));
|
||||
const resolvedCommand = resolvedOptions._.join(' ');
|
||||
if (resolvedCommand === command) {
|
||||
options = resolvedOptions;
|
||||
commands = options._;
|
||||
delete options._;
|
||||
break;
|
||||
}
|
||||
// Unlikely scenario, where after applying the command schema different command resolves
|
||||
// It can happen only in cases where e.g. for "sls deploy --force function -f foo"
|
||||
// we intially assume "deploy" command, while after applying "deploy" command schema it's
|
||||
// actually a "deploy function" command that resolves
|
||||
command = resolvedCommand;
|
||||
commandSchema = commandsSchema.get(resolvedCommand);
|
||||
}
|
||||
|
||||
const argsString = args.join(' ');
|
||||
if (command && argsString !== command && !argsString.startsWith(`${command} `)) {
|
||||
// Some options were passed before command name (e.g. "sls -v deploy"), deprecate such usage
|
||||
require('../utils/logDeprecation')(
|
||||
'CLI_OPTIONS_BEFORE_COMMAND',
|
||||
'"serverless" command options are expected to follow command and not be put before the command.\n' +
|
||||
'Starting from next major Serverless will no longer support the latter form.'
|
||||
);
|
||||
}
|
||||
|
||||
return { commands, options };
|
||||
});
|
||||
|
||||
@ -71,7 +71,7 @@ describe('test/unit/lib/cli/resolve-input.test.js', () => {
|
||||
resolveInput.clear();
|
||||
data = overrideArgv(
|
||||
{
|
||||
args: ['serverless', 'cmd1', 'cmd2', '-s', 'stage'],
|
||||
args: ['serverless', 'package', '-s', 'stage'],
|
||||
},
|
||||
() => resolveInput()
|
||||
);
|
||||
@ -124,5 +124,44 @@ describe('test/unit/lib/cli/resolve-input.test.js', () => {
|
||||
it('should recognize --c alias', async () => {
|
||||
expect(data.options.config).to.equal('conf');
|
||||
});
|
||||
|
||||
it('should recognize --version', async () => {
|
||||
resolveInput.clear();
|
||||
data = overrideArgv(
|
||||
{
|
||||
args: ['serverless', '--version'],
|
||||
},
|
||||
() => resolveInput()
|
||||
);
|
||||
expect(data).to.deep.equal({ commands: [], options: { version: true } });
|
||||
});
|
||||
|
||||
it('should recognize interactive setup', async () => {
|
||||
resolveInput.clear();
|
||||
data = overrideArgv(
|
||||
{
|
||||
args: ['serverless', '--app', 'foo'],
|
||||
},
|
||||
() => resolveInput()
|
||||
);
|
||||
expect(data).to.deep.equal({ commands: [], options: { app: 'foo' } });
|
||||
});
|
||||
});
|
||||
|
||||
describe('"help" command', () => {
|
||||
let data;
|
||||
before(() => {
|
||||
resolveInput.clear();
|
||||
data = overrideArgv(
|
||||
{
|
||||
args: ['serverless', 'help'],
|
||||
},
|
||||
() => resolveInput()
|
||||
);
|
||||
});
|
||||
|
||||
it('should recognize', async () => {
|
||||
expect(data).to.deep.equal({ commands: ['help'], options: {} });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user