diff --git a/lib/ServerlessFunction.js b/lib/ServerlessFunction.js index 980c1a4e1..fa531ecf9 100644 --- a/lib/ServerlessFunction.js +++ b/lib/ServerlessFunction.js @@ -22,7 +22,7 @@ class ServerlessFunction { constructor(Serverless, options) { this.S = Serverless; this.options = options || {}; - this.load(options); + this.load(); } /** @@ -30,13 +30,11 @@ class ServerlessFunction { * - Load from source (i.e., file system); */ - load(module, func) { + load() { let _this = this; - if (module) { - _this.options.module = module; - _this.options.function = func; + if (_this.options.module && _this.options.function) { _this.options.functionPath = path.join( _this.S.config.projectPath, 'back', @@ -47,14 +45,53 @@ class ServerlessFunction { 's-function.json'); } - // TODO: Validate function exists - // Defaults _this.data = {}; - _this.data.endpoints = []; + _this.data.name = _this.options.function || 'function' + SUtils.generateShortId(6); + _this.data.custom = { + "excludePatterns": [], + "envVars": [] + }; + _this.data.handler = ""; + _this.data.timeout = 6; + _this.data.memorySize = 1024; + _this.data.events = [ + { + "name": "default", + "eventSourceArn": "", + "startingPosition": "TRIM_HORIZON", + "batchSize": 100, + "enabled": true + } + ]; + _this.data.endpoints = [ + { + "path": "", + "method": "GET", + "authorizationType": "none", + "apiKeyRequired": false, + "requestParameters": {}, + "requestTemplates": { + "application/json": "" + }, + "responses": { + "default": { + "statusCode": "200", + "responseParameters": {}, + "responseModels": {}, + "responseTemplates": { + "application/json": "" + } + }, + "400": { + "statusCode": "400" + } + } + } + ]; // If no function path exists, return - if (!_this.options.functionPath) return; + if (!_this.options.functionPath || !SUtils.fileExistsSync(path.join(_this.options.functionPath, 's-function.json'))) return; let functionJson = SUtils.readAndParseJsonSync(path.join(_this.options.functionPath, 's-function.json')); diff --git a/lib/ServerlessModule.js b/lib/ServerlessModule.js index 658d1725a..ddb4036ae 100644 --- a/lib/ServerlessModule.js +++ b/lib/ServerlessModule.js @@ -22,7 +22,7 @@ class ServerlessModule { constructor(Serverless, options) { this.S = Serverless; this.options = options || {}; - this.load(options); + this.load(); } /** @@ -30,25 +30,23 @@ class ServerlessModule { * - Load from source (i.e., file system); */ - load(module) { + load() { let _this = this; - if (moduleJson) { - _this.options.module = moduleJson; - _this.options.modulePath = path.join(_this.S.config.projectPath, 'back', 'modules', _this.options.module, 's-module.json') + if (_this.options.module) { + _this.options.modulePath = path.join(_this.S.config.projectPath, 'back', 'modules', _this.options.module) } - // TODO: Validate module exists in project - // Defaults _this.data = {}; - _this.data.name = 'module' + SUtils.generateShortId(6); + _this.data.name = _this.options.module || 'module' + SUtils.generateShortId(6); _this.data.version = '0.0.1'; _this.data.profile = 'aws-v' + require('../package.json').version; _this.data.location = 'https://github.com/...'; _this.data.author = ''; _this.data.description = 'A Serverless Module'; + _this.data.runtime = _this.options.runtime || 'nodejs'; _this.data.custom = {}; _this.data.functions = {}; _this.data.cloudFormation = { @@ -57,9 +55,9 @@ class ServerlessModule { }; // If no project path exists, return - if (!_this.S.config.projectPath || _this.options.module) return; + if (!_this.S.config.projectPath || !_this.options.module || !SUtils.fileExistsSync(path.join(_this.options.modulePath, 's-module.json'))) return; - let moduleJson = SUtils.readAndParseJsonSync(path.join(_this.S.config.projectPath, 'back', 'modules', _this.options.module, 's-module.json')); + let moduleJson = SUtils.readAndParseJsonSync(path.join(_this.options.modulePath, 's-module.json')); // Add Functions moduleJson.functions = {}; diff --git a/lib/actions/FunctionCreate.js b/lib/actions/FunctionCreate.js index f7c26a290..6195c2770 100644 --- a/lib/actions/FunctionCreate.js +++ b/lib/actions/FunctionCreate.js @@ -32,7 +32,7 @@ module.exports = function(SPlugin, serverlessPath) { constructor(S, config) { super(S, config); this._templatesDir = path.join(__dirname, '..', 'templates'); - this.evt = {}; + this.options = {}; } static getName() { @@ -83,19 +83,15 @@ usage: serverless function create `, * Action */ - functionCreate(evt) { + functionCreate(options) { let _this = this; + this.options = options || {}; - if (evt) { - _this.evt = evt; - _this.S.config.interactive = false; - } - - // If CLI and not subaction, parse options - if (_this.S.cli && (!evt || !evt._subaction)) { - _this.evt = JSON.parse(JSON.stringify(this.S.cli.options)); // Important: Clone objects, don't refer to them - if (_this.S.cli.options.nonInteractive) _this.S.config.interactive = false; + // If CLI, parse arguments + if (this.S.cli && (!options || !options.subaction)) { + this.options = JSON.parse(JSON.stringify(this.S.cli.options)); // Important: Clone objects, don't refer to them + if (this.S.cli.options.nonInteractive) this.S.config.interactive = false; } return _this._promptModuleFunction() @@ -103,8 +99,12 @@ usage: serverless function create `, .then(_this._validateAndPrepare) .then(_this._createFunctionSkeleton) .then(function() { - SCli.log('Successfully created function: "' + _this.functionName + '"'); - return _this.evt; + SCli.log('Successfully created function: "' + _this.options.function + '"'); + + // Return + return { + options: _this.options + } }); } @@ -120,7 +120,7 @@ usage: serverless function create `, if (!_this.S.config.interactive) return BbPromise.resolve(); ['module', 'function'].forEach(memberVarKey => { - overrides[memberVarKey] = _this['evt'][memberVarKey]; + overrides[memberVarKey] = _this['options'][memberVarKey]; }); let prompts = { @@ -140,8 +140,8 @@ usage: serverless function create `, return _this.cliPromptInput(prompts, overrides) .then(function(answers) { - _this.evt.module = answers.module; - _this.evt.function = answers.function; + _this.options.module = answers.module; + _this.options.function = answers.function; }); }; @@ -153,53 +153,34 @@ usage: serverless function create `, _validateAndPrepare() { // Non interactive validation if (!this.S.config.interactive) { - if (!this.evt.module || !this.evt.function) { + if (!this.options.module || !this.options.function) { return BbPromise.reject(new SError('Missing module or/and function names')); } } - // Add default runtime - if (!this.evt.runtime) { - this.evt.runtime = 'nodejs'; - } - - // Check if runtime is supported - if (!SUtils.supportedRuntimes[this.evt.runtime]) { - return BbPromise.reject(new SError('Unsupported runtime ' + this.evt.runtime, SError.errorCodes.UNKNOWN)); - } - - // Set default function template - if (!this.evt.template) this.evt.template = 's-function.json'; - // Sanitize function folder name - this.evt.function = this.evt.function.toLowerCase().trim() + this.options.function = this.options.function.toLowerCase().trim() .replace(/\s/g, '-') .replace(/[^a-zA-Z-\d:]/g, '') .substring(0, 19); // If module does NOT exists, throw error - this.evt.module = this.evt.module.trim(); + this.options.module = this.options.module.trim(); - let moduleFullPath = path.join(this.S.config.projectPath, 'back', 'modules', this.evt.module); + let moduleFullPath = path.join(this.S.config.projectPath, 'back', 'modules', this.options.module); if (!SUtils.dirExistsSync(moduleFullPath)) { - return BbPromise.reject(new SError('module ' + this.evt.module + ' does NOT exist', + return BbPromise.reject(new SError('module ' + this.options.module + ' does NOT exist', SError.errorCodes.INVALID_PROJECT_SERVERLESS)); } // If function already exists in module, throw error - let functionPath = path.join(this.S.config.projectPath, 'back', 'modules', this.evt.module, this.evt.function); + let functionPath = path.join(this.S.config.projectPath, 'back', 'modules', this.options.module, 'functions', this.options.function); if (SUtils.dirExistsSync(functionPath)) { - return BbPromise.reject(new SError('function ' + this.evt.function + ' already exists in module ' + this.evt.module, + return BbPromise.reject(new SError('function ' + this.options.function + ' already exists in module ' + this.options.module, SError.errorCodes.INVALID_PROJECT_SERVERLESS )); } - // Fetch Module Json - let moduleName = this.evt.module; - - this.evt.module = SUtils.readAndParseJsonSync(path.join(moduleFullPath, 's-module.json')); - this.evt.module.pathModule = path.join('back', 'modules', moduleName); - return BbPromise.resolve(); }; @@ -212,62 +193,23 @@ usage: serverless function create `, let _this = this, writeDeferred = [], + functionInstance = new _this.S.classes.Function(_this.S, { module: _this.options.module, function: _this.options.function}), + functionPath = path.join('back', 'modules', _this.options.module, 'functions', _this.options.function), eventJson = {}, handlerJs = fs.readFileSync(path.join(this._templatesDir, 'nodejs', 'handler.js')); - // Generate Function JSON - let functionShortName = _this.evt.function; - let functionJson = _this._generateFunctionJson(); - - // Change EVT function to Object - _this.evt.function = functionJson.functions[Object.keys(functionJson.functions)[0]]; - _this.pathFunction = path.join('back', 'modules', _this.evt.module.name, functionShortName); - - // Set function on event - eventJson[_this.functionName] = {}; + eventJson[_this.options.function] = {}; // Write function files: directory, handler, event.json, s-function.json writeDeferred.push( - fs.mkdirSync(path.join(_this.S.config.projectPath, _this.pathFunction)), - SUtils.writeFile(path.join(path.join(_this.S.config.projectPath, _this.pathFunction), 'handler.js'), handlerJs), - SUtils.writeFile(path.join(_this.S.config.projectPath, _this.pathFunction, 'event.json'), JSON.stringify(eventJson, null, 2)), - SUtils.writeFile(path.join(_this.S.config.projectPath, _this.pathFunction, 's-function.json'), JSON.stringify(functionJson, null, 2)) + fs.mkdirSync(path.join(_this.S.config.projectPath, functionPath)), + SUtils.writeFile(path.join(path.join(_this.S.config.projectPath, functionPath), 'handler.js'), handlerJs), + SUtils.writeFile(path.join(_this.S.config.projectPath, functionPath, 'event.json'), JSON.stringify(eventJson, null, 2)), + functionInstance.save() ); - // Add path function to evt function - _this.evt.function.pathFunction = _this.pathFunction; - _this.evt.function.name = _this.functionName; - return BbPromise.all(writeDeferred); }; - - /* - * Generate s-function.json template (private) - */ - - _generateFunctionJson() { - - let _this = this; - - // Load s-function.json template - let functionJsonTemplate = SUtils.readAndParseJsonSync(path.join(_this._templatesDir, _this.evt.template)); - - // Add Full Function Name (Include Module Name) - _this.functionName = _this.evt.function; - functionJsonTemplate.functions[_this.functionName] = functionJsonTemplate.functions.functionTemplate; - - // Remove Template - delete functionJsonTemplate.functions.functionTemplate; - - // Add Function Handler - functionJsonTemplate.functions[_this.functionName].handler = path.join('modules', _this.evt.module.name, _this.evt.function, 'handler.handler'); - - // Add endpoint path - functionJsonTemplate.functions[_this.functionName].endpoints[0].path = _this.evt.module.name + '/' + _this.evt.function; - - // Return - return functionJsonTemplate; - }; } return( FunctionCreate ); diff --git a/lib/actions/ModuleCreate.js b/lib/actions/ModuleCreate.js index 9b0cbfb50..9772d3632 100644 --- a/lib/actions/ModuleCreate.js +++ b/lib/actions/ModuleCreate.js @@ -31,7 +31,7 @@ module.exports = function(SPlugin, serverlessPath) { constructor(S, config) { super(S, config); this._templatesDir = path.join(__dirname, '..', 'templates'); - this.evt = {}; + this.options = {}; } static getName() { @@ -81,22 +81,15 @@ usage: serverless module create`, * Action */ - moduleCreate(evt) { + moduleCreate(options) { let _this = this; + this.options = options || {}; - if (evt) { - _this.evt = evt; - _this.S.config.interactive = false; - } - - // If CLI, parse options - if (_this.S.cli) { - _this.evt = JSON.parse(JSON.stringify(this.S.cli.options)); // Important: Clone objects, don't refer to them - - if (_this.S.cli.options.nonInteractive) { - _this.S.config.interactive = false; - } + // If CLI, parse arguments + if (this.S.cli && (!options || !options.subaction)) { + this.options = JSON.parse(JSON.stringify(this.S.cli.options)); // Important: Clone objects, don't refer to them + if (this.S.cli.options.nonInteractive) this.S.config.interactive = false; } return _this._promptModuleFunction() @@ -106,23 +99,23 @@ usage: serverless module create`, .then(function() { let _this = this, - evtClone = { + options = { _subaction: true, - module: _this.evt.module.name, - function: _this.evt.function + module: _this.options.module, + function: _this.options.function }; - return _this.S.actions.functionCreate(evtClone) - .then(function(evt) { - _this.evt.function = evt.function; - }); + return _this.S.actions.functionCreate(options) }) .then(_this._installModuleDependencies) .then(function() { SCli.log('Successfully created new serverless module "' + _this.evt.module.name + '" with its first function "' + _this.evt.function.name + '"'); - // Return Event - return _this.evt; + + // Return + return { + options: _this.options + } }); }; @@ -138,7 +131,7 @@ usage: serverless module create`, if (!_this.S.config.interactive) return BbPromise.resolve(); ['module', 'function'].forEach(memberVarKey => { - overrides[memberVarKey] = _this['evt'][memberVarKey]; + overrides[memberVarKey] = _this['options'][memberVarKey]; }); let prompts = { @@ -158,8 +151,8 @@ usage: serverless module create`, return _this.cliPromptInput(prompts, overrides) .then(function(answers) { - _this.evt.module = answers.module; - _this.evt.function = answers.function; + _this.options.module = answers.module; + _this.options.function = answers.function; }); }; @@ -171,40 +164,37 @@ usage: serverless module create`, // non interactive validation if (!this.S.config.interactive) { - if (!this.evt.module || !this.evt.function) { + if (!this.options.module || !this.options.function) { return BbPromise.reject(new SError('Missing module or/and function names')); } } // Add default runtime - if (!this.evt.runtime) { - this.evt.runtime = 'nodejs'; + if (!this.options.runtime) { + this.options.runtime = 'nodejs'; } - if (!SUtils.supportedRuntimes[this.evt.runtime]) { - return BbPromise.reject(new SError('Unsupported runtime ' + this.evt.runtime, SError.errorCodes.UNKNOWN)); + if (!SUtils.supportedRuntimes[this.options.runtime]) { + return BbPromise.reject(new SError('Unsupported runtime ' + this.options.runtime, SError.errorCodes.UNKNOWN)); } - // Set default function template - if (!this.evt.template) this.evt.template = 's-function.json'; - // Sanitize module - this.evt.module = this.evt.module.toLowerCase().trim() + this.options.module = this.options.module.toLowerCase().trim() .replace(/\s/g, '-') .replace(/[^a-zA-Z-\d:]/g, '') .substring(0, 19); // Sanitize function - this.evt.function = this.evt.function.toLowerCase().trim() + this.options.function = this.options.function.toLowerCase().trim() .replace(/\s/g, '-') .replace(/[^a-zA-Z-\d:]/g, '') .substring(0, 19); // If module already exists, throw error - let pathModule = path.join(this.S.config.projectPath, 'back', 'modules', this.evt.module); + let pathModule = path.join(this.S.config.projectPath, 'back', 'modules', this.options.module); if (SUtils.dirExistsSync(pathModule)) { return BbPromise.reject(new SError( - 'Module ' + this.evt.module + ' already exists', + 'Module ' + this.options.module + ' already exists', SError.errorCodes.INVALID_PROJECT_SERVERLESS )); } @@ -220,59 +210,30 @@ usage: serverless module create`, let _this = this, writeDeferred = [], - moduleJsonTemplate = _this._generateModuleJson(), + module = new _this.S.classes.Module(_this.S, { module: _this.options.module, runtime: _this.options.runtime }), + pathModule = path.join('back', 'modules', _this.options.module), packageJsonTemplate = SUtils.readAndParseJsonSync(path.join(_this._templatesDir, 'nodejs', 'package.json')), libJs = fs.readFileSync(path.join(this._templatesDir, 'nodejs', 'index.js')); - // Save Path - let pathModule = path.join('back', 'modules', _this.evt.module); - // Prep package.json - packageJsonTemplate.name = _this.evt.module; + packageJsonTemplate.name = _this.options.module; packageJsonTemplate.description = 'Dependencies for a Serverless Module written in Node.js'; // Write base module structure writeDeferred.push( fs.mkdirSync(path.join(_this.S.config.projectPath, pathModule)), fs.mkdirSync(path.join(_this.S.config.projectPath, pathModule, 'lib')), - fs.mkdirSync(path.join(_this.S.config.projectPath, pathModule, 'package')), + fs.mkdirSync(path.join(_this.S.config.projectPath, pathModule, '_module')), + fs.mkdirSync(path.join(_this.S.config.projectPath, pathModule, 'functions')), SUtils.writeFile(path.join(_this.S.config.projectPath, pathModule, 'lib', 'index.js'), libJs), SUtils.writeFile(path.join(_this.S.config.projectPath, pathModule, 'package.json'), JSON.stringify(packageJsonTemplate, null, 2)), - SUtils.writeFile(path.join(_this.S.config.projectPath, pathModule, 's-module.json'), JSON.stringify(moduleJsonTemplate, null, 2)) - ); + module.save() - // Set evt properties - let moduleName = _this.evt.module; - _this.evt.module = moduleJsonTemplate; - _this.evt.module.name = moduleName; - _this.evt.module.pathModule = pathModule; + ); return BbPromise.all(writeDeferred); }; - /* - * Generate s-module.json template (private) - */ - - _generateModuleJson() { - - let _this = this; - let moduleJsonTemplate = SUtils.readAndParseJsonSync(path.join(this._templatesDir, 's-module.json')); - moduleJsonTemplate.name = _this.evt.module; - - // Add runtime - switch (_this.evt.runtime) { - case 'nodejs': - moduleJsonTemplate.runtime = 'nodejs'; - break; - default: - return BbPromise.reject(new SError('Unsupported runtime ' + this.evt.runtime, SError.errorCodes.UNKNOWN)); - break; - } - - // Return - return moduleJsonTemplate; - }; _installModuleDependencies() { SCli.log('Installing "serverless-helpers" for this module via NPM...'); diff --git a/lib/actions/StageCreate.js b/lib/actions/StageCreate.js index 900afdc7f..bdc3c5520 100644 --- a/lib/actions/StageCreate.js +++ b/lib/actions/StageCreate.js @@ -208,7 +208,7 @@ usage: serverless stage create`, .then(function(result) { // Overwrite this meta w/ RegionCreate's meta - this.meta = result.meta; + _this.meta = result.meta; }); } }