mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
initial commit for adding plugin command
This commit is contained in:
parent
793ef87b15
commit
e3d4e23c8a
@ -14,6 +14,7 @@
|
||||
"./remove/remove.js",
|
||||
"./rollback/index.js",
|
||||
"./slstats/slstats.js",
|
||||
"./plugin/plugin.js",
|
||||
"./aws/configCredentials/awsConfigCredentials.js",
|
||||
"./aws/provider/awsProvider.js",
|
||||
"./aws/common/index.js",
|
||||
|
||||
303
lib/plugins/plugin/plugin.js
Normal file
303
lib/plugins/plugin/plugin.js
Normal file
@ -0,0 +1,303 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const BbPromise = require('bluebird');
|
||||
const childProcess = require('child_process');
|
||||
const fetch = require('node-fetch');
|
||||
const chalk = require('chalk');
|
||||
const fs = require('fs');
|
||||
const fse = require('fs-extra');
|
||||
const path = require('path');
|
||||
const _ = require('lodash');
|
||||
const YAML = require('js-yaml');
|
||||
|
||||
class Plugin {
|
||||
constructor(serverless, options) {
|
||||
this.serverless = serverless;
|
||||
this.options = options;
|
||||
|
||||
this.commands = {
|
||||
plugin: {
|
||||
usage: 'Plugin management for Serverless',
|
||||
commands: {
|
||||
install: {
|
||||
usage: 'Install and add a plugin to your service',
|
||||
lifecycleEvents: [
|
||||
'install',
|
||||
],
|
||||
options: {
|
||||
name: {
|
||||
usage: 'The plugin name',
|
||||
required: true,
|
||||
shortcut: 'n',
|
||||
},
|
||||
},
|
||||
},
|
||||
uninstall: {
|
||||
usage: 'Uninstall and remove a plugin from your service',
|
||||
lifecycleEvents: [
|
||||
'uninstall',
|
||||
],
|
||||
options: {
|
||||
name: {
|
||||
usage: 'The plugin name',
|
||||
required: true,
|
||||
shortcut: 'n',
|
||||
},
|
||||
},
|
||||
},
|
||||
list: {
|
||||
usage: 'Lists all available plugins',
|
||||
lifecycleEvents: [
|
||||
'list',
|
||||
],
|
||||
},
|
||||
search: {
|
||||
usage: 'Search for plugins',
|
||||
lifecycleEvents: [
|
||||
'search',
|
||||
],
|
||||
options: {
|
||||
query: {
|
||||
usage: 'Search query',
|
||||
required: true,
|
||||
shortcut: 'q',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
this.hooks = {
|
||||
'plugin:install:install': () => BbPromise.bind(this).then(this.install),
|
||||
'plugin:uninstall:uninstall': () => BbPromise.bind(this).then(this.uninstall),
|
||||
'plugin:list:list': () => BbPromise.bind(this).then(this.list),
|
||||
'plugin:search:search': () => BbPromise.bind(this).then(this.search),
|
||||
};
|
||||
}
|
||||
|
||||
install() {
|
||||
return BbPromise.bind(this)
|
||||
.then(this.validate)
|
||||
.then(this.getPlugins)
|
||||
.then((plugins) => {
|
||||
const pluginName = this.options.name;
|
||||
const plugin = plugins.find((item) => item.name === pluginName);
|
||||
|
||||
if (plugin) {
|
||||
const servicePath = this.serverless.config.servicePath;
|
||||
const packageJsonFilePath = path.join(servicePath, 'package.json');
|
||||
|
||||
// check if package.json is already present. Otherwise create one
|
||||
if (!fs.existsSync(packageJsonFilePath)) {
|
||||
this.serverless.cli
|
||||
.log('Creating an empty package.json file in your service directory');
|
||||
|
||||
const packageJsonFileContent = {
|
||||
name: this.serverless.service.service,
|
||||
description: '',
|
||||
version: '0.1.0',
|
||||
dependencies: {},
|
||||
devDependencies: {},
|
||||
};
|
||||
|
||||
fse.writeJsonSync(packageJsonFilePath, packageJsonFileContent);
|
||||
}
|
||||
|
||||
// install the package through npm
|
||||
this.serverless.cli
|
||||
.log(`Installing plugin "${pluginName}" (this might take a few seconds...)`);
|
||||
|
||||
childProcess
|
||||
.execSync(`npm install --save-dev ${pluginName}`, {
|
||||
stdio: 'ignore',
|
||||
});
|
||||
|
||||
// check if plugin was installed correctly
|
||||
const pluginInstalled = !!JSON.parse(
|
||||
fs.readFileSync(packageJsonFilePath).toString()
|
||||
).devDependencies[pluginName];
|
||||
|
||||
if (pluginInstalled) {
|
||||
const serverlessFilePath = this.getServerlessFilePath();
|
||||
let serverlessFileContent = fs.readFileSync(serverlessFilePath).toString();
|
||||
|
||||
const newPluginsArray = {
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
// load the service and parse it to JSON
|
||||
const parsedYaml = YAML.load(serverlessFileContent);
|
||||
|
||||
if (parsedYaml.plugins) {
|
||||
newPluginsArray.plugins = parsedYaml.plugins;
|
||||
}
|
||||
|
||||
newPluginsArray.plugins = _.union(newPluginsArray.plugins, [pluginName]);
|
||||
|
||||
if (parsedYaml.plugins) {
|
||||
// replace the plugins definition in the serverless file
|
||||
serverlessFileContent = serverlessFileContent
|
||||
.replace(
|
||||
/(plugins:)+(\s)*(- .*\s*)*(\[\])*/, // eslint-disable-line no-useless-escape
|
||||
YAML.dump(newPluginsArray)
|
||||
);
|
||||
} else {
|
||||
serverlessFileContent = serverlessFileContent
|
||||
.concat(`\n${YAML.dump(newPluginsArray)}`);
|
||||
}
|
||||
|
||||
// write the file back to the disc
|
||||
fs.writeFileSync(serverlessFilePath, serverlessFileContent);
|
||||
this.serverless.cli.log(`Successfully installed "${pluginName}"`);
|
||||
} else {
|
||||
const message = 'An error occurred while installing your plugin. Please try again...';
|
||||
this.serverless.cli.log(message);
|
||||
}
|
||||
} else {
|
||||
const message = `Plugin "${pluginName}" not found. Did you spell it correct?`;
|
||||
this.serverless.cli.log(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
return BbPromise.bind(this)
|
||||
.then(this.validate)
|
||||
.then(() => {
|
||||
const pluginName = this.options.name;
|
||||
|
||||
const servicePath = this.serverless.config.servicePath;
|
||||
const packageJsonFilePath = path.join(servicePath, 'package.json');
|
||||
|
||||
// uninstall the package through npm
|
||||
this.serverless.cli
|
||||
.log(`Uninstalling plugin "${pluginName}" (this might take a few seconds...)`);
|
||||
|
||||
childProcess
|
||||
.execSync(`npm uninstall --save-dev ${pluginName}`, {
|
||||
stdio: 'ignore',
|
||||
});
|
||||
|
||||
// check if plugin was uninstalled correctly
|
||||
const pluginStillAvailable = !!JSON.parse(
|
||||
fs.readFileSync(packageJsonFilePath).toString()
|
||||
).devDependencies[pluginName];
|
||||
|
||||
if (!pluginStillAvailable) {
|
||||
const serverlessFilePath = this.getServerlessFilePath();
|
||||
let serverlessFileContent = fs.readFileSync(serverlessFilePath).toString();
|
||||
|
||||
// load the service and parse it to JSON
|
||||
const parsedYaml = YAML.load(serverlessFileContent);
|
||||
// remove the plugin from the array if the plugins array is available
|
||||
if (parsedYaml.plugins) {
|
||||
const newPluginsArray = {
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
newPluginsArray.plugins = _.pull(parsedYaml.plugins, pluginName);
|
||||
if (!_.isEmpty(newPluginsArray.plugins)) {
|
||||
// replace the plugins definition in the serverless file
|
||||
serverlessFileContent = serverlessFileContent
|
||||
.replace(/(plugins:)+(\s)+(- .*\s*)+/, YAML.dump(newPluginsArray));
|
||||
} else {
|
||||
// remove the plugins definition from the serverless file
|
||||
serverlessFileContent = serverlessFileContent
|
||||
.replace(/(plugins:)+(\s)+(- .*\s*)+/, '');
|
||||
}
|
||||
|
||||
// write the file back to the disc
|
||||
fs.writeFileSync(serverlessFilePath, serverlessFileContent);
|
||||
this.serverless.cli.log(`Successfully uninstalled "${pluginName}"`);
|
||||
}
|
||||
} else {
|
||||
const message = 'An error occurred while uninstalling your plugin. Please try again...';
|
||||
this.serverless.cli.log(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
list() {
|
||||
return BbPromise.bind(this)
|
||||
.then(this.getPlugins)
|
||||
.then((plugins) => this.display(plugins));
|
||||
}
|
||||
|
||||
search() {
|
||||
return BbPromise.bind(this)
|
||||
.then(this.getPlugins)
|
||||
.then((plugins) => {
|
||||
// filter out plugins which match the query
|
||||
const regex = new RegExp(this.options.query);
|
||||
|
||||
const filteredPlugins = plugins.filter((plugin) =>
|
||||
(plugin.name.match(regex) || plugin.description.match(regex))
|
||||
);
|
||||
|
||||
// print a message with the search result
|
||||
const pluginCount = filteredPlugins.length;
|
||||
const query = this.options.query;
|
||||
const message = `${pluginCount} plugin(s) found for your search query "${query}"\n`;
|
||||
this.serverless.cli.consoleLog(chalk.yellow(message));
|
||||
|
||||
return filteredPlugins;
|
||||
})
|
||||
.then((plugins) => {
|
||||
this.display(plugins);
|
||||
});
|
||||
}
|
||||
|
||||
// helper methods
|
||||
validate() {
|
||||
if (!this.serverless.config.servicePath) {
|
||||
throw new this.serverless.classes
|
||||
.Error('This command can only be run inside a service directory');
|
||||
}
|
||||
|
||||
return BbPromise.resolve();
|
||||
}
|
||||
|
||||
getServerlessFilePath() {
|
||||
const servicePath = this.serverless.config.servicePath;
|
||||
const serverlessYmlFilePath = path.join(servicePath, 'serverless.yml');
|
||||
const serverlessYamlFilePath = path.join(servicePath, 'serverless.yaml');
|
||||
|
||||
let serverlessFilePath;
|
||||
if (fs.existsSync(serverlessYmlFilePath)) {
|
||||
serverlessFilePath = serverlessYmlFilePath;
|
||||
} else {
|
||||
serverlessFilePath = serverlessYamlFilePath;
|
||||
}
|
||||
|
||||
return serverlessFilePath;
|
||||
}
|
||||
|
||||
getPlugins() {
|
||||
const endpoint = 'https://raw.githubusercontent.com/serverless/plugins/master/plugins.json';
|
||||
|
||||
return fetch(endpoint).then((result) => result.json()).then((json) => json);
|
||||
}
|
||||
|
||||
display(plugins) {
|
||||
let message = '';
|
||||
if (plugins && plugins.length) {
|
||||
// order plugins by name
|
||||
const orderedPlugins = _.orderBy(plugins, ['name'], ['asc']);
|
||||
orderedPlugins.forEach((plugin) => {
|
||||
message += `${chalk.yellow.underline(plugin.name)}\n`;
|
||||
message += `${plugin.description}\n\n`;
|
||||
});
|
||||
// remove last two newlines for a prettier output
|
||||
message = message.slice(0, -2);
|
||||
this.serverless.cli.consoleLog(message);
|
||||
} else {
|
||||
message = 'There are no plugins available to display';
|
||||
this.serverless.cli.consoleLog(message);
|
||||
}
|
||||
return BbPromise.resolve(message);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Plugin;
|
||||
688
lib/plugins/plugin/plugin.test.js
Normal file
688
lib/plugins/plugin/plugin.test.js
Normal file
@ -0,0 +1,688 @@
|
||||
'use strict';
|
||||
|
||||
const expect = require('chai').expect;
|
||||
const sinon = require('sinon');
|
||||
const BbPromise = require('bluebird');
|
||||
const YAML = require('js-yaml');
|
||||
const path = require('path');
|
||||
const childProcess = require('child_process');
|
||||
const fs = require('fs');
|
||||
const fse = require('fs-extra');
|
||||
const proxyquire = require('proxyquire');
|
||||
const chalk = require('chalk');
|
||||
const Plugin = require('./plugin');
|
||||
const Serverless = require('../../Serverless');
|
||||
const CLI = require('../../classes/CLI');
|
||||
const testUtils = require('../../../tests/utils');
|
||||
|
||||
describe('Plugin', () => {
|
||||
let plugin;
|
||||
let serverless;
|
||||
let consoleLogStub;
|
||||
const plugins = [
|
||||
{
|
||||
name: 'serverless-plugin-1',
|
||||
description: 'Serverless Plugin 1',
|
||||
githubUrl: 'https://github.com/serverless/serverless-plugin-1',
|
||||
},
|
||||
{
|
||||
name: 'serverless-plugin-2',
|
||||
description: 'Serverless Plugin 2',
|
||||
githubUrl: 'https://github.com/serverless/serverless-plugin-2',
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
serverless = new Serverless();
|
||||
serverless.cli = new CLI(serverless);
|
||||
const options = {};
|
||||
plugin = new Plugin(serverless, options);
|
||||
consoleLogStub = sinon.stub(serverless.cli, 'consoleLog').returns();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
serverless.cli.consoleLog.restore();
|
||||
});
|
||||
|
||||
describe('#constructor()', () => {
|
||||
it('should have the command "plugin"', () => {
|
||||
expect(plugin.commands.plugin).to.not.equal(undefined);
|
||||
});
|
||||
|
||||
it('should have the sub-command "install"', () => {
|
||||
expect(plugin.commands.plugin.commands.install).to.not.equal(undefined);
|
||||
});
|
||||
|
||||
it('should have the sub-command "uninstall"', () => {
|
||||
expect(plugin.commands.plugin.commands.uninstall).to.not.equal(undefined);
|
||||
});
|
||||
|
||||
it('should have the sub-command "list"', () => {
|
||||
expect(plugin.commands.plugin.commands.list).to.not.equal(undefined);
|
||||
});
|
||||
|
||||
it('should have the sub-command "search"', () => {
|
||||
expect(plugin.commands.plugin.commands.search).to.not.equal(undefined);
|
||||
});
|
||||
|
||||
it('should have the lifecycle event "install" for the "install" sub-command', () => {
|
||||
expect(plugin.commands.plugin.commands.install.lifecycleEvents).to.deep.equal([
|
||||
'install',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should have the lifecycle event "uninstall" for the "uninstall" sub-command', () => {
|
||||
expect(plugin.commands.plugin.commands.uninstall.lifecycleEvents).to.deep.equal([
|
||||
'uninstall',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should have the lifecycle event "list" for the "list" sub-command', () => {
|
||||
expect(plugin.commands.plugin.commands.list.lifecycleEvents).to.deep.equal([
|
||||
'list',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should have the lifecycle event "search" for the "search" sub-command', () => {
|
||||
expect(plugin.commands.plugin.commands.search.lifecycleEvents).to.deep.equal([
|
||||
'search',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should have a required option "name" for the "install" sub-command', () => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
expect(plugin.commands.plugin.commands.install.options.name.required).to.be.true;
|
||||
});
|
||||
|
||||
it('should have a required option "name" for the "uninstall" sub-command', () => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
expect(plugin.commands.plugin.commands.uninstall.options.name.required).to.be.true;
|
||||
});
|
||||
|
||||
it('should have no option for the "list" sub-command', () => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
expect(plugin.commands.plugin.commands.list.options).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('should have a required option "query" for the "search" sub-command', () => {
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
expect(plugin.commands.plugin.commands.search.options.query.required).to.be.true;
|
||||
});
|
||||
|
||||
it('should have a "plugin:install:install" hook', () => {
|
||||
expect(plugin.hooks['plugin:install:install']).to.not.equal(undefined);
|
||||
});
|
||||
|
||||
it('should have a "plugin:uninstall:uninstall" hook', () => {
|
||||
expect(plugin.hooks['plugin:uninstall:uninstall']).to.not.equal(undefined);
|
||||
});
|
||||
|
||||
it('should have a "plugin:list:list" hook', () => {
|
||||
expect(plugin.hooks['plugin:list:list']).to.not.equal(undefined);
|
||||
});
|
||||
|
||||
it('should have a "plugin:search:search" hook', () => {
|
||||
expect(plugin.hooks['plugin:search:search']).to.not.equal(undefined);
|
||||
});
|
||||
|
||||
it('should run promise chain in order for "plugin:install:install" hook', () => {
|
||||
const installStub = sinon
|
||||
.stub(plugin, 'install').returns(BbPromise.resolve());
|
||||
|
||||
return plugin.hooks['plugin:install:install']().then(() => {
|
||||
expect(installStub.calledOnce).to.equal(true);
|
||||
|
||||
plugin.install.restore();
|
||||
});
|
||||
});
|
||||
|
||||
it('should run promise chain in order for "plugin:uninstall:uninstall" hook', () => {
|
||||
const uninstallStub = sinon
|
||||
.stub(plugin, 'uninstall').returns(BbPromise.resolve());
|
||||
|
||||
return plugin.hooks['plugin:uninstall:uninstall']().then(() => {
|
||||
expect(uninstallStub.calledOnce).to.equal(true);
|
||||
|
||||
plugin.uninstall.restore();
|
||||
});
|
||||
});
|
||||
|
||||
it('should run promise chain in order for "plugin:list:list" hook', () => {
|
||||
const listStub = sinon
|
||||
.stub(plugin, 'list').returns(BbPromise.resolve());
|
||||
|
||||
return plugin.hooks['plugin:list:list']().then(() => {
|
||||
expect(listStub.calledOnce).to.equal(true);
|
||||
|
||||
plugin.list.restore();
|
||||
});
|
||||
});
|
||||
|
||||
it('should run promise chain in order for "plugin:search:search" hook', () => {
|
||||
const searchStub = sinon
|
||||
.stub(plugin, 'search').returns(BbPromise.resolve());
|
||||
|
||||
return plugin.hooks['plugin:search:search']().then(() => {
|
||||
expect(searchStub.calledOnce).to.equal(true);
|
||||
|
||||
plugin.search.restore();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#install()', () => {
|
||||
let servicePath;
|
||||
let serverlessYmlFilePath;
|
||||
let packageJsonFilePath;
|
||||
let validateStub;
|
||||
let getPluginsStub;
|
||||
let npmInstallStub;
|
||||
let savedCwd;
|
||||
|
||||
beforeEach(() => {
|
||||
servicePath = testUtils.getTmpDirPath();
|
||||
fse.ensureDirSync(servicePath);
|
||||
serverlessYmlFilePath = path.join(servicePath, 'serverless.yml');
|
||||
packageJsonFilePath = path.join(servicePath, 'package.json');
|
||||
plugin.serverless.config.servicePath = servicePath;
|
||||
validateStub = sinon
|
||||
.stub(plugin, 'validate')
|
||||
.returns(BbPromise.resolve());
|
||||
getPluginsStub = sinon
|
||||
.stub(plugin, 'getPlugins')
|
||||
.returns(BbPromise.resolve(plugins));
|
||||
npmInstallStub = sinon
|
||||
.stub(childProcess, 'execSync')
|
||||
.withArgs('npm install --save-dev serverless-plugin-1')
|
||||
.returns();
|
||||
// save the cwd so that we can restore it later
|
||||
savedCwd = process.cwd();
|
||||
process.chdir(servicePath);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
plugin.validate.restore();
|
||||
plugin.getPlugins.restore();
|
||||
childProcess.execSync.restore();
|
||||
process.chdir(savedCwd);
|
||||
});
|
||||
|
||||
it('should not install the plugin if it can not be found in the registry', () => {
|
||||
// serverless.yml
|
||||
const serverlessYml = {
|
||||
service: 'plugin-service',
|
||||
provider: 'aws',
|
||||
};
|
||||
serverless.utils
|
||||
.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml));
|
||||
|
||||
plugin.options.name = 'some-not-available-plugin'; // this plugin is not in the plugins mock
|
||||
|
||||
return plugin.install().then(() => {
|
||||
expect(validateStub.calledOnce).to.equal(true);
|
||||
expect(getPluginsStub.calledOnce).to.equal(true);
|
||||
expect(consoleLogStub.called).to.equal(true);
|
||||
expect(npmInstallStub.called).to.equal(false);
|
||||
|
||||
// inspect the serverless.yml file
|
||||
const serverlessFileContent = YAML.load(fs.readFileSync(serverlessYmlFilePath).toString());
|
||||
expect(serverlessFileContent.plugins).to.equal(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it('should log a message if a problem during installation happens', () => {
|
||||
// serverless.yml
|
||||
const serverlessYml = {
|
||||
service: 'plugin-service',
|
||||
provider: 'aws',
|
||||
};
|
||||
serverless.utils
|
||||
.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml));
|
||||
// package.json file
|
||||
const packageJsonFileContent = {
|
||||
devDependencies: {}, // plugin was not added successfully
|
||||
};
|
||||
fse.writeJsonSync(packageJsonFilePath, packageJsonFileContent);
|
||||
|
||||
plugin.options.name = 'serverless-plugin-1';
|
||||
|
||||
return plugin.install().then(() => {
|
||||
expect(validateStub.calledOnce).to.equal(true);
|
||||
expect(getPluginsStub.calledOnce).to.equal(true);
|
||||
expect(consoleLogStub.called).to.equal(true);
|
||||
expect(npmInstallStub.calledWithExactly(
|
||||
`npm install --save-dev ${plugin.options.name}`,
|
||||
{ stdio: 'ignore' }
|
||||
)).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate a package.json file in the service directory if not present', () => {
|
||||
// serverless.yml
|
||||
const serverlessYml = {
|
||||
service: 'plugin-service',
|
||||
provider: 'aws',
|
||||
};
|
||||
serverless.utils
|
||||
.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml));
|
||||
|
||||
plugin.options.name = 'serverless-plugin-1';
|
||||
|
||||
return plugin.install().then(() => {
|
||||
expect(validateStub.calledOnce).to.equal(true);
|
||||
expect(getPluginsStub.calledOnce).to.equal(true);
|
||||
expect(npmInstallStub.calledWithExactly(
|
||||
`npm install --save-dev ${plugin.options.name}`,
|
||||
{ stdio: 'ignore' }
|
||||
)).to.equal(true);
|
||||
expect(consoleLogStub.called).to.equal(true);
|
||||
expect(fs.existsSync(packageJsonFilePath)).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should add the plugin to the service file if plugins array is not present', () => {
|
||||
// serverless.yml
|
||||
const serverlessYml = {
|
||||
service: 'plugin-service',
|
||||
provider: 'aws',
|
||||
// no plugins array here
|
||||
};
|
||||
serverless.utils
|
||||
.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml));
|
||||
// package.json file
|
||||
const packageJsonFileContent = {
|
||||
devDependencies: {
|
||||
'serverless-plugin-1': '0.1.0',
|
||||
},
|
||||
};
|
||||
fse.writeJsonSync(packageJsonFilePath, packageJsonFileContent);
|
||||
|
||||
plugin.options.name = 'serverless-plugin-1';
|
||||
|
||||
return plugin.install().then(() => {
|
||||
expect(validateStub.calledOnce).to.equal(true);
|
||||
expect(getPluginsStub.calledOnce).to.equal(true);
|
||||
expect(npmInstallStub.calledWithExactly(
|
||||
`npm install --save-dev ${plugin.options.name}`,
|
||||
{ stdio: 'ignore' }
|
||||
)).to.equal(true);
|
||||
expect(consoleLogStub.called).to.equal(true);
|
||||
|
||||
// inspect the serverless.yml file
|
||||
const serverlessFileContent = YAML.load(fs.readFileSync(serverlessYmlFilePath).toString());
|
||||
expect(serverlessFileContent.plugins[0]).to.equal('serverless-plugin-1');
|
||||
});
|
||||
});
|
||||
|
||||
it('should push the plugin to the service files plugin array if present', () => {
|
||||
// serverless.yml
|
||||
const serverlessYml = {
|
||||
service: 'plugin-service',
|
||||
provider: 'aws',
|
||||
plugins: ['serverless-existing-plugin'], // one plugin was already added
|
||||
};
|
||||
serverless.utils
|
||||
.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml));
|
||||
// package.json file
|
||||
const packageJsonFileContent = {
|
||||
devDependencies: {
|
||||
'serverless-plugin-1': '0.1.0',
|
||||
},
|
||||
};
|
||||
fse.writeJsonSync(packageJsonFilePath, packageJsonFileContent);
|
||||
|
||||
plugin.options.name = 'serverless-plugin-1';
|
||||
|
||||
return plugin.install().then(() => {
|
||||
expect(validateStub.calledOnce).to.equal(true);
|
||||
expect(getPluginsStub.calledOnce).to.equal(true);
|
||||
expect(npmInstallStub.calledWithExactly(
|
||||
`npm install --save-dev ${plugin.options.name}`,
|
||||
{ stdio: 'ignore' }
|
||||
)).to.equal(true);
|
||||
expect(consoleLogStub.called).to.equal(true);
|
||||
|
||||
// inspect the serverless.yml file
|
||||
const serverlessFileContent = YAML.load(fs.readFileSync(serverlessYmlFilePath).toString());
|
||||
expect(serverlessFileContent.plugins.length).to.equal(2);
|
||||
expect(serverlessFileContent.plugins).to.include('serverless-existing-plugin');
|
||||
expect(serverlessFileContent.plugins).to.include('serverless-plugin-1');
|
||||
});
|
||||
});
|
||||
|
||||
it('should push the plugin to the service files plugin array if it is empty', () => {
|
||||
// serverless.yml
|
||||
const serverlessYml = {
|
||||
service: 'plugin-service',
|
||||
provider: 'aws',
|
||||
plugins: [], // empty plugins array
|
||||
};
|
||||
serverless.utils
|
||||
.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml));
|
||||
// package.json file
|
||||
const packageJsonFileContent = {
|
||||
devDependencies: {
|
||||
'serverless-plugin-1': '0.1.0',
|
||||
},
|
||||
};
|
||||
fse.writeJsonSync(packageJsonFilePath, packageJsonFileContent);
|
||||
|
||||
plugin.options.name = 'serverless-plugin-1';
|
||||
|
||||
return plugin.install().then(() => {
|
||||
expect(validateStub.calledOnce).to.equal(true);
|
||||
expect(getPluginsStub.calledOnce).to.equal(true);
|
||||
expect(npmInstallStub.calledWithExactly(
|
||||
`npm install --save-dev ${plugin.options.name}`,
|
||||
{ stdio: 'ignore' }
|
||||
)).to.equal(true);
|
||||
expect(consoleLogStub.called).to.equal(true);
|
||||
|
||||
// inspect the serverless.yml file
|
||||
const serverlessFileContent = YAML.load(fs.readFileSync(serverlessYmlFilePath).toString());
|
||||
expect(serverlessFileContent.plugins.length).to.equal(1);
|
||||
expect(serverlessFileContent.plugins).to.include('serverless-plugin-1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#uninstall()', () => {
|
||||
let servicePath;
|
||||
let serverlessYmlFilePath;
|
||||
let packageJsonFilePath;
|
||||
let validateStub;
|
||||
let npmUninstallStub;
|
||||
let savedCwd;
|
||||
|
||||
beforeEach(() => {
|
||||
servicePath = testUtils.getTmpDirPath();
|
||||
fse.ensureDirSync(servicePath);
|
||||
serverlessYmlFilePath = path.join(servicePath, 'serverless.yml');
|
||||
packageJsonFilePath = path.join(servicePath, 'package.json');
|
||||
plugin.serverless.config.servicePath = servicePath;
|
||||
validateStub = sinon
|
||||
.stub(plugin, 'validate').returns(BbPromise.resolve());
|
||||
npmUninstallStub = sinon
|
||||
.stub(childProcess, 'execSync')
|
||||
.withArgs('npm uninstall --save-dev serverless-plugin-1')
|
||||
.returns();
|
||||
// save the cwd so that we can restore it later
|
||||
savedCwd = process.cwd();
|
||||
process.chdir(servicePath);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
plugin.validate.restore();
|
||||
childProcess.execSync.restore();
|
||||
process.chdir(savedCwd);
|
||||
});
|
||||
|
||||
it('should log a message if a problem during uninstallation happens', () => {
|
||||
// serverless.yml
|
||||
const serverlessYml = {
|
||||
service: 'plugin-service',
|
||||
provider: 'aws',
|
||||
};
|
||||
serverless.utils
|
||||
.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml));
|
||||
// package.json file
|
||||
const packageJsonFileContent = {
|
||||
devDependencies: {
|
||||
'serverless-plugin-1': '0.1.0', // plugin is still available
|
||||
},
|
||||
};
|
||||
fse.writeJsonSync(packageJsonFilePath, packageJsonFileContent);
|
||||
|
||||
plugin.options.name = 'serverless-plugin-1';
|
||||
|
||||
return plugin.uninstall().then(() => {
|
||||
expect(validateStub.calledOnce).to.equal(true);
|
||||
expect(npmUninstallStub.calledWithExactly(
|
||||
`npm uninstall --save-dev ${plugin.options.name}`,
|
||||
{ stdio: 'ignore' }
|
||||
)).to.equal(true);
|
||||
expect(consoleLogStub.called).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should only remove the given plugin from the service', () => {
|
||||
// serverless.yml
|
||||
const serverlessYml = {
|
||||
service: 'plugin-service',
|
||||
provider: 'aws',
|
||||
plugins: [
|
||||
'serverless-plugin-1', // only this should be removed
|
||||
'serverless-existing-plugin',
|
||||
],
|
||||
};
|
||||
serverless.utils
|
||||
.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml));
|
||||
// package.json file
|
||||
const packageJsonFileContent = {
|
||||
devDependencies: {}, // plugin was removed via npm
|
||||
};
|
||||
fse.writeJsonSync(packageJsonFilePath, packageJsonFileContent);
|
||||
|
||||
plugin.options.name = 'serverless-plugin-1';
|
||||
|
||||
return plugin.uninstall().then(() => {
|
||||
expect(validateStub.calledOnce).to.equal(true);
|
||||
expect(npmUninstallStub.calledWithExactly(
|
||||
`npm uninstall --save-dev ${plugin.options.name}`,
|
||||
{ stdio: 'ignore' }
|
||||
)).to.equal(true);
|
||||
expect(consoleLogStub.called).to.equal(true);
|
||||
|
||||
// inspect the serverless.yml file
|
||||
const serverlessFileContent = YAML.load(fs.readFileSync(serverlessYmlFilePath).toString());
|
||||
expect(serverlessFileContent.plugins.length).to.equal(1);
|
||||
expect(serverlessFileContent.plugins).to.not.contain('serverless-plugin-1');
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove the plugin from the service if it is the only one', () => {
|
||||
// serverless.yml
|
||||
const serverlessYml = {
|
||||
service: 'plugin-service',
|
||||
provider: 'aws',
|
||||
plugins: ['serverless-plugin-1'], // plugin is available
|
||||
};
|
||||
serverless.utils
|
||||
.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml));
|
||||
// package.json file
|
||||
const packageJsonFileContent = {
|
||||
devDependencies: {}, // plugin was removed via npm
|
||||
};
|
||||
fse.writeJsonSync(packageJsonFilePath, packageJsonFileContent);
|
||||
|
||||
plugin.options.name = 'serverless-plugin-1';
|
||||
|
||||
return plugin.uninstall().then(() => {
|
||||
expect(validateStub.calledOnce).to.equal(true);
|
||||
expect(npmUninstallStub.calledWithExactly(
|
||||
`npm uninstall --save-dev ${plugin.options.name}`,
|
||||
{ stdio: 'ignore' }
|
||||
)).to.equal(true);
|
||||
expect(consoleLogStub.called).to.equal(true);
|
||||
|
||||
// inspect the serverless.yml file
|
||||
const serverlessFileContent = YAML.load(fs.readFileSync(serverlessYmlFilePath).toString());
|
||||
expect(serverlessFileContent).to.not.have.property('plugins');
|
||||
});
|
||||
});
|
||||
|
||||
it('should do nothing if plugins array is not present in service file', () => {
|
||||
// serverless.yml
|
||||
const serverlessYml = {
|
||||
service: 'plugin-service',
|
||||
provider: 'aws',
|
||||
// no plugins array
|
||||
};
|
||||
serverless.utils
|
||||
.writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml));
|
||||
// package.json file
|
||||
const packageJsonFileContent = {
|
||||
devDependencies: {}, // plugin was removed via npm
|
||||
};
|
||||
fse.writeJsonSync(packageJsonFilePath, packageJsonFileContent);
|
||||
|
||||
plugin.options.name = 'serverless-plugin-1';
|
||||
|
||||
return plugin.uninstall().then(() => {
|
||||
expect(validateStub.calledOnce).to.equal(true);
|
||||
expect(npmUninstallStub.calledWithExactly(
|
||||
`npm uninstall --save-dev ${plugin.options.name}`,
|
||||
{ stdio: 'ignore' }
|
||||
)).to.equal(true);
|
||||
expect(consoleLogStub.called).to.equal(true);
|
||||
|
||||
// inspect the serverless.yml file
|
||||
const serverlessFileContent = YAML.load(fs.readFileSync(serverlessYmlFilePath).toString());
|
||||
expect(serverlessFileContent.plugins).to.equal(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#list()', () => {
|
||||
let getPluginsStub;
|
||||
let displayStub;
|
||||
|
||||
beforeEach(() => {
|
||||
getPluginsStub = sinon
|
||||
.stub(plugin, 'getPlugins').returns(BbPromise.resolve());
|
||||
displayStub = sinon
|
||||
.stub(plugin, 'display').returns(BbPromise.resolve());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
plugin.getPlugins.restore();
|
||||
plugin.display.restore();
|
||||
});
|
||||
|
||||
it('should print a list with all available plugins', () =>
|
||||
plugin.list().then(() => {
|
||||
expect(getPluginsStub.calledOnce).to.equal(true);
|
||||
expect(displayStub.calledOnce).to.equal(true);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
describe('#search()', () => {
|
||||
let getPluginsStub;
|
||||
let displayStub;
|
||||
|
||||
beforeEach(() => {
|
||||
getPluginsStub = sinon.stub(plugin, 'getPlugins').returns(BbPromise.resolve(plugins));
|
||||
displayStub = sinon.stub(plugin, 'display').returns(BbPromise.resolve());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
plugin.getPlugins.restore();
|
||||
plugin.display.restore();
|
||||
});
|
||||
|
||||
it('should return a list of plugins based on the search query', () => {
|
||||
plugin.options.query = 'serverless-plugin-1';
|
||||
|
||||
return plugin.search().then(() => {
|
||||
expect(consoleLogStub.calledOnce).to.equal(true);
|
||||
expect(getPluginsStub.calledOnce).to.equal(true);
|
||||
expect(displayStub.calledOnce).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#validate()', () => {
|
||||
it('should throw an error if the the cwd is not a Serverless service', () => {
|
||||
plugin.serverless.config.servicePath = false;
|
||||
|
||||
expect(() => { plugin.validate(); }).to.throw(Error);
|
||||
});
|
||||
|
||||
it('should resolve if the cwd is a Serverless service', (done) => {
|
||||
plugin.serverless.config.servicePath = true;
|
||||
|
||||
plugin.validate().then(() => done());
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getServerlessFilePath()', () => {
|
||||
let servicePath;
|
||||
|
||||
beforeEach(() => {
|
||||
servicePath = testUtils.getTmpDirPath();
|
||||
plugin.serverless.config.servicePath = servicePath;
|
||||
});
|
||||
|
||||
it('should return the correct serverless file path for a .yml file', () => {
|
||||
const serverlessYmlFilePath = path.join(servicePath, 'serverless.yml');
|
||||
fse.ensureFileSync(serverlessYmlFilePath);
|
||||
|
||||
const serverlessFilePath = plugin.getServerlessFilePath();
|
||||
|
||||
expect(serverlessFilePath).to.equal(serverlessYmlFilePath);
|
||||
});
|
||||
|
||||
it('should return the correct serverless file path for a .yaml file', () => {
|
||||
const serverlessYamlFilePath = path.join(servicePath, 'serverless.yaml');
|
||||
fse.ensureFileSync(serverlessYamlFilePath);
|
||||
|
||||
const serverlessFilePath = plugin.getServerlessFilePath();
|
||||
|
||||
expect(serverlessFilePath).to.equal(serverlessYamlFilePath);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getPlugins()', () => {
|
||||
let fetchStub;
|
||||
let PluginWithFetchStub;
|
||||
let pluginWithFetchStub;
|
||||
|
||||
beforeEach(() => {
|
||||
fetchStub = sinon.stub().returns(
|
||||
BbPromise.resolve({
|
||||
json: sinon.stub().returns(BbPromise.resolve(plugins)),
|
||||
})
|
||||
);
|
||||
PluginWithFetchStub = proxyquire('./plugin.js', {
|
||||
'node-fetch': fetchStub,
|
||||
});
|
||||
pluginWithFetchStub = new PluginWithFetchStub(serverless);
|
||||
});
|
||||
|
||||
it('should fetch and return the plugins from the plugins repository', () => {
|
||||
const endpoint = 'https://raw.githubusercontent.com/serverless/plugins/master/plugins.json';
|
||||
|
||||
return pluginWithFetchStub.getPlugins().then((result) => {
|
||||
expect(fetchStub.calledOnce).to.equal(true);
|
||||
expect(fetchStub.args[0][0]).to.equal(endpoint);
|
||||
expect(result).to.deep.equal(plugins);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#display()', () => {
|
||||
it('should display the plugins if present', () => {
|
||||
let expectedMessage = '';
|
||||
expectedMessage += `${chalk.yellow.underline('serverless-plugin-1')}\n`;
|
||||
expectedMessage += 'Serverless Plugin 1\n\n';
|
||||
expectedMessage += `${chalk.yellow.underline('serverless-plugin-2')}\n`;
|
||||
expectedMessage += 'Serverless Plugin 2\n\n';
|
||||
expectedMessage = expectedMessage.slice(0, -2);
|
||||
|
||||
return plugin.display(plugins).then((message) => {
|
||||
expect(consoleLogStub.calledOnce).to.equal(true);
|
||||
expect(message).to.equal(expectedMessage);
|
||||
});
|
||||
});
|
||||
|
||||
it('should print a message when no plugins are available to display', () => {
|
||||
const expectedMessage = 'There are no plugins available to display';
|
||||
|
||||
return plugin.display([]).then((message) => {
|
||||
expect(consoleLogStub.calledOnce).to.equal(true);
|
||||
expect(message).to.equal(expectedMessage);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user