diff --git a/lib/actions/CodePackageLambdaNodejs.js b/lib/actions/CodePackageLambdaNodejs.js index abb36d992..ba28c6436 100644 --- a/lib/actions/CodePackageLambdaNodejs.js +++ b/lib/actions/CodePackageLambdaNodejs.js @@ -104,36 +104,24 @@ class Packager { _validateAndPrepare(evt) { - let _this = this; + // Skip Function if it does not have a lambda + if (!evt.function.cloudFormation || + !evt.function.cloudFormation.lambda || + !evt.function.cloudFormation.lambda.Function) { + throw new SError(evt.function.name + 'does not have a lambda property'); + } - // Get Function JSON - return SUtils.getFunctions( - path.join(_this.S._projectRootPath, 'back', 'modules'), - [evt.function]) - .then(function(functionJsons) { + // Validate lambda attributes + let lambda = evt.function.cloudFormation.lambda; + if (!lambda.Function.Type + || !lambda.Function + || !lambda.Function.Properties + || !lambda.Function.Properties.Runtime + || !lambda.Function.Properties.Handler) { + throw new SError('Missing required lambda attributes'); + } - // Attach to evt - evt.function = functionJsons[0]; - - // Skip Function if it does not have a lambda - if (!evt.function.cloudFormation || - !evt.function.cloudFormation.lambda || - !evt.function.cloudFormation.lambda.Function) { - throw new SError(evt.function.name + 'does not have a lambda property'); - } - - // Validate lambda attributes - let lambda = evt.function.cloudFormation.lambda; - if (!lambda.Function.Type - || !lambda.Function - || !lambda.Function.Properties - || !lambda.Function.Properties.Runtime - || !lambda.Function.Properties.Handler) { - throw new SError('Missing required lambda attributes'); - } - - return evt; - }); + return BbPromise.resolve(evt); } /** @@ -250,8 +238,7 @@ class Packager { _optimize(evt) { - let _this = this, - lambda = evt.function.cloudFormation.lambda; + let _this = this; if (!evt.function.package.optimize || !evt.function.package.optimize.builder) { @@ -368,6 +355,7 @@ class Packager { return new BbPromise(function(resolve, reject) { b.bundle(function(err, bundledBuf) { + if (err) { console.error('Error running browserify bundle'); reject(err); diff --git a/lib/actions/EndpointPrepareApiGateway.js b/lib/actions/EndpointPrepareApiGateway.js index ac6bda531..ce8ab4799 100644 --- a/lib/actions/EndpointPrepareApiGateway.js +++ b/lib/actions/EndpointPrepareApiGateway.js @@ -69,53 +69,41 @@ class EndpointPackageApiGateway extends SPlugin { _validateAndPrepare(evt) { - let _this = this; + // If endpoint properties are missing, skip + if (!evt.function.cloudFormation || + !evt.function.cloudFormation.apiGateway || + !evt.function.cloudFormation.apiGateway.Endpoint) { + throw new SError(evt.function.name + ' does not have required apiGateway properties'); + } - // Get Function JSON and validate Endpoint(s) - return SUtils.getFunctions( - _this.S._projectRootPath, - [evt.function]) - .then(function(functionJsons) { + evt.endpoints = evt.function.cloudFormation.apiGateway.Endpoint; - // Attach to evt - evt.function = functionJsons[0]; + // Endpoint property can be an array to support multiple endpoints per function + // Convert endpointJson to array, if it's not already + if (!Array.isArray(evt.endpoints)) evt.endpoints = [evt.endpoints]; - // If endpoint properties are missing, skip - if (!evt.function.cloudFormation || - !evt.function.cloudFormation.apiGateway || - !evt.function.cloudFormation.apiGateway.Endpoint) { - throw new SError(evt.function.name + ' does not have required apiGateway properties'); - } + // Validate all evt.endpoints + for (let i = 0; i < evt.endpoints.length;i++) { - evt.endpoints = evt.function.cloudFormation.apiGateway.Endpoint; + let e = evt.endpoints[i]; - // Endpoint property can be an array to support multiple endpoints per function - // Convert endpointJson to array, if it's not already - if (!Array.isArray(evt.endpoints)) evt.endpoints = [evt.endpoints]; + // Validate and sanitize endpoint attributes + if (!e.Type + || !e.Path + || !e.Method + || !e.AuthorizationType + || typeof e.ApiKeyRequired === 'undefined') { + return BbPromise.reject(new SError('Missing one of many required endpoint attributes: Type, Path, Method, AuthorizationType, ApiKeyRequired')); + } - // Validate all evt.endpoints - for (let i = 0; i < evt.endpoints.length;i++) { + // Sanitize path + if (e.Path.charAt(0) === '/') e.Path = e.Path.substring(1); - let e = evt.endpoints[i]; + // Sanitize method + e.Method = e.Method.toUpperCase(); + } - // Validate and sanitize endpoint attributes - if (!e.Type - || !e.Path - || !e.Method - || !e.AuthorizationType - || typeof e.ApiKeyRequired === 'undefined') { - return BbPromise.reject(new SError('Missing one of many required endpoint attributes: Type, Path, Method, AuthorizationType, ApiKeyRequired')); - } - - // Sanitize path - if (e.Path.charAt(0) === '/') e.Path = e.Path.substring(1); - - // Sanitize method - e.Method = e.Method.toUpperCase(); - } - - return evt; - }); + return BbPromise.resolve(evt); } /** diff --git a/lib/actions/FunctionDeploy.js b/lib/actions/FunctionDeploy.js index 311d2b2d6..4d3618094 100644 --- a/lib/actions/FunctionDeploy.js +++ b/lib/actions/FunctionDeploy.js @@ -101,15 +101,37 @@ class FunctionDeploy extends SPlugin { functionDeploy(event) { - let _this = this; - let evt = {}; - evt.type = event.type ? event.type : null; - evt.stage = event.stage ? event.stage : null; - evt.regions = event.region ? [event.region] : []; - evt.paths = event.paths ? event.paths : []; - evt.all = event.all ? event.all : null; - evt.aliasEndpoint = event.aliasEndpoint ? event.aliasEndpoint : null; - evt.aliasFunction = event.aliasFunction ? event.aliasFunction : null; + let _this = this, + evt = {}; + + // If CLI, parse options + if (_this.S.cli) { + + // Options + evt = this.S.cli.options; + + // Option - Non-interactive + if (_this.S.cli.options.nonInteractive) _this.S._interactive = false + + // Type - Should be first in array + if (_this.S.cli.params.length) evt.type = _this.S.cli.params[0]; + + // Function paths - They should be all other array items + _this.S.cli.params.splice(0,1); + evt.paths = _this.S.cli.params; + } + + // If NO-CLI, add options + if (event) evt = event; + + // Add defaults + evt.type = evt.type ? evt.type : 'code'; + evt.stage = evt.stage ? evt.stage : null; + evt.regions = evt.region ? [evt.region] : []; + evt.paths = evt.paths ? evt.paths : []; + evt.all = evt.all ? true : false; + evt.aliasEndpoint = evt.aliasEndpoint ? evt.aliasEndpoint : null; + evt.aliasFunction = evt.aliasFunction ? evt.aliasFunction : null; evt.functions = []; evt.deployed = {}; evt.failed = {}; @@ -118,6 +140,7 @@ class FunctionDeploy extends SPlugin { return _this._validateAndPrepare(evt) .bind(_this) .then(_this._promptStage) + .then(_this._prepareRegions) .then(_this._processDeployment) .then(function(evt) { return evt; @@ -133,27 +156,18 @@ class FunctionDeploy extends SPlugin { let _this = this; - // If CLI, parse command line input and validate - if (_this.S.cli) { - - // Add Options - evt = _this.S.cli.options; - - // Add type. Should be first in array - evt.type = _this.S.cli.params[0]; - - // Add function paths. Should be all other array items - _this.S.cli.params.splice(0,1); - evt.paths = _this.S.cli.params; - } - - // If NO-CLI, validate + // If NO-CLI, validate paths if (!_this.S.cli) { - // Check if paths or all is not used + // Validate Paths if (!evt.paths.length && !evt.all) { throw new SError(`One or multiple paths are required`); } + + // Validate Stage + if (!evt.stage) { + throw new SError(`Stage is required`); + } } // Validate type @@ -164,46 +178,38 @@ class FunctionDeploy extends SPlugin { throw new SError(`Invalid type. Must be "code", "endpoint", or "both" `); } - // Validate stage - if (!evt.stage) { - throw new SError(`Stage is required`); - } + // Get Functions From Paths - // If no region specified, deploy to all regions in stage - if (!evt.regions.length) { - evt.regions = _this.S._projectJson.stages[evt.stage].map(rCfg => { - return rCfg.region; - }); - } - - SUtils.sDebug('Queued regions: ' + evt.regions); - - // If CLI and paths are missing, get paths from CWD, and return if (_this.S.cli) { - if (!evt.paths || !evt.paths.length) { - // If CLI and no paths, get full paths from CWD - return SUtils.getFunctions( - evt.all ? _this.S._projectRootPath : process.cwd(), - null) - .then(function(functions) { + // If CLI & paths, get functions + // If CLI & no paths, get full paths from CWD - if (!functions.length) throw new SError(`No functions found`); + return SUtils.getFunctions( + evt.all ? _this.S._projectRootPath : process.cwd(), + evt.paths && evt.paths.length ? evt.paths : null) + .then(function(functions) { + if (!functions.length) throw new SError(`No functions found`); + evt.functions = functions; + // Delete Paths + if (evt.paths) delete evt.paths; + return evt; + }); + + } else { + + // If NO-CLI, resolve full paths from submitted paths + return SUtils.getFunctions( + _this.S._projectRootPath, + evt.all ? null : evt.paths) + .then(function(functions) { + evt.functions = functions; + // Delete Paths + if (evt.paths) delete evt.paths; + return evt; + }); - evt.functions = functions; - return evt; - }); - } } - - // Otherwise, resolve full paths - return SUtils.getFunctions( - _this.S._projectRootPath, - evt.all ? null : evt.paths) - .then(function(functions) { - evt.functions = functions; - return evt; - }); } /** @@ -212,19 +218,17 @@ class FunctionDeploy extends SPlugin { _promptStage(evt) { - let _this = this, - stages = []; + let _this = this; - if (!evt.stage) { + // If user provided stage, skip prompt + if (evt.stage) return BbPromise.resolve(evt); - stages = Object.keys(_this.S._projectJson.stage); + // Collect project stages + let stages = Object.keys(_this.S._projectJson.stages); - // If project only has 1 stage, skip prompt - if (stages.length === 1) evt.stage = stages[0]; - - } else { - - // If user provided stage, skip prompt + // If project only has 1 stage, skip prompt + if (stages.length === 1) { + evt.stage = stages[0]; return BbPromise.resolve(evt); } @@ -246,6 +250,22 @@ class FunctionDeploy extends SPlugin { }); } + /** + * Prepare Regions + */ + + _prepareRegions(evt) { + + // If no region specified, deploy to all regions in stage + if (!evt.regions.length) { + evt.regions = this.S._projectJson.stages[evt.stage].map(rCfg => { + return rCfg.region; + }); + } + + return evt; + } + /** * Process Deployment */ @@ -254,6 +274,9 @@ class FunctionDeploy extends SPlugin { let _this = this; + // Status + SCli.log('Deploying in "' + evt.stage + '" to the following regions: ' + evt.regions); + return BbPromise.try(function() { return evt.regions; }) @@ -270,9 +293,11 @@ class FunctionDeploy extends SPlugin { endpoints: [], }; - //Deploy Function Code in each region + // Deploy Function Code in each region if (['code', 'both'].indexOf(evt.type) > -1) { + // Status + SCli.log('"' + evt.stage + ' - ' + region + '": Deploying function code...'); return _this._deployCodeByRegion(evt, region); } }) @@ -281,11 +306,11 @@ class FunctionDeploy extends SPlugin { }) .each(function(region) { - /** - * Deploy Function Endpoints in each region - */ + // Deploy Function Endpoints in each region if (['endpoint', 'both'].indexOf(evt.type) > -1) { + // Status + SCli.log('"' + evt.stage + ' - ' + region + '" - Deploying function endpoints...'); return _this._deployEndpointsByRegion(evt, region) } }) @@ -318,7 +343,7 @@ class FunctionDeploy extends SPlugin { _this.S._projectJson, evt.stage, region), - function: func.path, + function: func, }; // Process sub-Actions @@ -368,7 +393,7 @@ class FunctionDeploy extends SPlugin { _this.S._projectJson, evt.stage, region), - function: func.path, + function: func, aliasEndpoint: evt.aliasEndpoint, }; diff --git a/lib/actions/ModuleCreate.js b/lib/actions/ModuleCreate.js index 9e4d1e9c3..9a4319c1f 100644 --- a/lib/actions/ModuleCreate.js +++ b/lib/actions/ModuleCreate.js @@ -125,8 +125,7 @@ usage: serverless module create`, .then(_this._createModuleSkeleton) .then(_this._installFunctionDependencies) .then(function() { - SCli.log('Successfully created ' + _this.evt.module + '/' + _this.evt.function); - + SCli.log('Successfully created new serverless module "' + _this.evt.module + '" with its first function "' + _this.evt.function + '"'); // Return Event return _this.evt; }); @@ -243,29 +242,21 @@ usage: serverless module create`, // Save Paths _this.evt.modulePath = path.join(this.S._projectRootPath, 'back', 'modules', _this.evt.module); - _this.evt.functionPath = path.join(_this.evt.modulePath, _this.evt.function); + _this.evt.functionPath = path.join(_this.evt.modulePath, _this.evt.function); // Prep package.json packageJsonTemplate.name = _this.evt.module; - packageJsonTemplate.description = 'A serverless module'; - packageJsonTemplate.dependencies = {}; - + packageJsonTemplate.description = 'Dependencies for a Serverless Module written in Node.js'; + // Write base module structure writeDeferred.push( fs.mkdirSync(_this.evt.modulePath), fs.mkdirSync(path.join(_this.evt.modulePath, 'lib')), fs.mkdirSync(path.join(_this.evt.modulePath, 'package')), fs.mkdirSync(path.join(_this.evt.modulePath, 'package', 'functions')), - fs.mkdirSync(path.join(_this.evt.modulePath, 'node_modules')), SUtils.writeFile(path.join(_this.evt.modulePath, 'package.json'), JSON.stringify(packageJsonTemplate, null, 2)), SUtils.writeFile(path.join(_this.evt.modulePath, 's-module.json'), JSON.stringify(moduleJsonTemplate, null, 2)) ); - - // Copy NPM Dependencies - wrench.copyDirSyncRecursive( - path.join(_this._templatesDir, 'nodejs', 'dotenv'), - path.join(_this.evt.modulePath, 'node_modules', 'dotenv') - ); // Write module/function structure writeDeferred.push( @@ -295,6 +286,7 @@ usage: serverless module create`, */ _generateFunctionJson() { + let _this = this; let functionJsonTemplate = SUtils.readAndParseJsonSync(path.join(this._templatesDir, 's-function.json')); @@ -330,7 +322,9 @@ usage: serverless module create`, }; _installFunctionDependencies() { - return SUtils.npmInstall(this.evt.modulePath); + SCli.log('Installing "serverless-helpers" for this module via NPM...'); + SUtils.npmInstall(this.evt.modulePath); + return BbPromise.resolve(); } } diff --git a/lib/serverless.js b/lib/serverless.js index 59a953d1e..532928d25 100644 --- a/lib/serverless.js +++ b/lib/serverless.js @@ -124,7 +124,11 @@ class Serverless { command(argv) { - SUtils.sDebug('command argv', argv); + // Set Debug to True + if (argv && argv.d) process.env.DEBUG = true; + + SUtils.sDebug('Command raw argv: ', argv); + // Handle version command if (argv._[0] === 'version') { console.log(this._version); @@ -134,7 +138,7 @@ class Serverless { let cmdContext = argv._[0], cmdContextAction = argv._[1]; - this.cli = {}; //options and args that the command was called with on the CLI so plugins can leverage + this.cli = {}; // Options and args that the command was called with on the CLI so plugins can leverage if (argv._.length === 0 || argv._[0] === 'help' || argv._[0] === 'h') { if (!this.commands[cmdContext]) { diff --git a/lib/templates/nodejs/dotenv/README.md b/lib/templates/nodejs/dotenv/README.md deleted file mode 100644 index de324261a..000000000 --- a/lib/templates/nodejs/dotenv/README.md +++ /dev/null @@ -1,198 +0,0 @@ -# dotenv - -dotenv - -Dotenv loads environment variables from `.env` into `ENV` (process.env). - -[![BuildStatus](https://img.shields.io/travis/motdotla/dotenv/master.svg?style=flat-square)](https://travis-ci.org/motdotla/dotenv) -[![NPM version](https://img.shields.io/npm/v/dotenv.svg?style=flat-square)](https://www.npmjs.com/package/dotenv) -[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard) - -> "Storing [configuration in the environment](http://www.12factor.net/config) -> is one of the tenets of a [twelve-factor app](http://www.12factor.net/). -> Anything that is likely to change between deployment environments–such as -> resource handles for databases or credentials for external services–should be -> extracted from the code into environment variables. -> -> But it is not always practical to set environment variables on development -> machines or continuous integration servers where multiple projects are run. -> Dotenv loads variables from a `.env` file into ENV when the environment is -> bootstrapped." -> -> [Brandon Keepers' Dotenv in Ruby](https://github.com/bkeepers/dotenv) - -## Install - -```bash -npm install dotenv --save -``` - -## Usage - -As early as possible in your application, require and load dotenv. - -```javascript -require('dotenv').load(); -``` - -Create a `.env` file in the root directory of your project. Add -environment-specific variables on new lines in the form of `NAME=VALUE`. -For example: - -``` -DB_HOST=localhost -DB_USER=root -DB_PASS=s1mpl3 -``` - -That's it. - -`process.env` now has the keys and values you defined in your `.env` file. - -```javascript -db.connect({ - host: process.env.DB_HOST, - username: process.env.DB_USER, - password: process.env.DB_PASS -}); -``` - -### Preload - -If you are using iojs-v1.6.0 or later, you can use the `--require` (`-r`) command line option to preload dotenv. By doing this, you do not need to require and load dotenv in your application code. - - -```bash -$ node -r dotenv/config your_script.js -``` - -The configuration options below are supported as command line arguments in the format `dotenv_config_