Merge plugin create

This commit is contained in:
ac360 2016-02-11 08:40:16 -08:00
commit ce474d5eb1
6 changed files with 398 additions and 1 deletions

View File

@ -33,6 +33,7 @@
"./actions/StageRemove.js",
"./actions/ProjectRemove.js",
"./actions/FunctionLogs.js",
"./actions/ResourcesDiff.js"
"./actions/ResourcesDiff.js",
"./actions/PluginCreate"
]
}

170
lib/actions/PluginCreate.js Normal file
View File

@ -0,0 +1,170 @@
'use strict';
/**
* Action: Plugin create
* - validates that plugin does NOT already exists
* - validates that the plugins directory is present
* - generates plugin skeleton with the plugins name
*
* Event Options:
* - pluginName: (String) The name of your plugin
*/
module.exports = function(SPlugin, serverlessPath) {
const path = require('path'),
SError = require(path.join(serverlessPath, 'ServerlessError')),
SCli = require(path.join(serverlessPath, 'utils/cli')),
BbPromise = require('bluebird'),
SUtils = require(path.join(serverlessPath, 'utils')),
_ = require('lodash'),
execSync = require('child_process').execSync;
let fs = require('fs');
BbPromise.promisifyAll(fs);
/**
* PluginCreate Class
*/
class PluginCreate extends SPlugin {
constructor(S, config) {
super(S, config);
}
static getName() {
return 'serverless.core.' + PluginCreate.name;
}
registerActions() {
this.S.addAction(this.pluginCreate.bind(this), {
handler: 'pluginCreate',
description: `Creates scaffolding for a new plugin.
usage: serverless plugin create <plugin>`,
context: 'plugin',
contextAction: 'create',
options: [],
parameters: [
{
parameter: 'pluginName',
description: 'The name of your plugin',
position: '0'
}
]
});
return BbPromise.resolve();
}
/**
* Action
*/
pluginCreate(evt) {
let _this = this;
_this.evt = evt;
return _this._prompt()
.bind(_this)
.then(_this._createPluginSkeleton)
.then(function() {
SCli.log('Successfully created plugin scaffold with the name: "' + _this.evt.options.pluginName + '"');
/**
* Return Event
*/
return _this.evt;
});
}
/**
* Prompt plugin if they're missing
*/
_prompt() {
let _this = this,
overrides = {};
// If non-interactive, skip
if (!_this.S.config.interactive) return BbPromise.resolve();
let prompts = {
properties: {
name: {
description: 'Enter a new plugin name: '.yellow,
message: 'Plugin name must contain only letters, numbers, hyphens, or underscores.',
required: true,
conform: function(pluginName) {
return SUtils.isPluginNameValid(pluginName);
}
}
}
};
return _this.cliPromptInput(prompts, overrides)
.then(function(answers) {
_this.evt.options.pluginName = answers.name;
});
};
/**
* Create Plugin Skeleton
*/
_createPluginSkeleton() {
// Name of the plugin
let pluginName = this.evt.options.pluginName;
// Paths
let projectPath = this.S.config.projectPath;
let serverlessPath = this.S.config.serverlessPath;
// Directories
let pluginsDirectory = path.join(projectPath, 'plugins');
let pluginDirectory = path.join(pluginsDirectory, pluginName);
let pluginTemplateDirectory = path.join(serverlessPath, 'templates', 'plugin');
// Plugin files from the serverless template directory
let indexJs = fs.readFileSync(path.join(pluginTemplateDirectory, 'index.js'));
let packageJson = fs.readFileSync(path.join(pluginTemplateDirectory, 'package.json'));
let readmeMd = fs.readFileSync(path.join(pluginTemplateDirectory, 'README.md'));
// Create the plugins directory if it's not yet present
if (!SUtils.dirExistsSync(pluginsDirectory)) {
fs.mkdirSync(pluginsDirectory);
}
// Create the directory for the new plugin in the plugins directory
if (!SUtils.dirExistsSync(pluginDirectory)) {
fs.mkdirSync(pluginDirectory);
} else {
throw new SError('Plugin with the name ' + pluginName + ' already exists.');
}
// Prepare and copy all files
let modifiedPackageJson = _.template(packageJson)({ pluginName: pluginName });
fs.writeFileSync(path.join(pluginDirectory, 'package.json'), modifiedPackageJson);
fs.writeFileSync(path.join(pluginDirectory, 'index.js'), indexJs);
fs.writeFileSync(path.join(pluginDirectory, 'README.md'), readmeMd);
// link the new package
execSync('cd ' + pluginDirectory + ' && npm link');
execSync('cd ' + projectPath + ' && npm link ' + pluginName);
// TODO: Remove in V1 because will result in breaking change
// Add the newly create plugin to the plugins array of the projects s-project.json file
let sProjectJson = SUtils.readAndParseJsonSync(path.join(projectPath, 's-project.json'));
sProjectJson.plugins.push(pluginName);
fs.writeFileSync(path.join(projectPath, 's-project.json'), JSON.stringify(sProjectJson, null, 2));
// Add the newly created plugin to the package.json file of the project
let projectPackageJson = SUtils.readAndParseJsonSync(path.join(projectPath, 'package.json'));
projectPackageJson.dependencies[pluginName] = JSON.parse(packageJson).version;
fs.writeFileSync(path.join(projectPath, 'package.json'), JSON.stringify(projectPackageJson, null, 2));
};
}
return( PluginCreate );
};

View File

@ -0,0 +1 @@
# Serverless plugin

View File

@ -0,0 +1,183 @@
'use strict';
/**
* Serverless Plugin Boilerplate
* - Useful example/starter code for writing a plugin for the Serverless Framework.
* - In a plugin, you can:
* - Create a Custom Action that can be called via the CLI or programmatically via a function handler.
* - Overwrite a Core Action that is included by default in the Serverless Framework.
* - Add a hook that fires before or after a Core Action or a Custom Action
* - All of the above at the same time :)
*
* - Setup:
* - Make a Serverless Project dedicated for plugin development, or use an existing Serverless Project
* - Make a "plugins" folder in the root of your Project and copy this codebase into it. Title it your custom plugin name with the suffix "-dev", like "myplugin-dev"
* - Run "npm link" in your plugin, then run "npm link myplugin" in the root of your project.
* - Start developing!
*
* - Good luck, serverless.com :)
*/
module.exports = function(ServerlessPlugin) { // Always pass in the ServerlessPlugin Class
const path = require('path'),
fs = require('fs'),
BbPromise = require('bluebird'); // Serverless uses Bluebird Promises and we recommend you do to because they provide more than your average Promise :)
/**
* ServerlessPluginBoilerplate
*/
class ServerlessPluginBoilerplate extends ServerlessPlugin {
/**
* Constructor
* - Keep this and don't touch it unless you know what you're doing.
*/
constructor(S) {
super(S);
}
/**
* Define your plugins name
* - We recommend adding prefixing your personal domain to the name so people know the plugin author
*/
static getName() {
return 'com.serverless.' + ServerlessPluginBoilerplate.name;
}
/**
* Register Actions
* - If you would like to register a Custom Action or overwrite a Core Serverless Action, add this function.
* - If you would like your Action to be used programatically, include a "handler" which can be called in code.
* - If you would like your Action to be used via the CLI, include a "description", "context", "action" and any options you would like to offer.
* - Your custom Action can be called programatically and via CLI, as in the example provided below
*/
registerActions() {
this.S.addAction(this._customAction.bind(this), {
handler: 'customAction',
description: 'A custom action from a custom plugin',
context: 'custom',
contextAction: 'run',
options: [{ // These must be specified in the CLI like this "-option true" or "-o true"
option: 'option',
shortcut: 'o',
description: 'test option 1'
}],
parameters: [ // Use paths when you multiple values need to be input (like an array). Input looks like this: "serverless custom run module1/function1 module1/function2 module1/function3. Serverless will automatically turn this into an array and attach it to evt.options within your plugin
{
parameter: 'paths',
description: 'One or multiple paths to your function',
position: '0->' // Can be: 0, 0-2, 0-> This tells Serverless which params are which. 3-> Means that number and infinite values after it.
}
]
});
return BbPromise.resolve();
}
/**
* Register Hooks
* - If you would like to register hooks (i.e., functions) that fire before or after a core Serverless Action or your Custom Action, include this function.
* - Make sure to identify the Action you want to add a hook for and put either "pre" or "post" to describe when it should happen.
*/
registerHooks() {
this.S.addHook(this._hookPre.bind(this), {
action: 'functionRunLambdaNodeJs',
event: 'pre'
});
this.S.addHook(this._hookPost.bind(this), {
action: 'functionRunLambdaNodeJs',
event: 'post'
});
return BbPromise.resolve();
}
/**
* Custom Action Example
* - Here is an example of a Custom Action. Include this and modify it if you would like to write your own Custom Action for the Serverless Framework.
* - Be sure to ALWAYS accept and return the "evt" object, or you will break the entire flow.
* - The "evt" object contains Action-specific data. You can add custom data to it, but if you change any data it will affect subsequent Actions and Hooks.
* - You can also access other Project-specific data @ this.S Again, if you mess with data on this object, it could break everything, so make sure you know what you're doing ;)
*/
_customAction(evt) {
let _this = this;
return new BbPromise(function (resolve, reject) {
// console.log(evt) // Contains Action Specific data
// console.log(_this.S) // Contains Project Specific data
// console.log(_this.S.state) // Contains tons of useful methods for you to use in your plugin. It's the official API for plugin developers.
console.log('-------------------');
console.log('YOU JUST RAN YOUR CUSTOM ACTION, NICE!');
console.log('-------------------');
return resolve(evt);
});
}
/**
* Your Custom PRE Hook
* - Here is an example of a Custom PRE Hook. Include this and modify it if you would like to write your a hook that fires BEFORE an Action.
* - Be sure to ALWAYS accept and return the "evt" object, or you will break the entire flow.
* - The "evt" object contains Action-specific data. You can add custom data to it, but if you change any data it will affect subsequent Actions and Hooks.
* - You can also access other Project-specific data @ this.S Again, if you mess with data on this object, it could break everything, so make sure you know what you're doing ;)
*/
_hookPre(evt) {
let _this = this;
return new BbPromise(function (resolve, reject) {
console.log('-------------------');
console.log('YOUR SERVERLESS PLUGIN\'S CUSTOM "PRE" HOOK HAS RUN BEFORE "FunctionRunLambdaNodeJs"');
console.log('-------------------');
return resolve(evt);
});
}
/**
* Your Custom POST Hook
* - Here is an example of a Custom POST Hook. Include this and modify it if you would like to write your a hook that fires AFTER an Action.
* - Be sure to ALWAYS accept and return the "evt" object, or you will break the entire flow.
* - The "evt" object contains Action-specific data. You can add custom data to it, but if you change any data it will affect subsequent Actions and Hooks.
* - You can also access other Project-specific data @ this.S Again, if you mess with data on this object, it could break everything, so make sure you know what you're doing ;)
*/
_hookPost(evt) {
let _this = this;
return new BbPromise(function (resolve, reject) {
console.log('-------------------');
console.log('YOUR SERVERLESS PLUGIN\'S CUSTOM "POST" HOOK HAS RUN AFTER "FunctionRunLambdaNodeJs"');
console.log('-------------------');
return resolve(evt);
});
}
}
// Export Plugin Class
return ServerlessPluginBoilerplate;
};
// Godspeed!

View File

@ -0,0 +1,38 @@
{
"name": "<%= pluginName %>",
"version": "0.1.0",
"engines": {
"node": ">=4.0"
},
"description": "Serverless plugin",
"author": "serverless.com",
"license": "MIT",
"repository": {
"type": "git",
"url": "http://github.com/"
},
"keywords": [
"serverless framework plugin",
"serverless applications",
"serverless plugins",
"api gateway",
"lambda",
"aws",
"aws lambda",
"amazon",
"amazon web services",
"serverless.com"
],
"main": "index.js",
"bin": {},
"scripts": {
"test": "mocha tests/all"
},
"devDependencies": {
"chai": "^3.2.0",
"mocha": "^2.2.5"
},
"dependencies": {
"bluebird": "^3.0.6"
}
}

View File

@ -498,6 +498,10 @@ exports.isFunctionNameValid = function(functionName) {
return /^[\w-]{1,20}$/.test(functionName);
};
exports.isPluginNameValid = function(pluginName) {
return /^[\w-]+$/.test(pluginName);
};
exports.getModulePath = function(moduleName, componentName, projectRootPath) {
return path.join(projectRootPath, componentName, moduleName);
};