Repository: bustlelabs/shep (js)

Issue:
`shep run --all`
To put shep in a CI environment an `--all` flag would help. This would instruct shep to run all the functions against events and return a count of error'd functions (so it has a non zero exit code if anything fails)

Pull request description:
Run multiple functions
Closes #108. 

Patch:
diff --git a/README.md b/README.md
index 1b49a01..a762d80 100644
--- a/README.md
+++ b/README.md
@@ -61,7 +61,7 @@ Commands:
   new [path]                Create a new shep project
   pull                      Pulls a swagger JSON representation of an existing API and writes it to a local file
   push                      Create a new shep project
-  run [name]                Run a function in your local environemnt
+  run [pattern]             Run a function in your local environemnt
 
 Options:
   --version  Show version number                                                                               [boolean]
@@ -91,8 +91,8 @@ Options:
   --version     Show version number                                                                            [boolean]
   --help        Show help                                                                                      [boolean]
   --region, -r  AWS region                                                                                    [required]
-  --stage, -s   AWS API Gateway stage. Read from the shep config in project.json if not provided              [required]
-  --api-id, -a  AWS API Gateway ID. Read from the shep config in project.json if not provided                 [required]
+  --stage, -s   AWS API Gateway stage. Read from the shep config in package.json if not provided              [required]
+  --api-id, -a  AWS API Gateway ID. Read from the shep config in package.json if not provided                 [required]
   --output, -o  Path of the file to output                                                         [default: "api.json"]
 
 Examples:
@@ -118,7 +118,7 @@ Examples:
 ```
 #### `shep run`
 ```
-shep run [name]
+shep run [pattern]
 
 Options:
   --version      Show version number                                                                           [boolean]
@@ -134,7 +134,10 @@ Examples:
   shep run foo --no-build                Run the already built `foo` function in the dist folder
   shep run foo --event default           Runs the `foo` function for just the `default` event
   shep run foo --environment production  Runs the `foo` function with production environment
-  DB_TABLE=custom shep run foo           Runs the `foo` function with process.env.DB_TABLE assigned to custom (vars declared this way will overwrite vals in your environments/${env}.json file)
+  DB_TABLE=custom shep run foo           Runs the `foo` function with process.env.DB_TABLE assigned to custom (vars
+                                         declared this way will overwrite vals in your environments/${env}.json file)
+  shep run '*'                           Runs all functions for all events
+  shep run 'foo-*'                       Runs all functions matching pattern `foo-*`
 ```
 #### `shep deploy`
 ```
diff --git a/package.json b/package.json
index fd33259..ad9b998 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "shep",
-  "version": "3.0.1",
+  "version": "3.0.0",
   "description": "A framework for building JavaScript APIs with AWS API Gateway and Lambda",
   "main": "lib/index.js",
   "scripts": {
@@ -41,7 +41,7 @@
     "bluebird": "^3.4.7",
     "chalk": "^1.1.3",
     "cliui": "^3.2.0",
-    "execa": "^0.6.0",
+    "execa": "^0.5.0",
     "fs-extra-promise": "^0.4.1",
     "inquirer": "^2.0.0",
     "listr": "^0.9.0",
@@ -60,7 +60,7 @@
     "babel-plugin-add-module-exports": "^0.2.1",
     "babel-plugin-transform-async-to-module-method": "^6.16.0",
     "babel-plugin-transform-object-rest-spread": "^6.20.2",
-    "babel-preset-env": "^1.1.8",
+    "babel-preset-es2015-node4": "^2.1.1",
     "babel-register": "^6.18.0",
     "np": "^2.12.0",
     "standard": "^8.6.0",
@@ -68,17 +68,7 @@
   },
   "babel": {
     "presets": [
-      [
-        "env",
-        {
-          "targets": {
-            "node": 4.3
-          },
-          "exclude": [
-            "transform-regenerator"
-          ]
-        }
-      ]
+      "es2015-node4"
     ],
     "plugins": [
       "add-module-exports",
diff --git a/src/commands/run.js b/src/commands/run.js
index df895b2..5bfaf2b 100644
--- a/src/commands/run.js
+++ b/src/commands/run.js
@@ -3,7 +3,7 @@ import run from '../run'
 import * as load from '../util/load'
 import merge from 'lodash.merge'
 
-export const command = 'run [name]'
+export const command = 'run [pattern]'
 export const desc = 'Run a function in your local environemnt'
 export function builder (yargs) {
   return yargs
@@ -19,12 +19,15 @@ export function builder (yargs) {
   .example('shep run foo --no-build', 'Run the already built `foo` function in the dist folder')
   .example('shep run foo --event default', 'Runs the `foo` function for just the `default` event')
   .example('shep run foo --environment production', 'Runs the `foo` function with production environment')
+  .example('DB_TABLE=custom shep run foo', 'Runs the `foo` function with process.env.DB_TABLE assigned to custom (vars declared this way will overwrite vals in your environments/env.json file)')
+  .example('shep run \'*\'', 'Runs all functions for all events')
+  .example('shep run \'foo-*\'', 'Runs all functions matching pattern `foo-*`')
 }
 
 export function handler (opts) {
   const questions = [
     {
-      name: 'name',
+      name: 'pattern',
       message: 'Function',
       type: 'list',
       choices: () => load.funcs()
diff --git a/src/run/index.js b/src/run/index.js
index f29c288..53daceb 100644
--- a/src/run/index.js
+++ b/src/run/index.js
@@ -22,60 +22,81 @@ export default async function (opts) {
     console.log(`Warning: Lambda currently runs node v${awsNodeVersion} but you are using v${processVersion}`)
   }
 
-  const verbose = opts.v
-  const performBuild = opts.build
-  const name = opts.name
-  const env = opts.environment || 'development'
-  const lambdaConfig = load.lambdaConfig(name)
-  const events = load.events(name, opts.event)
-  const [ fileName, handler ] = lambdaConfig.Handler.split('.')
-
-  mergeWith(process.env, load.envVars(env), function (objectValue, sourceValue) {
-    return objectValue
-  })
-
-  const context = {}
-
-  if (performBuild) {
-    await build(name, env)
+  const loggingFunction = logFunction(opts.v)
+  const funcRunner = runFunction(opts)
+  const names = load.funcs(opts.pattern)
+
+  const out = await Promise.map(names, funcRunner)
+  out.map(loggingFunction)
+  console.log(ui.toString())
+
+  const failedFunctions = out.reduce((count, eventResponse) => {
+    return count + eventResponse.filter((e) => e.error).length
+  }, 0)
+
+  if (failedFunctions > 0) {
+    process.exit(failedFunctions)
   }
+}
+
+function runFunction (opts) {
+  return async (name) => {
+    const env = opts.environment || 'development'
+    const performBuild = opts.build
+    const lambdaConfig = load.lambdaConfig(name)
+    const events = load.events(name, opts.event)
+    const [ fileName, handler ] = lambdaConfig.Handler.split('.')
+
+    mergeWith(process.env, load.envVars(env), function (objectValue, sourceValue) {
+      return objectValue
+    })
 
-  const func = requireProject(`dist/${name}/${fileName}`)[handler]
+    const context = {}
 
-  const out = await Promise.map(events, (eventFilename) => {
-    const event = requireProject(`functions/${name}/events/${eventFilename}`)
-    return new Promise((resolve) => {
-      const output = { name: eventFilename }
-      output.start = new Date()
-      try {
-        func(event, context, (err, res) => {
+    if (performBuild) {
+      await build(name, env)
+    }
+
+    const func = requireProject(`dist/${name}/${fileName}`)[handler]
+
+    return await Promise.map(events, (eventFilename) => {
+      const event = requireProject(`functions/${name}/events/${eventFilename}`)
+      return new Promise((resolve) => {
+        const output = { name: eventFilename, funcName: name }
+        output.start = new Date()
+        try {
+          func(event, context, (err, res) => {
+            output.end = new Date()
+            if (err) {
+              output.result = results.error
+              output.response = err
+            } else {
+              output.result = results.success
+              output.response = res
+            }
+            resolve(output)
+          })
+        } catch (e) {
+          output.error = true
           output.end = new Date()
-          if (err) {
-            output.result = results.error
-            output.response = err
-          } else {
-            output.result = results.success
-            output.response = res
-          }
+          output.result = results.exception
+          output.response = e
           resolve(output)
-        })
-      } catch (e) {
-        output.end = new Date()
-        output.result = results.exception
-        output.response = e
-        resolve(output)
-      }
+        }
+      })
     })
-  })
-
-  logOutput(out)
-  out.map((out) => formatOutput(out, verbose))
-  console.log(ui.toString())
+  }
 }
 
-function logOutput (outputs) {
-  if (outputs.length === 1) {
-    console.log(outputs[0].response)
+function logFunction (verbose) {
+  return (functionOutput) => {
+    ui.div(
+      {
+        text: functionOutput[0].funcName,
+        padding: [1, 0, 0, 0]
+      }
+    )
+    functionOutput.map((eventOut) => formatOutput(eventOut, verbose))
   }
 }
 
diff --git a/src/util/build-functions.js b/src/util/build-functions.js
index 6793e8e..7aeb533 100644
--- a/src/util/build-functions.js
+++ b/src/util/build-functions.js
@@ -2,8 +2,4 @@ import exec from './modules/exec'
 
 export default function (PATTERN, NODE_ENV) {
   return exec('webpack', ['--bail'], { env: { PATTERN, NODE_ENV } })
-  .catch({ code: 'ENOENT' }, (e) => {
-    console.warn('No locally installed webpack found. Verify that webpack is installed')
-    throw e
-  })
 }
diff --git a/src/util/load.js b/src/util/load.js
index 8258eb7..3b8dd7b 100644
--- a/src/util/load.js
+++ b/src/util/load.js
@@ -48,12 +48,7 @@ export function api () {
 }
 
 export function envVars (env) {
-  let envConfig
-  try {
-    envConfig = readJSONSync(`environments/${env}.json`)
-  } catch (e) {
-    return {}
-  }
+  const envConfig = readJSONSync(`environments/${env}.json`)
 
   Object.keys(envConfig).forEach(key => {
     let val = envConfig[key]
diff --git a/src/util/modules/exec.js b/src/util/modules/exec.js
index ea795a2..7b2c886 100644
--- a/src/util/modules/exec.js
+++ b/src/util/modules/exec.js
@@ -1,3 +1,2 @@
 import execa from 'execa'
-import Promise from 'bluebird'
-export default Promise.method(execa)
+export default execa


Test patch:
diff --git a/test/build/index-no-webpack.js b/test/build/index-no-webpack.js
deleted file mode 100644
index bd64a74..0000000
--- a/test/build/index-no-webpack.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import test from 'ava'
-import { exec } from '../helpers/exec'
-import td from '../helpers/testdouble'
-
-let error = new Error()
-error.code = 'ENOENT'
-
-td.when(exec('webpack', ['--bail']), { ignoreExtraArgs: true }).thenReject(error)
-td.replace(console, 'warn')
-
-test('Logs to console when no webpack found', async (t) => {
-  const shep = require('../../src')
-  const error = await t.throws(shep.build({ quiet: true }))
-  t.is(error.code, 'ENOENT')
-  td.verify(console.warn(), { ignoreExtraArgs: true })
-})
-
diff --git a/test/build/index.js b/test/build/index.js
index 1265baa..66405f1 100644
--- a/test/build/index.js
+++ b/test/build/index.js
@@ -1,8 +1,5 @@
 import test from 'ava'
-import { exec, didExec } from '../helpers/exec'
-import td from '../helpers/testdouble'
-
-td.when(exec('webpack', ['--bail']), { ignoreExtraArgs: true }).thenReturn(Promise.resolve())
+import { didExec } from '../helpers/exec'
 
 test.before(() => {
   const shep = require('../../src')
diff --git a/test/run/index-all.js b/test/run/index-all.js
new file mode 100644
index 0000000..02262e1
--- /dev/null
+++ b/test/run/index-all.js
@@ -0,0 +1,26 @@
+import test from 'ava'
+import td from '../helpers/testdouble'
+
+const funcNames = ['foo', 'bar']
+const handler = 'handler'
+const config = { Handler: `index.${handler}` }
+const events = [{}]
+const lambdaFunc = td.object([handler])
+td.when(lambdaFunc[handler](td.matchers.isA(Object), td.matchers.isA(Object))).thenCallback(null, 'bar')
+
+const load = td.replace('../../src/util/load')
+td.when(load.funcs('*')).thenReturn(funcNames)
+td.when(load.lambdaConfig(), { ignoreExtraArgs: true }).thenReturn(config)
+td.when(load.events(), { ignoreExtraArgs: true }).thenReturn(events)
+
+const requireProject = td.replace('../../src/util/require-project')
+td.when(requireProject(td.matchers.anything())).thenReturn(lambdaFunc)
+
+test.before(() => {
+  return require('../../src/run/index')({ pattern: '*', build: false })
+})
+
+test('Calls the functions', () => {
+  td.verify(lambdaFunc[handler](), { times: funcNames.length, ignoreExtraArgs: true })
+})
+
diff --git a/test/run/index.js b/test/run/index.js
index d841045..00ac7d8 100644
--- a/test/run/index.js
+++ b/test/run/index.js
@@ -9,6 +9,7 @@ const lambdaFunc = td.object([handler])
 td.when(lambdaFunc[handler](td.matchers.isA(Object), td.matchers.isA(Object))).thenCallback(null, 'bar')
 
 const load = td.replace('../../src/util/load')
+td.when(load.funcs(funcName)).thenReturn([funcName])
 td.when(load.lambdaConfig(funcName)).thenReturn(config)
 td.when(load.events(funcName, td.matchers.anything())).thenReturn(events)
 
@@ -16,7 +17,7 @@ const requireProject = td.replace('../../src/util/require-project')
 td.when(requireProject(td.matchers.contains(funcName))).thenReturn(lambdaFunc)
 
 test.before(() => {
-  return require('../../src/run/index')({ name: funcName, build: false })
+  return require('../../src/run/index')({ pattern: funcName, build: false })
 })
 
 test('Calls the function', () => {