2015-04-07 17:02:15 +02:00
|
|
|
/**
|
2017-01-11 16:24:33 +01:00
|
|
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
2015-04-07 17:02:15 +02:00
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
**/
|
|
|
|
|
|
|
|
var fs = require("fs");
|
|
|
|
var path = require("path");
|
|
|
|
|
2015-11-17 22:12:43 +01:00
|
|
|
var events;
|
2018-04-23 15:24:51 +02:00
|
|
|
var log;
|
2018-04-26 13:32:05 +02:00
|
|
|
|
2018-09-04 12:41:03 +02:00
|
|
|
var log = require("@node-red/util").log;
|
|
|
|
var i18n = require("@node-red/util").i18n;
|
2015-04-07 17:02:15 +02:00
|
|
|
|
|
|
|
var settings;
|
|
|
|
var disableNodePathScan = false;
|
2018-08-31 22:01:47 +02:00
|
|
|
var iconFileExtensions = [".png", ".gif", ".svg"];
|
2015-04-07 17:02:15 +02:00
|
|
|
|
2015-12-07 00:13:52 +01:00
|
|
|
function init(runtime) {
|
2015-11-17 22:12:43 +01:00
|
|
|
settings = runtime.settings;
|
|
|
|
events = runtime.events;
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
|
2017-03-08 11:00:00 +01:00
|
|
|
function isIncluded(name) {
|
|
|
|
if (settings.nodesIncludes) {
|
|
|
|
for (var i=0;i<settings.nodesIncludes.length;i++) {
|
|
|
|
if (settings.nodesIncludes[i] == name) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-06-09 00:17:45 +02:00
|
|
|
function isExcluded(name) {
|
|
|
|
if (settings.nodesExcludes) {
|
2015-05-27 15:11:11 +02:00
|
|
|
for (var i=0;i<settings.nodesExcludes.length;i++) {
|
2015-06-09 00:17:45 +02:00
|
|
|
if (settings.nodesExcludes[i] == name) {
|
|
|
|
return true;
|
2015-05-27 15:11:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-06-09 00:17:45 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
function getLocalFile(file) {
|
2017-03-08 11:00:00 +01:00
|
|
|
if (!isIncluded(path.basename(file)) || isExcluded(path.basename(file))) {
|
2015-06-09 00:17:45 +02:00
|
|
|
return null;
|
|
|
|
}
|
2015-11-16 12:31:55 +01:00
|
|
|
try {
|
|
|
|
fs.statSync(file.replace(/\.js$/,".html"));
|
2015-05-27 15:11:11 +02:00
|
|
|
return {
|
|
|
|
file: file,
|
|
|
|
module: "node-red",
|
|
|
|
name: path.basename(file).replace(/^\d+-/,"").replace(/\.js$/,""),
|
|
|
|
version: settings.version
|
|
|
|
};
|
2015-11-16 12:31:55 +01:00
|
|
|
} catch(err) {
|
|
|
|
return null;
|
2015-05-27 15:11:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Synchronously walks the directory looking for node files.
|
|
|
|
* Emits 'node-icon-dir' events for an icon dirs found
|
|
|
|
* @param dir the directory to search
|
|
|
|
* @return an array of fully-qualified paths to .js files
|
|
|
|
*/
|
|
|
|
function getLocalNodeFiles(dir) {
|
2017-04-10 16:41:20 +02:00
|
|
|
dir = path.resolve(dir);
|
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
var result = [];
|
|
|
|
var files = [];
|
2018-04-26 13:32:05 +02:00
|
|
|
var icons = [];
|
2015-04-07 17:02:15 +02:00
|
|
|
try {
|
|
|
|
files = fs.readdirSync(dir);
|
|
|
|
} catch(err) {
|
2018-05-23 11:59:08 +02:00
|
|
|
return {files: [], icons: []};
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
files.sort();
|
|
|
|
files.forEach(function(fn) {
|
|
|
|
var stats = fs.statSync(path.join(dir,fn));
|
|
|
|
if (stats.isFile()) {
|
|
|
|
if (/\.js$/.test(fn)) {
|
2015-05-27 15:11:11 +02:00
|
|
|
var info = getLocalFile(path.join(dir,fn));
|
|
|
|
if (info) {
|
|
|
|
result.push(info);
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (stats.isDirectory()) {
|
|
|
|
// Ignore /.dirs/, /lib/ /node_modules/
|
2015-04-26 00:29:53 +02:00
|
|
|
if (!/^(\..*|lib|icons|node_modules|test|locales)$/.test(fn)) {
|
2018-04-26 13:32:05 +02:00
|
|
|
var subDirResults = getLocalNodeFiles(path.join(dir,fn));
|
|
|
|
result = result.concat(subDirResults.files);
|
|
|
|
icons = icons.concat(subDirResults.icons);
|
2015-04-07 17:02:15 +02:00
|
|
|
} else if (fn === "icons") {
|
2017-11-30 14:13:35 +01:00
|
|
|
var iconList = scanIconDir(path.join(dir,fn));
|
2018-04-26 13:32:05 +02:00
|
|
|
icons.push({path:path.join(dir,fn),icons:iconList});
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2018-04-26 13:32:05 +02:00
|
|
|
return {files: result, icons: icons}
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function scanDirForNodesModules(dir,moduleName) {
|
|
|
|
var results = [];
|
2018-01-24 16:07:43 +01:00
|
|
|
var scopeName;
|
2015-04-07 17:02:15 +02:00
|
|
|
try {
|
|
|
|
var files = fs.readdirSync(dir);
|
2018-01-24 16:07:43 +01:00
|
|
|
if (moduleName) {
|
|
|
|
var m = /^(?:(@[^/]+)[/])?([^@/]+)/.exec(moduleName);
|
|
|
|
if (m) {
|
|
|
|
scopeName = m[1];
|
|
|
|
moduleName = m[2];
|
|
|
|
}
|
|
|
|
}
|
2015-04-07 17:02:15 +02:00
|
|
|
for (var i=0;i<files.length;i++) {
|
|
|
|
var fn = files[i];
|
2016-05-06 11:16:41 +02:00
|
|
|
if (/^@/.test(fn)) {
|
2018-01-24 16:07:43 +01:00
|
|
|
if (scopeName && scopeName === fn) {
|
|
|
|
// Looking for a specific scope/module
|
|
|
|
results = results.concat(scanDirForNodesModules(path.join(dir,fn),moduleName));
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
results = results.concat(scanDirForNodesModules(path.join(dir,fn),moduleName));
|
|
|
|
}
|
2016-05-06 11:16:41 +02:00
|
|
|
} else {
|
2017-03-08 11:00:00 +01:00
|
|
|
if (isIncluded(fn) && !isExcluded(fn) && (!moduleName || fn == moduleName)) {
|
2016-05-06 11:16:41 +02:00
|
|
|
var pkgfn = path.join(dir,fn,"package.json");
|
|
|
|
try {
|
|
|
|
var pkg = require(pkgfn);
|
|
|
|
if (pkg['node-red']) {
|
|
|
|
var moduleDir = path.join(dir,fn);
|
|
|
|
results.push({dir:moduleDir,package:pkg});
|
|
|
|
}
|
|
|
|
} catch(err) {
|
|
|
|
if (err.code != "MODULE_NOT_FOUND") {
|
|
|
|
// TODO: handle unexpected error
|
|
|
|
}
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
2016-05-06 11:16:41 +02:00
|
|
|
if (fn == moduleName) {
|
|
|
|
break;
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch(err) {
|
|
|
|
}
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Scans the node_modules path for nodes
|
|
|
|
* @param moduleName the name of the module to be found
|
|
|
|
* @return a list of node modules: {dir,package}
|
|
|
|
*/
|
|
|
|
function scanTreeForNodesModules(moduleName) {
|
2015-12-07 00:13:52 +01:00
|
|
|
var dir = settings.coreNodesDir;
|
2015-04-07 17:02:15 +02:00
|
|
|
var results = [];
|
|
|
|
var userDir;
|
|
|
|
|
|
|
|
if (settings.userDir) {
|
|
|
|
userDir = path.join(settings.userDir,"node_modules");
|
2016-08-04 17:49:36 +02:00
|
|
|
results = scanDirForNodesModules(userDir,moduleName);
|
|
|
|
results.forEach(function(r) { r.local = true; });
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
2015-07-10 22:42:14 +02:00
|
|
|
|
2015-12-07 00:29:58 +01:00
|
|
|
if (dir) {
|
|
|
|
var up = path.resolve(path.join(dir,".."));
|
|
|
|
while (up !== dir) {
|
|
|
|
var pm = path.join(dir,"node_modules");
|
|
|
|
if (pm != userDir) {
|
|
|
|
results = results.concat(scanDirForNodesModules(pm,moduleName));
|
|
|
|
}
|
|
|
|
dir = up;
|
|
|
|
up = path.resolve(path.join(dir,".."));
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getModuleNodeFiles(module) {
|
2015-07-10 22:42:14 +02:00
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
var moduleDir = module.dir;
|
|
|
|
var pkg = module.package;
|
2015-07-10 22:42:14 +02:00
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
var nodes = pkg['node-red'].nodes||{};
|
|
|
|
var results = [];
|
|
|
|
var iconDirs = [];
|
2018-04-26 13:32:05 +02:00
|
|
|
var iconList = [];
|
2015-04-07 17:02:15 +02:00
|
|
|
for (var n in nodes) {
|
|
|
|
/* istanbul ignore else */
|
|
|
|
if (nodes.hasOwnProperty(n)) {
|
|
|
|
var file = path.join(moduleDir,nodes[n]);
|
|
|
|
results.push({
|
|
|
|
file: file,
|
|
|
|
module: pkg.name,
|
|
|
|
name: n,
|
|
|
|
version: pkg.version
|
|
|
|
});
|
|
|
|
var iconDir = path.join(moduleDir,path.dirname(nodes[n]),"icons");
|
|
|
|
if (iconDirs.indexOf(iconDir) == -1) {
|
2015-11-16 12:31:55 +01:00
|
|
|
try {
|
|
|
|
fs.statSync(iconDir);
|
2018-04-26 13:32:05 +02:00
|
|
|
var icons = scanIconDir(iconDir);
|
|
|
|
iconList.push({path:iconDir,icons:icons});
|
2015-04-07 17:02:15 +02:00
|
|
|
iconDirs.push(iconDir);
|
2015-11-16 12:31:55 +01:00
|
|
|
} catch(err) {
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-04-26 13:32:05 +02:00
|
|
|
var result = {files:results,icons:iconList};
|
|
|
|
|
2016-03-03 00:34:24 +01:00
|
|
|
var examplesDir = path.join(moduleDir,"examples");
|
|
|
|
try {
|
|
|
|
fs.statSync(examplesDir)
|
2018-04-26 13:32:05 +02:00
|
|
|
result.examples = {path:examplesDir};
|
|
|
|
// events.emit("node-examples-dir",{name:pkg.name,path:examplesDir});
|
2016-03-03 00:34:24 +01:00
|
|
|
} catch(err) {
|
|
|
|
}
|
2018-04-26 13:32:05 +02:00
|
|
|
return result;
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
|
2015-12-07 00:13:52 +01:00
|
|
|
function getNodeFiles(disableNodePathScan) {
|
2015-04-07 17:02:15 +02:00
|
|
|
var dir;
|
|
|
|
// Find all of the nodes to load
|
2015-12-07 00:29:58 +01:00
|
|
|
var nodeFiles = [];
|
2018-04-26 13:32:05 +02:00
|
|
|
var results;
|
2015-07-10 22:42:14 +02:00
|
|
|
|
2018-08-16 00:12:51 +02:00
|
|
|
var dir;
|
|
|
|
var iconList = [];
|
2015-12-07 00:29:58 +01:00
|
|
|
if (settings.coreNodesDir) {
|
2018-04-26 13:32:05 +02:00
|
|
|
results = getLocalNodeFiles(path.resolve(settings.coreNodesDir));
|
|
|
|
nodeFiles = nodeFiles.concat(results.files);
|
|
|
|
iconList = iconList.concat(results.icons);
|
2018-08-20 21:31:29 +02:00
|
|
|
var defaultLocalesPath = path.join(settings.coreNodesDir,"locales");
|
2015-12-07 00:29:58 +01:00
|
|
|
i18n.registerMessageCatalog("node-red",defaultLocalesPath,"messages.json");
|
|
|
|
}
|
2015-07-10 22:42:14 +02:00
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
if (settings.userDir) {
|
2018-01-05 15:23:47 +01:00
|
|
|
dir = path.join(settings.userDir,"lib","icons");
|
2018-04-26 13:32:05 +02:00
|
|
|
var icons = scanIconDir(dir);
|
|
|
|
if (icons.length > 0) {
|
|
|
|
iconList.push({path:dir,icons:icons});
|
2018-01-05 15:23:47 +01:00
|
|
|
}
|
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
dir = path.join(settings.userDir,"nodes");
|
2018-04-26 13:32:05 +02:00
|
|
|
results = getLocalNodeFiles(path.resolve(dir));
|
|
|
|
nodeFiles = nodeFiles.concat(results.files);
|
|
|
|
iconList = iconList.concat(results.icons);
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
if (settings.nodesDir) {
|
|
|
|
dir = settings.nodesDir;
|
|
|
|
if (typeof settings.nodesDir == "string") {
|
|
|
|
dir = [dir];
|
|
|
|
}
|
|
|
|
for (var i=0;i<dir.length;i++) {
|
2018-04-26 13:32:05 +02:00
|
|
|
results = getLocalNodeFiles(dir[i]);
|
|
|
|
nodeFiles = nodeFiles.concat(results.files);
|
|
|
|
iconList = iconList.concat(results.icons);
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
}
|
2015-07-10 22:42:14 +02:00
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
var nodeList = {
|
|
|
|
"node-red": {
|
|
|
|
name: "node-red",
|
|
|
|
version: settings.version,
|
2018-04-26 13:32:05 +02:00
|
|
|
nodes: {},
|
|
|
|
icons: iconList
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
nodeFiles.forEach(function(node) {
|
|
|
|
nodeList["node-red"].nodes[node.name] = node;
|
|
|
|
});
|
2020-02-03 04:59:12 +01:00
|
|
|
if (settings.coreNodesDir) {
|
|
|
|
var examplesDir = path.join(settings.coreNodesDir,"examples");
|
|
|
|
nodeList["node-red"].examples = {path: examplesDir};
|
|
|
|
}
|
2015-07-10 22:42:14 +02:00
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
if (!disableNodePathScan) {
|
|
|
|
var moduleFiles = scanTreeForNodesModules();
|
2018-09-04 12:26:05 +02:00
|
|
|
|
|
|
|
// Filter the module list to ignore global modules
|
|
|
|
// that have also been installed locally - allowing the user to
|
|
|
|
// update a module they may not otherwise be able to touch
|
|
|
|
|
|
|
|
moduleFiles.sort(function(A,B) {
|
|
|
|
if (A.local && !B.local) {
|
|
|
|
return -1
|
|
|
|
} else if (!A.local && B.local) {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
})
|
|
|
|
var knownModules = {};
|
|
|
|
moduleFiles = moduleFiles.filter(function(mod) {
|
|
|
|
var result;
|
|
|
|
if (!knownModules[mod.package.name]) {
|
|
|
|
knownModules[mod.package.name] = true;
|
|
|
|
result = true;
|
|
|
|
} else {
|
|
|
|
result = false;
|
|
|
|
}
|
|
|
|
log.debug("Module: "+mod.package.name+" "+mod.package.version+(result?"":" *ignored due to local copy*"));
|
|
|
|
log.debug(" "+mod.dir);
|
|
|
|
return result;
|
|
|
|
});
|
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
moduleFiles.forEach(function(moduleFile) {
|
|
|
|
var nodeModuleFiles = getModuleNodeFiles(moduleFile);
|
|
|
|
nodeList[moduleFile.package.name] = {
|
|
|
|
name: moduleFile.package.name,
|
|
|
|
version: moduleFile.package.version,
|
2018-05-21 23:08:04 +02:00
|
|
|
path: moduleFile.dir,
|
2016-08-04 17:49:36 +02:00
|
|
|
local: moduleFile.local||false,
|
2018-04-26 13:32:05 +02:00
|
|
|
nodes: {},
|
|
|
|
icons: nodeModuleFiles.icons,
|
|
|
|
examples: nodeModuleFiles.examples
|
2015-04-07 17:02:15 +02:00
|
|
|
};
|
2015-07-10 22:42:14 +02:00
|
|
|
if (moduleFile.package['node-red'].version) {
|
|
|
|
nodeList[moduleFile.package.name].redVersion = moduleFile.package['node-red'].version;
|
|
|
|
}
|
2018-04-26 13:32:05 +02:00
|
|
|
nodeModuleFiles.files.forEach(function(node) {
|
2016-08-04 17:49:36 +02:00
|
|
|
node.local = moduleFile.local||false;
|
2015-04-07 17:02:15 +02:00
|
|
|
nodeList[moduleFile.package.name].nodes[node.name] = node;
|
|
|
|
});
|
2018-04-26 13:32:05 +02:00
|
|
|
nodeFiles = nodeFiles.concat(nodeModuleFiles.files);
|
2015-04-07 17:02:15 +02:00
|
|
|
});
|
2015-12-07 00:13:52 +01:00
|
|
|
} else {
|
2018-04-26 13:32:05 +02:00
|
|
|
// console.log("node path scan disabled");
|
2015-04-07 17:02:15 +02:00
|
|
|
}
|
|
|
|
return nodeList;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getModuleFiles(module) {
|
|
|
|
var nodeList = {};
|
2015-07-10 22:42:14 +02:00
|
|
|
|
2015-04-07 17:02:15 +02:00
|
|
|
var moduleFiles = scanTreeForNodesModules(module);
|
|
|
|
if (moduleFiles.length === 0) {
|
2015-05-21 00:46:49 +02:00
|
|
|
var err = new Error(log._("nodes.registry.localfilesystem.module-not-found", {module:module}));
|
2015-04-07 17:02:15 +02:00
|
|
|
err.code = 'MODULE_NOT_FOUND';
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
|
|
|
|
moduleFiles.forEach(function(moduleFile) {
|
|
|
|
var nodeModuleFiles = getModuleNodeFiles(moduleFile);
|
|
|
|
nodeList[moduleFile.package.name] = {
|
|
|
|
name: moduleFile.package.name,
|
|
|
|
version: moduleFile.package.version,
|
2020-09-25 04:58:03 +02:00
|
|
|
path: moduleFile.dir,
|
2018-04-26 13:32:05 +02:00
|
|
|
nodes: {},
|
|
|
|
icons: nodeModuleFiles.icons,
|
|
|
|
examples: nodeModuleFiles.examples
|
2015-04-07 17:02:15 +02:00
|
|
|
};
|
2015-07-10 22:42:14 +02:00
|
|
|
if (moduleFile.package['node-red'].version) {
|
|
|
|
nodeList[moduleFile.package.name].redVersion = moduleFile.package['node-red'].version;
|
|
|
|
}
|
2018-04-26 13:32:05 +02:00
|
|
|
nodeModuleFiles.files.forEach(function(node) {
|
2015-04-07 17:02:15 +02:00
|
|
|
nodeList[moduleFile.package.name].nodes[node.name] = node;
|
2016-11-16 15:12:31 +01:00
|
|
|
nodeList[moduleFile.package.name].nodes[node.name].local = moduleFile.local || false;
|
2015-04-07 17:02:15 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
return nodeList;
|
|
|
|
}
|
|
|
|
|
2019-06-21 13:36:20 +02:00
|
|
|
// If this finds an svg and a png with the same name, it will only list the svg
|
2017-11-30 14:13:35 +01:00
|
|
|
function scanIconDir(dir) {
|
|
|
|
var iconList = [];
|
2019-06-21 13:36:20 +02:00
|
|
|
var svgs = {};
|
2017-11-30 14:13:35 +01:00
|
|
|
try {
|
|
|
|
var files = fs.readdirSync(dir);
|
|
|
|
files.forEach(function(file) {
|
|
|
|
var stats = fs.statSync(path.join(dir, file));
|
2019-06-21 13:36:20 +02:00
|
|
|
var ext = path.extname(file).toLowerCase();
|
|
|
|
if (stats.isFile() && iconFileExtensions.indexOf(ext) !== -1) {
|
2017-11-30 14:13:35 +01:00
|
|
|
iconList.push(file);
|
2019-06-21 13:36:20 +02:00
|
|
|
if (ext === ".svg") {
|
|
|
|
svgs[file.substring(0,file.length-4)] = true;
|
|
|
|
}
|
2017-11-30 14:13:35 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
} catch(err) {
|
|
|
|
}
|
2019-06-21 13:36:20 +02:00
|
|
|
iconList = iconList.filter(f => {
|
|
|
|
return /.svg$/i.test(f) || !svgs[f.substring(0,f.length-4)]
|
|
|
|
})
|
2017-11-30 14:13:35 +01:00
|
|
|
return iconList;
|
|
|
|
}
|
2015-04-07 17:02:15 +02:00
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
init: init,
|
|
|
|
getNodeFiles: getNodeFiles,
|
2015-05-27 15:11:11 +02:00
|
|
|
getLocalFile: getLocalFile,
|
2015-04-07 17:02:15 +02:00
|
|
|
getModuleFiles: getModuleFiles
|
|
|
|
}
|