Version
- Getting started
- Overview
- Installation
- Quickstart
- Upgrading
- Setting up your pipeline
- Determining needs
- Using Plugins
- Deploying your app
- Plugin Packs
- Authoring Plugins
- Creating a plugin
- Pipeline hooks
- The deployment context
- Creating a plugin pack
- Creating in-repo plugins
- Cookbook
- Default options
- Using .env for secrets
- Including a plugin twice
- Development workflow
- The lightning strategy
- S3 walkthrough
- Deploy non-Ember apps
- Reference
- Usage
- Configuration
- Other API/Classes
Creating a plugin
So, you want to write a plugin? Great! It’s people like you that will help the ember-cli-deploy plugin ecosystem flourish.
So, let’s get started.
The anatomy of a plugin
ember-cli-deploy plugins are nothing more than standard ember-cli addons with 3 small ember-cli-deploy specific traits:
- they contain the
ember-cli-deploy-plugin
keyword in theirpackage.json
to identify them as plugins - they are named
ember-cli-deploy-*
- they return an object that implements one or more of the ember-cli-deploy pipeline hooks
Let’s have a look at each of these things in a bit more detail.
Create an addon
An ember-cli-deploy plugin is just a standard ember-cli addon. Create it as follows:
ember addon ember-cli-deploy-funky-plugin
Identify the addon as a plugin
In order for ember-cli-deploy to know your addon is a plugin, we need to identify it as such by updating the package.json
like so:
// package.json
"keywords": [
"ember-addon",
"ember-cli-deploy-plugin"
]
Implement one or more pipeline hooks
In order for a plugin to be useful it must implement one or more of the ember-cli-deploy pipeline hooks.
To do this you must implement a function called createDeployPlugin
in your index.js
file. This function must return an object that contains:
- a
name
property which is what your plugin will be referred to by in theconfig/deploy.js
and; - one or more implemented pipeline hooks
Let’s look at an example:
module.exports = {
name: 'ember-cli-deploy-funky-plugin',
createDeployPlugin: function(options) {
return {
name: options.name,
didBuild: function(context) {
//do something amazing here once the project has been built
},
upload: function(context) {
//do something here to actually deploy your app somewhere
},
didDeploy: function(context) {
//do something here like notify your team on slack
}
};
}
};
That’s seriously about as difficult as it gets. However, read on for some more advanced info to get the most out of your ember-cli-deploy plugin.
Ordering Plugins
To specify that your plugin should run before or after a particular plugin or set of plugins, specify the runBefore
or runAfter
properties:
var BasePlugin = require('ember-cli-deploy-plugin');
module.exports = {
name: 'ember-cli-deploy-funky-plugin',
createDeployPlugin: function(options) {
var DeployPlugin = BasePlugin.extend({
name: options.name,
runBefore: ['foo', 'bar'],
runAfter: 'baz',
didDeploy: function(context) {
//do something here like notify your team on slack
}
});
return new DeployPlugin();
}
};
An example use case of this is where we want to upload the project assets with the s3
plugin before uploading the index.html with the redis
plugin. This way we can be certain that the assets exist before the bootstrap index.html, that references them, can be loaded by clients.
The Base Deploy Plugin
There are some common tasks that the majority of plugins need to do like validate configuration and log messages out to the terminal. So we have created a base plugin that you can extend to get this functionality for free.
Extending the base plugin
To extend the base plugin, first you need to install it:
npm install ember-cli-deploy-plugin --save
Then you need to extend it like this:
var BasePlugin = require('ember-cli-deploy-plugin');
module.exports = {
name: 'ember-cli-deploy-funky-plugin',
createDeployPlugin: function(options) {
var DeployPlugin = BasePlugin.extend({
name: options.name,
didBuild: function(context) {
//do something amazing here once the project has been built
},
upload: function(context) {
//do something here to actually deploy your app somewhere
},
didDeploy: function(context) {
//do something here like notify your team on slack
}
});
return new DeployPlugin();
}
};
Validating plugin config
ember-cli-deploy provides a pipeline hook called configure
for the purpose of validating and setting up state that will be needed by the plugin hooks executed later on in the pipeline.
This hook is the perfect place to validate that the plugin has all the required config it needs to perform it’s pipeline tasks.
As this validation is such a common thing, the base deploy plugin will implement the configure
hook by default and validate the configuration for you. In order for this to happen, you must
implement one or both of defaultConfig
and requiredConfig
.
defaultConfig
The defaultConfig
property allows you to specify default values for config properties that are not defined in config/deploy.js
, like this:
var BasePlugin = require('ember-cli-deploy-plugin');
module.exports = {
name: 'ember-cli-deploy-funky-plugin',
createDeployPlugin: function(options) {
var DeployPlugin = BasePlugin.extend({
name: options.name,
defaultConfig: {
// default filePattern if it isn't
// defined in config/deploy.js
filePattern: '**/*.{js,css,png}'
},
upload: function(context) { }
});
return new DeployPlugin();
}
};
You can also have the defaultConfig options be a function that takes in the deployment context as the first argument. This allows the config value to be decided at runtime based on properties that have been added to the deployment context by other plugins that have run before it.
var BasePlugin = require('ember-cli-deploy-plugin');
module.exports = {
name: 'ember-cli-deploy-funky-plugin',
createDeployPlugin: function(options) {
var DeployPlugin = BasePlugin.extend({
name: options.name,
defaultConfig: {
gzippedFiles: function(context) {
// if gzippedFiles has been added
// to the context by another plugin we can use it
return context.gzippedFiles || [];
}
},
upload: function(context) { }
});
return new DeployPlugin();
}
};
requiredConfig
The requiredConfig
property allows you to specify config properties that must be provided in order for the plugin to function correctly in the deployment pipeline.
If any required config value is not provided the pipeline will be aborted and an error message will be displayed informing you of which config property is missing.
You can specify the required configuration properties like this:
var BasePlugin = require('ember-cli-deploy-plugin');
module.exports = {
name: 'ember-cli-deploy-funky-plugin',
createDeployPlugin: function(options) {
var DeployPlugin = BasePlugin.extend({
name: options.name,
requiredConfig: ['accessKeyId', 'secretAccessKey'],
upload: function(context) { }
});
return new DeployPlugin();
}
};
Logging messages to the terminal
Due to the custom pipeline output that ember-cli-deploy displays, the base plugin provides a function to log messages into the pipeline output.
To log a message to the terminal use the log
function as follows:
var BasePlugin = require('ember-cli-deploy-plugin');
module.exports = {
name: 'ember-cli-deploy-funky-plugin',
createDeployPlugin: function(options) {
var DeployPlugin = BasePlugin.extend({
name: options.name,
upload: function(context) {
this.log('Uploading assets');
}
});
return new DeployPlugin();
}
};
Log messages will be displayed using the log-info-color
config option (default: ‘blue’)
If you need to log an error or warning message using a different color, simply pass the color in as an option to the log
function like this:
var BasePlugin = require('ember-cli-deploy-plugin');
module.exports = {
name: 'ember-cli-deploy-funky-plugin',
createDeployPlugin: function(options) {
var DeployPlugin = BasePlugin.extend({
name: options.name,
upload: function(context) {
this.log('Oops. Something went wrong', { color: 'red' });
}
});
return new DeployPlugin();
}
};
If you want your message to be only visible when the user passes the --verbose
option, simply pass verbose: true
to the log
function`:
var BasePlugin = require('ember-cli-deploy-plugin');
module.exports = {
name: 'ember-cli-deploy-funky-plugin',
createDeployPlugin: function(options) {
var DeployPlugin = BasePlugin.extend({
name: options.name,
upload: function(context) {
this.log('Uploading all the things', { verbose: true });
}
});
return new DeployPlugin();
}
};
Accessing config properties
When you want to access config properties from inside your pipeline hooks, the base plugin provides the readConfig
function to do so. It is this function that allows
you to have config values that are calculated at run time based on data in the deployment context.
A basic example of using this function is with a static config value, like so:
var BasePlugin = require('ember-cli-deploy-plugin');
module.exports = {
name: 'ember-cli-deploy-funky-plugin',
createDeployPlugin: function(options) {
var DeployPlugin = BasePlugin.extend({
name: options.name,
defaultConfig: {
manifestPath: '/manifest.txt'
},
upload: function(context) {
// will return the static value of '/manifest.txt'
this.readConfig('manifestPath')
}
});
return new DeployPlugin();
}
};
However, it gets much more interesting when the config value is a function:
var BasePlugin = require('ember-cli-deploy-plugin');
module.exports = {
name: 'ember-cli-deploy-funky-plugin',
createDeployPlugin: function(options) {
var DeployPlugin = BasePlugin.extend({
name: options.name,
defaultConfig: {
manifestPath: function(context) {
return context.manifestPath;
}
},
upload: function(context) {
// will return the value of manifestPath,
// that has been added to the context by another plugin, at runtime
this.readConfig('manifestPath');
}
});
return new DeployPlugin();
}
};
In this example, it is assumed that some other plugin hook that has been run previously to this upload
hook has added the manifestPath
property to the deployment context.
At the point that readConfig
is called, the config function is exectuted passing in the current deployment context, returning the current value of manifestPath
.
Adding data to the deployment context object
The deployment context is an object that is passed to each pipeline hook as it is executed. It allows plugins to access data from plugin hooks that have run before it and to pass data to plugin hooks that will run after it.
To add something to the deployment context, simply return an object from your pipeline hook. This object will be merged into the current deployment context which will then be passed to every pipeline hook thereafter.
So, imagine the deployment context looks like this:
{ distFiles: ['index.html', 'assets/app.js' ] }
When you return an object from your pipeline hook like this:
var BasePlugin = require('ember-cli-deploy-plugin');
module.exports = {
name: 'ember-cli-deploy-funky-plugin',
createDeployPlugin: function(options) {
var DeployPlugin = BasePlugin.extend({
name: options.name,
upload: function(context) {
return {
uploadedAt: '2015-10-14T22:29:46.313Z'
};
}
});
return new DeployPlugin();
}
};
Then once the pipeline hook has run, the deployment context will look like this:
{
distFiles: ['index.html', 'assets/app.js' ],
uploadedAt: '2015-10-14T22:29:46.313Z'
}
And every pipeline hook run thereafter will be able to access the uploadedAt
property.
Testing
Because plugins are effectively node code rather than ember code, they aren’t tested like regular ember addons. You’ll want to install mocha
npm install mocha --save-dev
and write your tests in tests/unit/index-nodetest.js
, using the following as a template. Note the config.pluginClient
mock, which you should replace with a check that the correct params are being sent to the external api.
var chai = require('chai');
var chaiAsPromised = require("chai-as-promised");
chai.use(chaiAsPromised);
var assert = chai.assert;
var stubProject = {
name: function(){
return 'my-project';
}
};
describe('my new plugin', function() {
var subject, mockUi;
beforeEach(function() {
subject = require('../../index');
mockUi = {
verbose: true,
messages: [],
write: function() { },
writeLine: function(message) {
this.messages.push(message);
}
};
});
it('has a name', function() {
var result = subject.createDeployPlugin({
name: 'test-plugin'
});
assert.equal(result.name, 'test-plugin');
});
describe('hook',function() {
var plugin;
var context;
it('calls the hook', function() {
plugin = subject.createDeployPlugin({name:'my plugin' });
context = {
ui: mockUi,
project: stubProject,
config: { "my-plugin": {
pluginClient: function(context) {
return {
upload: function(context) {
return Promise.resolve();
}
};
}
}
}
};
return assert.isFulfilled(plugin.upload(context))
});
});
});
You should then update your package.json
like this, and run your tests with npm test
.
// package.json
"scripts": {
"build": "ember build",
"start": "ember server",
"test": "./node_modules/mocha/bin/mocha tests/unit/index-nodetest.js"
},