Files
homebridge/lib/plugin.js
Jan Philipp affe3fde63 Provide and output proper existing error message
If the found plugin / package does not fulfill some specs (i.e. does not contain the keyword `homebridge-plugin`), it will be rejected with an error. Without that change nobody will be notified WHY the plugin has been rejected/skipped.
2015-11-08 17:31:02 +01:00

203 lines
6.5 KiB
JavaScript

var path = require('path');
var fs = require('fs');
var semver = require('semver');
var User = require('./user').User;
var version = require('./version');
var log = require("./logger")._system;
'use strict';
module.exports = {
Plugin: Plugin
}
/**
* Homebridge Plugin.
*
* Allows for discovering and loading installed Homebridge plugins.
*/
function Plugin(pluginPath) {
this.pluginPath = pluginPath; // like "/usr/local/lib/node_modules/homebridge-lockitron"
this.initializer; // exported function from the plugin that initializes it
}
Plugin.prototype.name = function() {
return path.basename(this.pluginPath);
}
Plugin.prototype.load = function(options) {
options = options || {};
// does this plugin exist at all?
if (!fs.existsSync(this.pluginPath)) {
throw new Error("Plugin " + this.pluginPath + " was not found. Make sure the module '" + this.pluginPath + "' is installed.");
}
// attempt to load package.json
var pjson = Plugin.loadPackageJSON(this.pluginPath);
// make sure the name is prefixed with 'homebridge-'
if (!pjson.name || pjson.name.indexOf('homebridge-') != 0) {
throw new Error("Plugin " + this.pluginPath + " does not have a package name that begins with 'homebridge-'.");
}
// very temporary fix for first wave of plugins
if (pjson.peerDepdendencies && (!pjson.engines || !pjson.engines.homebridge)) {
var engines = pjson.engines || {}
engines.homebridge = pjson.peerDepdendencies.homebridge;
pjson.engines = engines;
}
// pluck out the HomeBridge version requirement
if (!pjson.engines || !pjson.engines.homebridge) {
throw new Error("Plugin " + this.pluginPath + " does not contain the 'homebridge' package in 'engines'.");
}
var versionRequired = pjson.engines.homebridge;
// make sure the version is satisfied by the currently running version of HomeBridge
if (!semver.satisfies(version, versionRequired)) {
throw new Error("Plugin " + this.pluginPath + " requires a HomeBridge version of " + versionRequired + " which does not satisfy the current HomeBridge version of " + version + ". You may need to upgrade your installation of HomeBridge.");
}
// figure out the main module - index.js unless otherwise specified
var main = pjson.main || "./index.js";
var mainPath = path.join(this.pluginPath, main);
// try to require() it and grab the exported initialization hook
this.initializer = require(mainPath);
}
Plugin.loadPackageJSON = function(pluginPath) {
// check for a package.json
var pjsonPath = path.join(pluginPath, "package.json");
var pjson = null;
if (!fs.existsSync(pjsonPath)) {
throw new Error("Plugin " + pluginPath + " does not contain a package.json.");
}
try {
// attempt to parse package.json
pjson = JSON.parse(fs.readFileSync(pjsonPath));
}
catch (err) {
throw new Error("Plugin " + pluginPath + " contains an invalid package.json. Error: " + err);
}
// verify that it's tagged with the correct keyword
if (!pjson.keywords || pjson.keywords.indexOf("homebridge-plugin") == -1) {
throw new Error("Plugin " + pluginPath + " package.json does not contain the keyword 'homebridge-plugin'.");
}
return pjson;
}
Plugin.getDefaultPaths = function() {
var win32 = process.platform === 'win32';
var paths = [];
// add the paths used by require()
paths = paths.concat(require.main.paths);
// THIS SECTION FROM: https://github.com/yeoman/environment/blob/master/lib/resolver.js
// Adding global npm directories
// We tried using npm to get the global modules path, but it haven't work out
// because of bugs in the parseable implementation of `ls` command and mostly
// performance issues. So, we go with our best bet for now.
if (process.env.NODE_PATH) {
paths = process.env.NODE_PATH.split(path.delimiter)
.filter(function(p) { return !!p; }) // trim out empty values
.concat(paths);
} else {
// Default paths for each system
if (win32) {
paths.push(path.join(process.env.APPDATA, 'npm/node_modules'));
} else {
paths.push('/usr/local/lib/node_modules');
paths.push('/usr/lib/node_modules');
}
}
return paths;
}
// All search paths we will use to discover installed plugins
Plugin.paths = Plugin.getDefaultPaths();
Plugin.addPluginPath = function(pluginPath) {
Plugin.paths.unshift(path.resolve(process.cwd(), pluginPath));
}
// Gets all plugins installed on the local system
Plugin.installed = function() {
var plugins = [];
var pluginsByName = {}; // don't add duplicate plugins
var searchedPaths = {}; // don't search the same paths twice
// search for plugins among all known paths, in order
for (var index in Plugin.paths) {
var requirePath = Plugin.paths[index];
// did we already search this path?
if (searchedPaths[requirePath])
continue;
searchedPaths[requirePath] = true;
// just because this path is in require.main.paths doesn't mean it necessarily exists!
if (!fs.existsSync(requirePath))
continue;
var names = fs.readdirSync(requirePath);
// does this path point inside a single plugin and not a directory containing plugins?
if (fs.existsSync(path.join(requirePath, "package.json")))
names = [""];
// read through each directory in this node_modules folder
for (var index2 in names) {
var name = names[index2];
// reconstruct full path
var pluginPath = path.join(requirePath, name);
// we only care about directories
if (!fs.statSync(pluginPath).isDirectory()) continue;
// does this module contain a package.json?
var pjson;
try {
// throws an Error if this isn't a homebridge plugin
pjson = Plugin.loadPackageJSON(pluginPath);
}
catch (err) {
if (name.substring(0,11) === 'homebridge-') {
// show warning only if module starts with prefix
log.warn("Warning: skipping plugin found at '" + pluginPath + "' because of: " + err.message);
}
// swallow error and skip this module
continue;
}
// get actual name if this path points inside a single plugin
if (!name) name = pjson.name;
// add it to the return list
if (!pluginsByName[name]) {
pluginsByName[name] = pluginPath;
plugins.push(new Plugin(pluginPath));
}
else {
log.warn("Warning: skipping plugin found at '" + pluginPath + "' since we already loaded the same plugin from '" + pluginsByName[name] + "'.");
}
}
}
return plugins;
}