( // Module boilerplate to support node.js and AMD. (typeof module !== "undefined" && function (m) { module.exports = m(require('events')); }) || (typeof define === "function" && function (m) { define(["events"], m); }) )(function (events) { "use strict"; var EventEmitter = events.EventEmitter; var exports = {}; var DEBUG = typeof location != "undefined" && location.href.match(/debug=[123]/) ? true : false; // Only define Node-style usage using sync I/O if in node. if (typeof module === "object") (function () { var dirname = require('path').dirname; var resolve = require('path').resolve; var existsSync = require('fs').existsSync || require('path').existsSync; var realpathSync = require('fs').realpathSync; var exists = require('fs').exists || require('path').exists; var realpath = require('fs').realpath; var packagePathCache = {}; exports.loadConfig = loadConfig; exports.resolveConfig = resolveConfig; // This is assumed to be used at startup and uses sync I/O as well as can // throw exceptions. It loads and parses a config file. function loadConfig(configPath, callback) { var config = require(configPath); var base = dirname(configPath); return resolveConfig(config, base, callback); } function resolveConfig(config, base, callback) { if (!callback) return resolveConfigSync(config, base); else resolveConfigAsync(config, base, callback); } function resolveConfigSync(config, base) { config.forEach(function (plugin, index) { // Shortcut where string is used for plugin without any options. if (typeof plugin === "string") { plugin = config[index] = { packagePath: plugin }; } // The plugin is a package on the disk. We need to load it. if (plugin.hasOwnProperty("packagePath") && !plugin.hasOwnProperty("setup")) { var defaults = resolveModuleSync(base, plugin.packagePath); Object.keys(defaults).forEach(function (key) { if (!plugin.hasOwnProperty(key)) { plugin[key] = defaults[key]; } }); plugin.packagePath = defaults.packagePath; plugin.setup = require(plugin.packagePath); } }); return config; } function resolveConfigAsync(config, base, callback) { function resolveNext(i) { if (i >= config.length) { return callback(null, config); } var plugin = config[i]; // Shortcut where string is used for plugin without any options. if (typeof plugin === "string") { plugin = config[i] = { packagePath: plugin }; } // The plugin is a package on the disk. We need to load it. if (plugin.hasOwnProperty("packagePath") && !plugin.hasOwnProperty("setup")) { resolveModule(base, plugin.packagePath, function(err, defaults) { if (err) return callback(err); Object.keys(defaults).forEach(function (key) { if (!plugin.hasOwnProperty(key)) { plugin[key] = defaults[key]; } }); plugin.packagePath = defaults.packagePath; try { plugin.setup = require(plugin.packagePath); } catch(e) { return callback(e); } return resolveNext(++i); }); return; } return resolveNext(++i); } resolveNext(0); } // Loads a module, getting metadata from either it's package.json or export // object. function resolveModuleSync(base, modulePath) { var packagePath; try { packagePath = resolvePackageSync(base, modulePath + "/package.json"); } catch (err) { if (err.code !== "ENOENT") throw err; } var metadata = packagePath && require(packagePath).plugin || {}; if (packagePath) { modulePath = dirname(packagePath); } else { modulePath = resolvePackageSync(base, modulePath); } var module = require(modulePath); metadata.provides = metadata.provides || module.provides || []; metadata.consumes = metadata.consumes || module.consumes || []; metadata.packagePath = modulePath; return metadata; } // Loads a module, getting metadata from either it's package.json or export // object. function resolveModule(base, modulePath, callback) { resolvePackage(base, modulePath + "/package.json", function(err, packagePath) { //if (err && err.code !== "ENOENT") return callback(err); var metadata = {}; if (!err) { try { metadata = packagePath && require(packagePath).plugin || {}; } catch(e) { return callback(e); } } (function(next) { if (err) { //@todo Fabian what is a better way? resolvePackage(base, modulePath + ".js", next); } else if (packagePath) { next(null, dirname(packagePath)); } else { resolvePackage(base, modulePath, next); } })(function(err, modulePath) { if (err) return callback(err); var module; try { module = require(modulePath); } catch(e) { return callback(e); } metadata.provides = metadata.provides || module.provides || []; metadata.consumes = metadata.consumes || module.consumes || []; metadata.packagePath = modulePath; callback(null, metadata); }); }); } // Node style package resolving so that plugins' package.json can be found relative to the config file // It's not the full node require system algorithm, but it's the 99% case // This throws, make sure to wrap in try..catch function resolvePackageSync(base, packagePath) { var originalBase = base; if (!(base in packagePathCache)) { packagePathCache[base] = {}; } var cache = packagePathCache[base]; if (packagePath in cache) { return cache[packagePath]; } var newPath; if (packagePath[0] === "." || packagePath[0] === "/") { newPath = resolve(base, packagePath); if (!existsSync(newPath)) { newPath = newPath + ".js"; } if (existsSync(newPath)) { newPath = realpathSync(newPath); cache[packagePath] = newPath; return newPath; } } else { while (base) { newPath = resolve(base, "node_modules", packagePath); if (existsSync(newPath)) { newPath = realpathSync(newPath); cache[packagePath] = newPath; return newPath; } base = resolve(base, '..'); } } var err = new Error("Can't find '" + packagePath + "' relative to '" + originalBase + "'"); err.code = "ENOENT"; throw err; } function resolvePackage(base, packagePath, callback) { var originalBase = base; if (!packagePathCache.hasOwnProperty(base)) { packagePathCache[base] = {}; } var cache = packagePathCache[base]; if (cache.hasOwnProperty(packagePath)) { return callback(null, cache[packagePath]); } if (packagePath[0] === "." || packagePath[0] === "/") { var newPath = resolve(base, packagePath); exists(newPath, function(exists) { if (exists) { realpath(newPath, function(err, newPath) { if (err) return callback(err); cache[packagePath] = newPath; return callback(null, newPath); }); } else { var err = new Error("Can't find '" + packagePath + "' relative to '" + originalBase + "'"); err.code = "ENOENT"; return callback(err); } }); } else { tryNext(base); } function tryNext(base) { if (base == "/") { var err = new Error("Can't find '" + packagePath + "' relative to '" + originalBase + "'"); err.code = "ENOENT"; return callback(err); } var newPath = resolve(base, "node_modules", packagePath); exists(newPath, function(exists) { if (exists) { realpath(newPath, function(err, newPath) { if (err) return callback(err); cache[packagePath] = newPath; return callback(null, newPath); }); } else { var nextBase = resolve(base, '..'); if (nextBase === base) tryNext("/"); // for windows else tryNext(nextBase); } }); } } }()); // Otherwise use amd to load modules. else (function () { exports.loadConfig = loadConfig; exports.resolveConfig = resolveConfig; function loadConfig(path, callback) { require([path], function (config) { resolveConfig(config, callback); }); } function resolveConfig(config, base, callback) { if (typeof base == "function") { callback = base; base = ""; } var paths = [], pluginIndexes = {}; config.forEach(function (plugin, index) { // Shortcut where string is used for plugin without any options. if (typeof plugin === "string") { plugin = config[index] = { packagePath: plugin }; } // The plugin is a package over the network. We need to load it. if (plugin.hasOwnProperty("packagePath") && !plugin.hasOwnProperty("setup")) { paths.push((base || "") + plugin.packagePath); pluginIndexes[plugin.packagePath] = index; } }); // Mass-Load path-based plugins using amd's require require(paths, function () { var args = arguments; paths.forEach(function (name, i) { var module = args[i]; var plugin = config[pluginIndexes[name]]; plugin.setup = module; plugin.provides = module.provides || []; plugin.consumes = module.consumes || []; }); callback(null, config); }); } }()); exports.createApp = createApp; exports.Architect = Architect; // Check a plugin config list for bad dependencies and throw on error function checkConfig(config, lookup) { // Check for the required fields in each plugin. config.forEach(function (plugin) { if (plugin.checked) { return; } if (!plugin.hasOwnProperty("setup")) { throw new Error("Plugin is missing the setup function " + JSON.stringify(plugin)); } if (!plugin.hasOwnProperty("provides")) { throw new Error("Plugin is missing the provides array " + JSON.stringify(plugin)); } if (!plugin.hasOwnProperty("consumes")) { throw new Error("Plugin is missing the consumes array " + JSON.stringify(plugin)); } }); return checkCycles(config, lookup); } function checkCycles(config, lookup) { var plugins = []; config.forEach(function(pluginConfig, index) { plugins.push({ packagePath: pluginConfig.packagePath, provides: pluginConfig.provides.concat(), consumes: pluginConfig.consumes.concat(), i: index }); }); var resolved = { hub: true }; var changed = true; var sorted = []; while(plugins.length && changed) { changed = false; plugins.concat().forEach(function(plugin) { var consumes = plugin.consumes.concat(); var resolvedAll = true; for (var i=0; i