mirror of
https://github.com/serverless/serverless.git
synced 2026-01-25 15:07:39 +00:00
Merge plugin create
This commit is contained in:
commit
ce474d5eb1
@ -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
170
lib/actions/PluginCreate.js
Normal 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 );
|
||||
};
|
||||
1
lib/templates/plugin/README.md
Normal file
1
lib/templates/plugin/README.md
Normal file
@ -0,0 +1 @@
|
||||
# Serverless plugin
|
||||
183
lib/templates/plugin/index.js
Normal file
183
lib/templates/plugin/index.js
Normal 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!
|
||||
38
lib/templates/plugin/package.json
Normal file
38
lib/templates/plugin/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user