From 27c98df3586f2aadd22c0b05498bda6ca50a850b Mon Sep 17 00:00:00 2001 From: Taylor Phillips Date: Thu, 27 Apr 2023 23:36:20 -0500 Subject: [PATCH] Add help flag and error handling for unknown flags and arguments to cli --- src/cli/flags.js | 28 +++++++++- src/cli/index.js | 110 ++++++++++++++++++++++++++++++++++++++++ test/unit/flags-test.js | 30 +++++++++++ 3 files changed, 167 insertions(+), 1 deletion(-) diff --git a/src/cli/flags.js b/src/cli/flags.js index 6261103f..64022d3f 100644 --- a/src/cli/flags.js +++ b/src/cli/flags.js @@ -1,6 +1,27 @@ let { statSync } = require('fs') let minimist = require('minimist') +let allowedFlags = [ + '_', + 'direct', + 'dirty', + 'debug', + 'd', + 'dry-run', + 'eject', + 'help', + 'h', + 'hydrate', + 'no-hydrate', + 'production', + 'p', + 'prune', + 'static', + 's', + 'verbose', + 'v', +] + /** * Read CLI flags and populate userland options */ @@ -13,11 +34,13 @@ module.exports = function getFlags () { tag: [ 'tags', 't' ], debug: [ 'd' ], verbose: [ 'v' ], + help: [ 'h' ], } - let boolean = [ 'direct', 'debug', 'dry-run', 'eject', 'no-hydrate', 'production', 'static', 'verbose' ] + let boolean = [ 'direct', 'debug', 'dry-run', 'eject', 'help', 'no-hydrate', 'production', 'static', 'verbose' ] let def = { hydrate: true } let args = minimist(process.argv.slice(2), { alias, boolean, default: def }) if (args._[0] === 'deploy') args._.splice(0, 1) + let unknownFlags = Object.keys(args).filter(key => !allowedFlags.includes(key)) // Log levels let logLevel = 'normal' @@ -38,6 +61,9 @@ module.exports = function getFlags () { isDryRun: args['dry-run'] || args.eject, isStatic: args.static, shouldHydrate: args.hydrate, + help: args.help, + unknownFlags: unknownFlags, + unknownArgs: args._, } } diff --git a/src/cli/index.js b/src/cli/index.js index 760acaaa..673e5a02 100755 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -25,6 +25,22 @@ let update = updater('Deploy') */ async function main (/* opts = {} */) { let flags = _flags() + + if (flags.help) { + helpMessage() + return + } + + if (flags.unknownArgs.length > 0) { + unknownArgMessage(flags) + return + } + + if (flags.unknownFlags.length > 0) { + unknownFlagMessage(flags) + return + } + let { deployStage } = flags // Ignore Inventory if passed, and re-Inventory with deployStage set let inventory = await _inventory({ deployStage, env: true }) @@ -67,6 +83,23 @@ module.exports = main if (require.main === module) { (async function () { try { + let flags = _flags() + + if (flags.help) { + helpMessage() + return + } + + if (flags.unknownArgs.length > 0) { + unknownArgMessage(flags) + return + } + + if (flags.unknownFlags.length > 0) { + unknownFlagMessage(flags) + return + } + let inventory = await _inventory({}) banner({ inventory, version: `Deploy ${version}` }) await main({ inventory }) @@ -79,3 +112,80 @@ if (require.main === module) { } })() } + +function helpMessage () { + let output = `Deploy an Architect project to AWS. + +For more information, see the documentation: + + +\x1b[1mUSAGE\x1b[0m + arc deploy [flags] + +\x1b[1mFLAGS\x1b[0m + -d, --direct path/to/function Directly deploy a specific function, code, or config + --dry-run Create a CloudFormation template but do not deploy it (useful for testing) + -n, --name string Deploy a custom named stack + --no-hydrate Do not automatically run npm, bundle or pip before deploying + -p, --production Deploy a CloudFormation stack to a production stack + --prune Remove assets that exist in the static S3 bucket but do not exist in the local /public folder + -s, --static Deploy only the files in the static folder + -t, --tag key=value Add a resource tag to the CloudFormation stack + -v, --verbose Display the full deploy status messages + +\x1b[1mEXAMPLES\x1b[0m + Deploy a staging stack + $ arc deploy + + Deploy a production stack + $ arc deploy --production + + Deploy a custom named stack + $ arc deploy --name mycustomstackname + + Deploy a stack with resource tags + $ arc deploy --tag tagA=foo --tag tagB=bar --tag tagC=baz + + Deploy static assets to S3 + $ arc deploy --static + + Deploy a specific function, code, or config + $ arc deploy --direct src/http/get-index + + Run deploy without deploying + $ arc deploy --dry-run +` + + console.log(output) +} + +function unknownFlagMessage (flags) { + let unknownFlag = flags.unknownFlags[0] + + let prefix + if (unknownFlag.length > 1) { + prefix = '--' + } + else { + prefix = '-' + } + + let output = `unknown flag: ${prefix}${unknownFlag} + +try 'arc deploy --help' for more information +` + + console.log(output) +} + +function unknownArgMessage (flags) { + let unknownArg = flags.unknownArgs[0] + + let output = `unknown argument: ${unknownArg} + +try 'arc deploy --help' for more information +` + + console.log(output) +} + diff --git a/test/unit/flags-test.js b/test/unit/flags-test.js index 598b84fd..6692bc4f 100644 --- a/test/unit/flags-test.js +++ b/test/unit/flags-test.js @@ -122,6 +122,36 @@ test('Tags', t => { t.deepEqual(flags().tags, [ tagA, tagB ], '"-t" flags returns multiple tags') }) +test('Help', t => { + t.plan(3) + + args(`-h`) + t.equal(flags().help, true, '"-h" flag sets help to true') + + args(`--help`) + t.equal(flags().help, true, '"--help" flag sets help to true') + + args(``) + t.equal(flags().help, false, 'Lack of "-h" or "--help flag sets help to false') +}) + +test('Unkown flags', t => { + t.plan(2) + + args(`-z -v --prune --foo --no-hydrate --bar`) + t.deepEqual(flags().unknownFlags, [ 'z', 'foo', 'bar' ], 'Unknown flags are returned in the unknownFlags array') + + args(`--direct --dirty --debug -d --dry-run --eject --help -h --hydrate --no-hydrate --production -p --prune --static -s --verbose -v`) + t.deepEqual(flags().unknownFlags, [], 'Known flags are not returned in the unknownFlags array') +}) + +test('Unkown arguments', t => { + t.plan(1) + + args(`foo bar`) + t.deepEqual(flags().unknownArgs, [ 'foo', 'bar' ], 'Unknown arguments are returned in the unknownArgs array') +}) + test('Teardown', t => { t.plan(1) process.argv = argv