1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00
node-red/packages/node_modules/@node-red/registry/lib/loader.js
Nick O'Leary 6fb96fa3c1
Move exec and events components to util module
The exec and events components are common components that
are used by both runtime and registry. It makes sense to
move them into the util package.

This also adds some docs to the registry module
2020-12-07 11:49:01 +00:00

377 lines
12 KiB
JavaScript

/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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-extra");
var path = require("path");
var semver = require("semver");
var localfilesystem = require("./localfilesystem");
var registry = require("./registry");
var registryUtil = require("./util")
var i18n = require("@node-red/util").i18n;
var log = require("@node-red/util").log;
var settings;
function init(_runtime) {
settings = _runtime.settings;
localfilesystem.init(settings);
registryUtil.init(_runtime);
}
function load(disableNodePathScan) {
// To skip node scan, the following line will use the stored node list.
// We should expose that as an option at some point, although the
// performance gains are minimal.
//return loadNodeFiles(registry.getModuleList());
log.info(log._("server.loading"));
var nodeFiles = localfilesystem.getNodeFiles(disableNodePathScan);
return loadNodeFiles(nodeFiles);
}
function loadNodeFiles(nodeFiles) {
var promises = [];
var nodes = [];
for (var module in nodeFiles) {
/* istanbul ignore else */
if (nodeFiles.hasOwnProperty(module)) {
if (nodeFiles[module].redVersion &&
!semver.satisfies((settings.version||"0.0.0").replace(/(\-[1-9A-Za-z-][0-9A-Za-z-\.]*)?(\+[0-9A-Za-z-\.]+)?$/,""), nodeFiles[module].redVersion)) {
//TODO: log it
log.warn("["+module+"] "+log._("server.node-version-mismatch",{version:nodeFiles[module].redVersion}));
nodeFiles[module].err = "version_mismatch";
continue;
}
if (module == "node-red" || !registry.getModuleInfo(module)) {
var first = true;
for (var node in nodeFiles[module].nodes) {
/* istanbul ignore else */
if (nodeFiles[module].nodes.hasOwnProperty(node)) {
if (module != "node-red" && first) {
// Check the module directory exists
first = false;
var fn = nodeFiles[module].nodes[node].file;
var parts = fn.split("/");
var i = parts.length-1;
for (;i>=0;i--) {
if (parts[i] == "node_modules") {
break;
}
}
var moduleFn = parts.slice(0,i+2).join("/");
try {
var stat = fs.statSync(moduleFn);
} catch(err) {
// Module not found, don't attempt to load its nodes
break;
}
}
try {
promises.push(loadNodeConfig(nodeFiles[module].nodes[node]).then((function() {
var m = module;
var n = node;
return function(nodeSet) {
nodeFiles[m].nodes[n] = nodeSet;
nodes.push(nodeSet);
}
})()).catch(err => {}));
} catch(err) {
//
}
}
}
}
}
}
return Promise.all(promises).then(function(results) {
for (var module in nodeFiles) {
if (nodeFiles.hasOwnProperty(module)) {
if (!nodeFiles[module].err) {
registry.addModule(nodeFiles[module]);
}
}
}
return loadNodeSetList(nodes);
});
}
async function loadNodeTemplate(node) {
return fs.readFile(node.template,'utf8').then(content => {
var types = [];
var regExp = /<script (?:[^>]*)data-template-name\s*=\s*['"]([^'"]*)['"]/gi;
var match = null;
while ((match = regExp.exec(content)) !== null) {
types.push(match[1]);
}
node.types = types;
var langRegExp = /^<script[^>]* data-lang\s*=\s*['"](.+?)['"]/i;
regExp = /(<script[^>]* data-help-name=[\s\S]*?<\/script>)/gi;
match = null;
var mainContent = "";
var helpContent = {};
var index = 0;
while ((match = regExp.exec(content)) !== null) {
mainContent += content.substring(index,regExp.lastIndex-match[1].length);
index = regExp.lastIndex;
var help = content.substring(regExp.lastIndex-match[1].length,regExp.lastIndex);
var lang = i18n.defaultLang;
if ((match = langRegExp.exec(help)) !== null) {
lang = match[1];
}
if (!helpContent.hasOwnProperty(lang)) {
helpContent[lang] = "";
}
helpContent[lang] += help;
}
mainContent += content.substring(index);
node.config = mainContent;
node.help = helpContent;
// TODO: parse out the javascript portion of the template
//node.script = "";
for (var i=0;i<node.types.length;i++) {
if (registry.getTypeId(node.types[i])) {
node.err = node.types[i]+" already registered";
break;
}
}
return node
}).catch(err => {
node.types = [];
if (err.code === 'ENOENT') {
if (!node.types) {
node.types = [];
}
node.err = "Error: "+node.template+" does not exist";
} else {
node.types = [];
node.err = err.toString();
}
return node;
});
}
async function loadNodeLocales(node) {
if (node.module === 'node-red') {
// do not look up locales directory for core nodes
node.namespace = node.module;
return node
}
return fs.stat(path.join(path.dirname(node.file),"locales")).then(stat => {
node.namespace = node.id;
return i18n.registerMessageCatalog(node.id,
path.join(path.dirname(node.file),"locales"),
path.basename(node.file,".js")+".json")
.then(() => node);
}).catch(err => {
node.namespace = node.module;
return node;
});
}
async function loadNodeConfig(fileInfo) {
var file = fileInfo.file;
var module = fileInfo.module;
var name = fileInfo.name;
var version = fileInfo.version;
var id = module + "/" + name;
var info = registry.getNodeInfo(id);
var isEnabled = true;
if (info) {
if (info.hasOwnProperty("loaded")) {
throw new Error(file+" already loaded");
}
isEnabled = info.enabled;
}
var node = {
id: id,
module: module,
name: name,
file: file,
template: file.replace(/\.js$/,".html"),
enabled: isEnabled,
loaded:false,
version: version,
local: fileInfo.local,
types: [],
config: "",
help: {}
};
if (fileInfo.hasOwnProperty("types")) {
node.types = fileInfo.types;
}
await loadNodeLocales(node)
if (!settings.disableEditor) {
return loadNodeTemplate(node);
}
return node;
}
/**
* Loads the specified node into the runtime
* @param node a node info object - see loadNodeConfig
* @return a promise that resolves to an update node info object. The object
* has the following properties added:
* err: any error encountered whilst loading the node
*
*/
function loadNodeSet(node) {
var nodeDir = path.dirname(node.file);
var nodeFn = path.basename(node.file);
if (!node.enabled) {
return Promise.resolve(node);
} else {
}
try {
var loadPromise = null;
var r = require(node.file);
if (typeof r === "function") {
var red = registryUtil.createNodeApi(node);
var promise = r(red);
if (promise != null && typeof promise.then === "function") {
loadPromise = promise.then(function() {
node.enabled = true;
node.loaded = true;
return node;
}).catch(function(err) {
node.err = err;
return node;
});
}
}
if (loadPromise == null) {
node.enabled = true;
node.loaded = true;
loadPromise = Promise.resolve(node);
}
return loadPromise;
} catch(err) {
node.err = err;
var stack = err.stack;
var message;
if (stack) {
var i = stack.indexOf(node.file);
if (i > -1) {
var excerpt = stack.substring(i+node.file.length+1,i+node.file.length+20);
var m = /^(\d+):(\d+)/.exec(excerpt);
if (m) {
node.err = err+" (line:"+m[1]+")";
}
}
}
return Promise.resolve(node);
}
}
function loadNodeSetList(nodes) {
var promises = [];
nodes.forEach(function(node) {
if (!node.err) {
promises.push(loadNodeSet(node).catch(err => {}));
} else {
promises.push(node);
}
});
return Promise.all(promises).then(function() {
if (settings.available()) {
return registry.saveNodeList();
} else {
return;
}
});
}
function addModule(module) {
if (!settings.available()) {
throw new Error("Settings unavailable");
}
var nodes = [];
if (registry.getModuleInfo(module)) {
// TODO: nls
var e = new Error("module_already_loaded");
e.code = "module_already_loaded";
return Promise.reject(e);
}
try {
var moduleFiles = localfilesystem.getModuleFiles(module);
return loadNodeFiles(moduleFiles);
} catch(err) {
return Promise.reject(err);
}
}
function loadNodeHelp(node,lang) {
var base = path.basename(node.template);
var localePath;
if (node.module === 'node-red') {
var cat_dir = path.dirname(node.template);
var cat = path.basename(cat_dir);
var dir = path.dirname(cat_dir);
localePath = path.join(dir, "..", "locales", lang, cat, base)
}
else {
var dir = path.dirname(node.template);
localePath = path.join(dir,"locales",lang,base);
}
try {
// TODO: make this async
var content = fs.readFileSync(localePath, "utf8")
return content;
} catch(err) {
return null;
}
}
function getNodeHelp(node,lang) {
if (!node.help[lang]) {
var help = loadNodeHelp(node,lang);
if (help == null) {
var langParts = lang.split("-");
if (langParts.length == 2) {
help = loadNodeHelp(node,langParts[0]);
}
}
if (help) {
node.help[lang] = help;
} else if (lang === i18n.defaultLang) {
return null;
} else {
node.help[lang] = getNodeHelp(node, i18n.defaultLang);
}
}
return node.help[lang];
}
module.exports = {
init: init,
load: load,
addModule: addModule,
loadNodeSet: loadNodeSet,
getNodeHelp: getNodeHelp
}