1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Merge branch 'dev' into debug-node-with-jsonata

This commit is contained in:
Hiroyasu Nishiyama 2018-12-08 17:43:29 +09:00
commit 987dbf8a92
55 changed files with 751 additions and 1095 deletions

View File

@ -1,3 +1,97 @@
#### 0.20.0-beta.2: Beta Release
- Split Node-RED internals into multiple sub-modules
Editor
- Allow the editor to use a custom admin api url root
- Improve performance of Flow Diff dialog - @TothiViseo #1989
- Add 'open project' option to Projects Welcome dialog
- Add 'type already registered' check in palette editor
- Handle missing tab.disabled property
- Handle missing wires prop and string x/y props on import
- Add RED.notifications.hide flag - for UI testing
- Improve alignment of node label edit inputs
- Show arrow-in node when invalid font-awesome icon name was specified for default icon
- Add ability to delete context values from sidebar
- Allow copy-to-clipboard copy whole tabs
- Make disabled flows more obvious in editor
- Allow import/export from file in editor
- Allow config nodes to be selected in sidebar and deleted
- Show port label of subflow with input port
- Support ctrl-click selection of flow tabs
- Allow left-hand node button to act as toggle
- Support dbl-click in tab bar to add new flow in position
- Fix duplicate subflow detection on import
- Add import notification with info on what has been imported Closes #1862
- Show error details when trying to import invalid json
- Show default icon when non-existent font-awesome icon was specified
- Add configurable option for showing node label
- Avoid http redirects as Safari doesn't reuse Auth header Fixes #1903
- Tidy up ace tooltip styling
- Add event log to editor
- Add tooltips to multiple editor elements
- Allow palette to be hidden
- Add node module into to sidebar and palette popover
- Mark all newly imported nodes as changed
- Allow a node label to be hidden
- Add markdown formatting toolbar
- Add markdown toolbar to various editors
- Fix i18n handling for ja-JP locale on Safari/MacOS
- Add node body tooltip
- Decrease opacity of flow-navigator
- Update tooltip style
- Update ACE to 1.4.1-src-min-noconflict
- Cache node locales by language
- Show icon element with either icon image or fa-icon
- Added font-awesome icons to user defined icon
- Update info side bar with node description section
- One-click search of config node users
- Redesign node edit dialog to tabbed style
- Add 'restart flows' option to deploy menu
- Add node description property UI
Runtime
- Allow a project to be loaded from cmdline
- Handle lookup of undefined property in Global context Fixes #1978
- Refuse to enable Manage Palette if npm too old
- Remove restriction on upgrading non-local modules
- Remove deprecated Buffer constructor usage Fixes #1709
- Update httpServerOptions doc in settings.js
- Exclude non-testable .js files from the unit tests
- Add --safe mode flag to allow starting without flows running
- Add setting-defined accessToken for automated access to the adminAPI - #1789
Nodes
- Move all core node EN help to their own locale files - #1990
- CSV: better regex for number detection
- Debug: hide button if not configured to send to sidebar
- Delay: report queue activity when in by-topic mode
- Delay: add msg.flush mode
- Exec: Preserve existing properties on msg object
- File: remove CR/LF from incoming filename
- Function: create custom ace javascript mode to handle ES6 Fixes #1911
- Function: add env.get
- HTTP Request: Add http-proxy config #1913
- HTTP Request: add msg.redirectList to output
- HTTP Request: add msg.requestTimeout option for per-message setting - @natcl #1959
- MQTT: add auto-detect and base64 output to mqtt node Fixes #1912 - @DurandA
- MQTT: only unsubscribe node that is being removed
- Sentiment: move to node-red-node-sentiment
- Switch: add missing edit dialog icon
- Tail: move to node-red-node-tail
- TCPGet: clear status if user changes target per message
- Template: tidy up edit dialog
- UDP: more resilient binding to correct port for udp, give input side priority
- Split/Join: add msg.reset to info panel
- Split/Join: reset join without sending part array
- Watch: add msg.filename so can feed direct to file in node
- WebSocket: preserve \_session on msg but don't send as part of wholemsg
#### 0.19.5: Maintenance Release #### 0.19.5: Maintenance Release
- Recognize pip installs of RPi.GPIO (#1934) - Recognize pip installs of RPi.GPIO (#1934)

View File

@ -373,6 +373,10 @@ module.exports = function(grunt) {
src: 'CHANGELOG.md', src: 'CHANGELOG.md',
dest: 'packages/node_modules/@node-red/editor-client/public/red/about' dest: 'packages/node_modules/@node-red/editor-client/public/red/about'
}, },
{
src: 'CHANGELOG.md',
dest: 'packages/node_modules/node-red/'
},
{ {
cwd: 'packages/node_modules/@node-red/editor-client/src/ace/bin/', cwd: 'packages/node_modules/@node-red/editor-client/src/ace/bin/',
src: '**', src: '**',
@ -429,23 +433,18 @@ module.exports = function(grunt) {
} }
}, },
jsdoc : { jsdoc : {
runtimeAPI: { modules: {
src: [ src: [
'packages/node_modules/node-red/lib/red.js', 'packages/node_modules/node-red/lib/red.js',
'packages/node_modules/@node-red/runtime/lib/index.js', 'packages/node_modules/@node-red/runtime/lib/index.js',
'packages/node_modules/@node-red/runtime/lib/api/*.js', 'packages/node_modules/@node-red/runtime/lib/api/*.js',
'packages/node_modules/@node-red/runtime/lib/events.js',
'packages/node_modules/@node-red/util/**/*.js',
], ],
options: { options: {
destination: 'docs', destination: 'docs',
configure: './jsdoc.json' configure: './jsdoc.json'
} }
},
nodeREDUtil: {
src: 'packages/node_modules/@node-red/util/**/*.js',
options: {
destination: 'packages/node_modules/@node-red/util/docs',
configure: './jsdoc.json'
}
} }
}, },
jsdoc2md: { jsdoc2md: {
@ -453,8 +452,11 @@ module.exports = function(grunt) {
options: { options: {
separators: true separators: true
}, },
src: ['packages/node_modules/@node-red/runtime/lib/index.js', src: [
'packages/node_modules/@node-red/runtime/lib/api/*.js'], 'packages/node_modules/@node-red/runtime/lib/index.js',
'packages/node_modules/@node-red/runtime/lib/api/*.js',
'packages/node_modules/@node-red/runtime/lib/events.js'
],
dest: 'packages/node_modules/@node-red/runtime/docs/api.md' dest: 'packages/node_modules/@node-red/runtime/docs/api.md'
}, },
nodeREDUtil: { nodeREDUtil: {
@ -528,12 +530,15 @@ module.exports = function(grunt) {
}); });
grunt.registerTask('verifyPackageDependencies', function() { grunt.registerTask('verifyPackageDependencies', function() {
var done = this.async();
var verifyDependencies = require("./scripts/verify-package-dependencies.js"); var verifyDependencies = require("./scripts/verify-package-dependencies.js");
var failures = verifyDependencies(); verifyDependencies().then(function(failures) {
if (failures.length > 0) { if (failures.length > 0) {
failures.forEach(f => grunt.log.error(f)); failures.forEach(f => grunt.log.error(f));
grunt.fail.fatal("Failed to verify package dependencies"); grunt.fail.fatal("Failed to verify package dependencies");
} }
done();
});
}); });
grunt.registerTask('setDevEnv', grunt.registerTask('setDevEnv',

View File

@ -1,6 +1,6 @@
{ {
"name": "node-red", "name": "node-red",
"version": "0.20.0-alpha.0", "version": "0.20.0-beta.2",
"description": "A visual tool for wiring the Internet of Things", "description": "A visual tool for wiring the Internet of Things",
"homepage": "http://nodered.org", "homepage": "http://nodered.org",
"license": "Apache-2.0", "license": "Apache-2.0",
@ -24,7 +24,7 @@
} }
], ],
"dependencies": { "dependencies": {
"ajv": "6.5.4", "ajv": "6.6.1",
"basic-auth": "2.0.1", "basic-auth": "2.0.1",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"body-parser": "1.18.3", "body-parser": "1.18.3",
@ -32,29 +32,31 @@
"clone": "2.1.2", "clone": "2.1.2",
"cookie": "0.3.1", "cookie": "0.3.1",
"cookie-parser": "1.4.3", "cookie-parser": "1.4.3",
"cors": "2.8.4", "cors": "2.8.5",
"cron": "1.5.0", "cron": "1.5.1",
"denque": "1.3.0", "denque": "1.4.0",
"express": "4.16.4", "express": "4.16.4",
"express-session": "1.15.6", "express-session": "1.15.6",
"fs-extra": "5.0.0", "fs-extra": "7.0.1",
"fs.notify": "0.0.4", "fs.notify": "0.0.4",
"hash-sum": "1.0.2", "hash-sum": "1.0.2",
"https-proxy-agent": "2.2.1", "https-proxy-agent": "2.2.1",
"i18next": "11.6.0", "i18next": "12.1.0",
"is-utf8": "0.2.1", "is-utf8": "0.2.1",
"js-yaml": "3.12.0", "js-yaml": "3.12.0",
"json-stringify-safe": "5.0.1", "json-stringify-safe": "5.0.1",
"jsonata": "1.5.4", "jsonata": "1.5.4",
"media-typer": "0.3.0", "media-typer": "1.0.1",
"memorystore": "1.6.0", "memorystore": "1.6.0",
"mime": "1.4.1", "mime": "2.4.0",
"mqtt": "2.18.8", "mqtt": "2.18.8",
"multer": "1.4.1", "multer": "1.4.1",
"mustache": "2.3.2", "mustache": "3.0.1",
"node-red-node-email": "0.1.*", "node-red-node-email": "1.0.*",
"node-red-node-feedparser": "^0.1.12", "node-red-node-feedparser": "^0.1.14",
"node-red-node-rbe": "0.2.*", "node-red-node-rbe": "0.2.*",
"node-red-node-sentiment": "^0.1.0",
"node-red-node-tail": "^0.0.1",
"node-red-node-twitter": "^1.1.0", "node-red-node-twitter": "^1.1.0",
"nopt": "4.0.1", "nopt": "4.0.1",
"oauth2orize": "1.11.0", "oauth2orize": "1.11.0",
@ -78,7 +80,7 @@
"chromedriver": "2.43.1", "chromedriver": "2.43.1",
"grunt": "~1.0.3", "grunt": "~1.0.3",
"grunt-chmod": "~1.1.1", "grunt-chmod": "~1.1.1",
"grunt-cli": "~1.3.1", "grunt-cli": "~1.3.2",
"grunt-concurrent": "~2.3.1", "grunt-concurrent": "~2.3.1",
"grunt-contrib-clean": "~1.1.0", "grunt-contrib-clean": "~1.1.0",
"grunt-contrib-compress": "~1.4.0", "grunt-contrib-compress": "~1.4.0",
@ -104,12 +106,12 @@
"mosca": "^2.8.3", "mosca": "^2.8.3",
"should": "^8.4.0", "should": "^8.4.0",
"sinon": "1.17.7", "sinon": "1.17.7",
"stoppable": "^1.0.7", "stoppable": "^1.1.0",
"supertest": "3.3.0", "supertest": "3.3.0",
"wdio-chromedriver-service": "^0.1.3", "wdio-chromedriver-service": "^0.1.5",
"wdio-mocha-framework": "^0.6.2", "wdio-mocha-framework": "^0.6.4",
"wdio-spec-reporter": "^0.1.5", "wdio-spec-reporter": "^0.1.5",
"webdriverio": "^4.13.1", "webdriverio": "^4.14.1",
"node-red-node-test-helper": "node-red/node-red-node-test-helper", "node-red-node-test-helper": "node-red/node-red-node-test-helper",
"jsdoc-nr-template": "node-red/jsdoc-nr-template" "jsdoc-nr-template": "node-red/jsdoc-nr-template"
}, },

View File

@ -57,7 +57,7 @@ module.exports = {
} }
runtimeAPI.nodes.getIcon(opts).then(function(data) { runtimeAPI.nodes.getIcon(opts).then(function(data) {
if (data) { if (data) {
var contentType = mime.lookup(icon); var contentType = mime.getType(icon);
res.set("Content-Type", contentType); res.set("Content-Type", contentType);
res.send(data); res.send(data);
} else { } else {

View File

@ -100,5 +100,5 @@ module.exports = {
auth: { auth: {
needsPermission: auth.needsPermission needsPermission: auth.needsPermission
}, },
get adminApp() { return adminApp; } get httpAdmin() { return adminApp; }
}; };

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/editor-api", "name": "@node-red/editor-api",
"version": "0.20.0-alpha.0", "version": "0.20.0-beta.2",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@ -8,21 +8,25 @@
"url": "https://github.com/node-red/node-red.git" "url": "https://github.com/node-red/node-red.git"
}, },
"contributors": [ "contributors": [
{ "name": "Nick O'Leary" }, {
{ "name": "Dave Conway-Jones"} "name": "Nick O'Leary"
},
{
"name": "Dave Conway-Jones"
}
], ],
"dependencies": { "dependencies": {
"@node-red/util": "*", "@node-red/util": "0.20.0-beta.2",
"@node-red/editor-client": "*", "@node-red/editor-client": "0.20.0-beta.2",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"body-parser": "1.18.3", "body-parser": "1.18.3",
"clone": "2.1.2", "clone": "2.1.2",
"cors": "2.8.4", "cors": "2.8.5",
"express-session": "1.15.6", "express-session": "1.15.6",
"express": "4.16.4", "express": "4.16.4",
"memorystore": "1.6.0", "memorystore": "1.6.0",
"mime": "1.4.1", "mime": "2.4.0",
"mustache": "2.3.2", "mustache": "3.0.1",
"oauth2orize": "1.11.0", "oauth2orize": "1.11.0",
"passport-http-bearer": "1.0.1", "passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2", "passport-oauth2-client-password": "0.1.2",

View File

@ -1,14 +1,18 @@
{ {
"name": "@node-red/editor-client", "name": "@node-red/editor-client",
"version": "0.20.0-alpha.0", "version": "0.20.0-beta.2",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/node-red/node-red.git" "url": "https://github.com/node-red/node-red.git"
}, },
"contributors": [ "contributors": [
{ "name": "Nick O'Leary" }, {
{ "name": "Dave Conway-Jones"} "name": "Nick O'Leary"
},
{
"name": "Dave Conway-Jones"
}
], ],
"main": "./lib/index.js" "main": "./lib/index.js"
} }

View File

@ -865,7 +865,7 @@ RED.editor = (function() {
var inputPlaceholder = node._def.inputLabels?RED._("editor.defaultLabel"):RED._("editor.noDefaultLabel"); var inputPlaceholder = node._def.inputLabels?RED._("editor.defaultLabel"):RED._("editor.noDefaultLabel");
var outputPlaceholder = node._def.outputLabels?RED._("editor.defaultLabel"):RED._("editor.noDefaultLabel"); var outputPlaceholder = node._def.outputLabels?RED._("editor.defaultLabel"):RED._("editor.noDefaultLabel");
$('<div class="form-row"><span style="margin-left: 10px;" data-i18n="editor.labelInputs"></span><div id="node-label-form-inputs"></div></div>').appendTo(dialogForm); $('<div class="form-row"><span style="margin-left: 50px;" data-i18n="editor.labelInputs"></span><div id="node-label-form-inputs"></div></div>').appendTo(dialogForm);
var inputsDiv = $("#node-label-form-inputs"); var inputsDiv = $("#node-label-form-inputs");
if (inputCount > 0) { if (inputCount > 0) {
for (i=0;i<inputCount;i++) { for (i=0;i<inputCount;i++) {
@ -874,7 +874,7 @@ RED.editor = (function() {
} else { } else {
buildLabelRow().appendTo(inputsDiv); buildLabelRow().appendTo(inputsDiv);
} }
$('<div class="form-row"><span style="margin-left: 10px;" data-i18n="editor.labelOutputs"></span><div id="node-label-form-outputs"></div></div>').appendTo(dialogForm); $('<div class="form-row"><span style="margin-left: 50px;" data-i18n="editor.labelOutputs"></span><div id="node-label-form-outputs"></div></div>').appendTo(dialogForm);
var outputsDiv = $("#node-label-form-outputs"); var outputsDiv = $("#node-label-form-outputs");
if (outputCount > 0) { if (outputCount > 0) {
for (i=0;i<outputCount;i++) { for (i=0;i<outputCount;i++) {

View File

@ -16,6 +16,10 @@
RED.notifications = (function() { RED.notifications = (function() {
/* /*
If RED.notifications.hide is set to true, all notifications will be hidden.
This is to help with UI testing in certain cases and not intended for the
end-user.
// Example usage for a modal dialog with buttons // Example usage for a modal dialog with buttons
var myNotification = RED.notify("This is the message to display",{ var myNotification = RED.notify("This is the message to display",{
modal: true, modal: true,
@ -108,7 +112,9 @@ RED.notifications = (function() {
$("#notifications").append(n); $("#notifications").append(n);
if (!RED.notifications.hide) {
$(n).slideDown(300); $(n).slideDown(300);
}
n.close = (function() { n.close = (function() {
var nn = n; var nn = n;
return function() { return function() {
@ -123,9 +129,13 @@ RED.notifications = (function() {
notificationButtonWrapper.hide(); notificationButtonWrapper.hide();
} }
} }
if (!RED.notifications.hide) {
$(nn).slideUp(300, function() { $(nn).slideUp(300, function() {
nn.parentNode.removeChild(nn); nn.parentNode.removeChild(nn);
}); });
} else {
nn.parentNode.removeChild(nn);
}
if (options.modal) { if (options.modal) {
$("#full-shade").hide(); $("#full-shade").hide();
} }
@ -138,8 +148,10 @@ RED.notifications = (function() {
return return
} }
nn.hidden = true; nn.hidden = true;
if (!RED.notifications.hide) {
$(nn).slideUp(300); $(nn).slideUp(300);
} }
}
})(); })();
n.showNotification = (function() { n.showNotification = (function() {
var nn = n; var nn = n;
@ -148,8 +160,10 @@ RED.notifications = (function() {
return return
} }
nn.hidden = false; nn.hidden = false;
if (!RED.notifications.hide) {
$(nn).slideDown(300); $(nn).slideDown(300);
} }
}
})(); })();
n.update = (function() { n.update = (function() {

View File

@ -789,7 +789,11 @@ RED.utils = (function() {
return RED.settings.apiRootUrl+"icons/"+iconPath.module+"/"+iconPath.file; return RED.settings.apiRootUrl+"icons/"+iconPath.module+"/"+iconPath.file;
} }
} else { } else {
if (def.category === 'subflows') { // This could be a non-core node trying to use a core icon.
iconPath.module = 'node-red';
if (isIconExists(iconPath)) {
return RED.settings.apiRootUrl+"icons/"+iconPath.module+"/"+iconPath.file;
} else if (def.category === 'subflows') {
return RED.settings.apiRootUrl+"icons/node-red/subflow.png"; return RED.settings.apiRootUrl+"icons/node-red/subflow.png";
} else { } else {
return RED.settings.apiRootUrl+"icons/node-red/arrow-in.png"; return RED.settings.apiRootUrl+"icons/node-red/arrow-in.png";

View File

@ -343,7 +343,7 @@
top: -3000px; top: -3000px;
} }
.node-label-form-row { .node-label-form-row {
margin: 5px 0; margin: 5px 0 0 50px;
label { label {
margin-right: 20px; margin-right: 20px;
text-align: right; text-align: right;

View File

@ -1,37 +0,0 @@
<script type="text/x-red" data-template-name="sentiment">
<div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="node-red:common.label.property"></span></label>
<input type="text" id="node-input-property" style="width:70%;"/>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
<script type="text/javascript">
RED.nodes.registerType('sentiment',{
category: 'analysis-function',
color:"#E6E0F8",
defaults: {
name: {value:""},
property: {value:"payload",required:true}
},
inputs:1,
outputs:1,
icon: "arrow-in.png",
label: function() {
return this.name||"sentiment";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
if (this.property === undefined) {
$("#node-input-property").val("payload");
}
$("#node-input-property").typedInput({default:'msg',types:['msg']});
}
});
</script>

View File

@ -1,23 +0,0 @@
module.exports = function(RED) {
"use strict";
var sentiment = require('sentiment');
function SentimentNode(n) {
RED.nodes.createNode(this,n);
this.property = n.property||"payload";
var node = this;
this.on("input", function(msg) {
var value = RED.util.getMessageProperty(msg,node.property);
if (value !== undefined) {
sentiment(value, msg.overrides || null, function (err, result) {
msg.sentiment = result;
node.send(msg);
});
}
else { node.send(msg); } // If no matching property - just pass it on.
});
}
RED.nodes.registerType("sentiment",SentimentNode);
}

View File

@ -2,10 +2,10 @@
<script type="text/x-red" data-template-name="template"> <script type="text/x-red" data-template-name="template">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"> <div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></div>
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-field"><i class="fa fa-edit"></i> <span data-i18n="template.label.property"></span></label> <label for="node-input-field"><i class="fa fa-ellipsis-h"></i> <span data-i18n="template.label.property"></span></label>
<input type="text" id="node-input-field" placeholder="payload" style="width:250px;"> <input type="text" id="node-input-field" placeholder="payload" style="width:250px;">
<input type="hidden" id="node-input-fieldType"> <input type="hidden" id="node-input-fieldType">
</div> </div>

View File

@ -131,8 +131,16 @@ module.exports = function(RED) {
if (msg.hasOwnProperty('followRedirects')) { if (msg.hasOwnProperty('followRedirects')) {
opts.followRedirect = msg.followRedirects; opts.followRedirect = msg.followRedirects;
} }
var redirectList = [];
if (!opts.hasOwnProperty('followRedirect') || opts.followRedirect) { if (!opts.hasOwnProperty('followRedirect') || opts.followRedirect) {
opts.followRedirect = function(res) { opts.followRedirect = function(res) {
var redirectInfo = {
location: res.headers.location,
};
if (res.headers.hasOwnProperty('set-cookie')) {
redirectInfo.cookies = extractCookies(res.headers['set-cookie']);
}
redirectList.push(redirectInfo);
if (this.headers.cookie) { if (this.headers.cookie) {
delete this.headers.cookie; delete this.headers.cookie;
} }
@ -256,17 +264,10 @@ module.exports = function(RED) {
msg.headers = res.headers; msg.headers = res.headers;
msg.responseUrl = res.request.uri.href; msg.responseUrl = res.request.uri.href;
msg.payload = body; msg.payload = body;
msg.redirectList = redirectList;
if (msg.headers.hasOwnProperty('set-cookie')) { if (msg.headers.hasOwnProperty('set-cookie')) {
msg.responseCookies = {}; msg.responseCookies = extractCookies(msg.headers['set-cookie']);
msg.headers['set-cookie'].forEach(function(c) {
var parsedCookie = cookie.parse(c);
var eq_idx = c.indexOf('=');
var key = c.substr(0, eq_idx).trim()
parsedCookie.value = parsedCookie[key];
delete parsedCookie[key];
msg.responseCookies[key] = parsedCookie;
});
} }
msg.headers['x-node-red-request-node'] = hashSum(msg.headers); msg.headers['x-node-red-request-node'] = hashSum(msg.headers);
// msg.url = url; // revert when warning above finally removed // msg.url = url; // revert when warning above finally removed
@ -299,6 +300,19 @@ module.exports = function(RED) {
this.on("close",function() { this.on("close",function() {
node.status({}); node.status({});
}); });
function extractCookies(setCookie) {
var cookies = {};
setCookie.forEach(function(c) {
var parsedCookie = cookie.parse(c);
var eq_idx = c.indexOf('=');
var key = c.substr(0, eq_idx).trim()
parsedCookie.value = parsedCookie[key];
delete parsedCookie[key];
cookies[key] = parsedCookie;
});
return cookies;
}
} }
RED.nodes.registerType("http request",HTTPRequest,{ RED.nodes.registerType("http request",HTTPRequest,{

View File

@ -20,7 +20,7 @@
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-row"> <div class="form-row">
<label data-i18n="switch.label.property"></label> <label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="switch.label.property"></span></label>
<input type="text" id="node-input-property" style="width: 70%"/> <input type="text" id="node-input-property" style="width: 70%"/>
<input type="hidden" id="node-input-outputs"/> <input type="hidden" id="node-input-outputs"/>
</div> </div>

View File

@ -1,51 +0,0 @@
<script type="text/x-red" data-template-name="tail">
<div class="form-row">
<label for="node-input-filename"><i class="fa fa-file"></i> <span data-i18n="tail.label.filename"></span></label>
<input id="node-input-filename" type="text">
</div>
<div class="form-row">
<label for="node-input-filetype"><i class="fa fa-file-text-o"></i> <span data-i18n="tail.label.type"></span></label>
<select type="text" id="node-input-filetype">
<option value="text" data-i18n="tail.action.text"></option>
<option value="binary" data-i18n="tail.action.binary"></option>
</select>
</div>
<div class="form-row" id="node-tail-split">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-split" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-split" style="width: 70%;"><span data-i18n="tail.label.splitlines"></span></label>
</div>
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div>
</script>
<script type="text/javascript">
RED.nodes.registerType('tail',{
category: 'storage-input',
defaults: {
name: {value:""},
filetype: {value:"text"},
split: {value:false},
filename: {value:"",required:true}
},
color:"BurlyWood",
inputs:0,
outputs:1,
icon: "file.png",
label: function() {
return this.name||this.filename||this._("tail.tail");
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
$("#node-input-filetype").on("change",function() {
if (this.value === "text") { $("#node-tail-split").show(); }
else { $("#node-tail-split").hide(); }
});
}
});
</script>

View File

@ -1,75 +0,0 @@
/**
* 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.
**/
module.exports = function(RED) {
"use strict";
var spawn = require('child_process').spawn;
var plat = require('os').platform();
if (plat.match(/^win/)) {
throw RED._("tail.errors.windowsnotsupport");
}
function TailNode(n) {
RED.nodes.createNode(this,n);
this.filename = n.filename;
this.filetype = n.filetype || "text";
this.split = n.split || false;
var node = this;
var err = "";
// TODO: rewrite to use node-tail
var tail = spawn("tail", ["-F", "-n", "0", this.filename]);
tail.stdout.on("data", function (data) {
var msg = { topic:node.filename };
if (node.filetype === "text") {
if (node.split) {
// TODO: allow customisation of the line break - as we do elsewhere
var strings = data.toString().split("\n");
for (var s in strings) {
//TODO: should we really filter blanks? Is that expected?
if (strings[s] !== "") {
node.send({
topic: node.filename,
payload: strings[s]
});
}
}
}
else {
msg.payload = data.toString();
node.send(msg);
}
}
else {
msg.payload = data;
node.send(msg);
}
});
tail.stderr.on("data", function(data) {
node.error(data.toString());
});
this.on("close", function() {
/* istanbul ignore else */
if (tail) { tail.kill(); }
});
}
RED.nodes.registerType("tail",TailNode);
}

View File

@ -1,35 +0,0 @@
<!--
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.
-->
<script type="text/x-red" data-help-name="sentiment">
<p>Analyses the chosen property, default <code>payload</code>, and adds a <code>sentiment</code> object.</p>
<h3>Outputs</h3>
<dl class="message-properties">
<dt>sentiment <span class="property-type">object</span></dt>
<dd>contains the resulting AFINN-111 sentiment.</dd>
<dt>sentiment.score <span class="property-type">number</span></dt>
<dd>the sentiment score.</dd>
</dl>
<h3>Inputs</h3>
<dl class="message-properties">
<dt>overrides <span class="property-type">object</span></dt>
<dd>an object of word score overrides can be supplied - <code>{ word:score,... }</code>.</dd>
</dl>
<h3>Details</h3>
<p>A score greater than zero is positive and less than zero is negative.</p>
<p>The score typically ranges from -5 to +5, but can go higher and lower.</p>
<p>See <a href="https://github.com/thisandagain/sentiment/blob/master/README.md" target="_blank">the Sentiment docs here</a>.</p>
</script>

View File

@ -53,6 +53,8 @@
Otherwise, the url of the original request.</dd> Otherwise, the url of the original request.</dd>
<dt>responseCookies <span class="property-type">object</span></dt> <dt>responseCookies <span class="property-type">object</span></dt>
<dd>If the response includes cookies, this propery is an object of name/value pairs for each cookie.</dd> <dd>If the response includes cookies, this propery is an object of name/value pairs for each cookie.</dd>
<dt>redirectList <span class="property-type">array</span></dt>
<dd>If the request was redirected one or more times, the accumulated information will be added to this property. `location` is the next redirect destination. `cookies` is the cookies returned from the redirect source.</dd>
</dl> </dl>
<h3>Details</h3> <h3>Details</h3>
<p>When configured within the node, the URL property can contain <a href="http://mustache.github.io/mustache.5.html" target="_blank">mustache-style</a> tags. These allow the <p>When configured within the node, the URL property can contain <a href="http://mustache.github.io/mustache.5.html" target="_blank">mustache-style</a> tags. These allow the

View File

@ -206,7 +206,7 @@
"template": "template", "template": "template",
"label": { "label": {
"template": "Template", "template": "Template",
"property": "Set property", "property": "Property",
"format": "Syntax Highlight", "format": "Syntax Highlight",
"syntax": "Format", "syntax": "Format",
"output": "Output as", "output": "Output as",
@ -827,21 +827,6 @@
"pythoncommandnotfound": "nrpgio python command not running" "pythoncommandnotfound": "nrpgio python command not running"
} }
}, },
"tail": {
"tail": "tail",
"label": {
"filename": "Filename",
"type": "File type",
"splitlines": "Split lines on \\n?"
},
"action": {
"text": "Text - returns String",
"binary": "Binary - returns Buffer"
},
"errors": {
"windowsnotsupport": "Not currently supported on Windows."
}
},
"file": { "file": {
"label": { "label": {
"filename": "Filename", "filename": "Filename",

View File

@ -1,25 +0,0 @@
<!--
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.
-->
<script type="text/x-red" data-help-name="tail">
<p>Tails (watches for things to be added) to the configured file. (Linux/Mac ONLY)</p>
<p>This will not work on Windows filesystems, as it relies on the <b>tail -F</b> command.</p>
<h3>Outputs</h3>
<ul>
<li>Text (UTF-8) files will be returned as strings.</li>
<li>Binary files will be returned as Buffer objects.</li>
</ul>
</script>

View File

@ -1,35 +0,0 @@
<!--
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.
-->
<script type="text/x-red" data-help-name="sentiment">
<p>指定したプロパティ(デフォルトは<code>payload</code>)を分析し、<code>sentiment</code>オブジェクトを追加します。</p>
<h3>出力</h3>
<dl class="message-properties">
<dt>sentiment <span class="property-type">オブジェクト</span></dt>
<dd>AFINN-111による感情分析の結果</dd>
<dt>sentiment.score <span class="property-type">数値</span></dt>
<dd>感情分析スコア</dd>
</dl>
<h3>入力</h3>
<dl class="message-properties">
<dt>overrides <span class="property-type">オブジェクト</span></dt>
<dd>単語スコアの上書きをするためのオブジェクト - <code>{ word:score,... }</code></dd>
</dl>
<h3>詳細</h3>
<p>ゼロ以上のスコアはポジティブ、ゼロ以下はネガティブを意味します。</p>
<p>スコアの範囲は通常-5から+5ですが、より大きかったり小さかったりすることもあります。</p>
<p>詳細は<a href="https://github.com/thisandagain/sentiment/blob/master/README.md" target="_blank">the Sentiment docs here</a>を参照してください。</p>
</script>

View File

@ -46,6 +46,8 @@
<dd>リクエストの処理時にリダイレクトが発生した場合、このプロパティが最後にリダイレクトされたURLを表します。リダイレクトが起こらなかった場合、最初リクエストのURLを表します。</dd> <dd>リクエストの処理時にリダイレクトが発生した場合、このプロパティが最後にリダイレクトされたURLを表します。リダイレクトが起こらなかった場合、最初リクエストのURLを表します。</dd>
<dt>responseCookies <span class="property-type">オブジェクト</span></dt> <dt>responseCookies <span class="property-type">オブジェクト</span></dt>
<dd>レスポンスがクッキーを含む場合、このプロパティは各クッキーの名前/値を含むオブジェクトを表します。</dd> <dd>レスポンスがクッキーを含む場合、このプロパティは各クッキーの名前/値を含むオブジェクトを表します。</dd>
<dt>redirectList <span class="property-type">配列</span></dt>
<dd>リクエストが一回以上リダイレクトされた場合は、このプロパティに情報が蓄積されます。`location`は、リダイレクト先を示します。`cookies`は、リダイレクト元から返されたクッキー情報です。</dd>
</dl> </dl>
<h3>詳細</h3> <h3>詳細</h3>
<p>ードの設定でurlプロパティを指定する場合、<a href="http://mustache.github.io/mustache.5.html" target="_blank">mustache形式</a>のタグを含めることができます。これにより、URLを入力メッセージの値から構成することができます。例えば、urlが<code>example.com/{{{topic}}}</code>の場合、<code>msg.topic</code>の値による置き換えを自動的に行います。{{{...}}}表記を使うと、/、&といった文字をmustacheがエスケープするのを抑止できます。</p> <p>ードの設定でurlプロパティを指定する場合、<a href="http://mustache.github.io/mustache.5.html" target="_blank">mustache形式</a>のタグを含めることができます。これにより、URLを入力メッセージの値から構成することができます。例えば、urlが<code>example.com/{{{topic}}}</code>の場合、<code>msg.topic</code>の値による置き換えを自動的に行います。{{{...}}}表記を使うと、/、&といった文字をmustacheがエスケープするのを抑止できます。</p>

View File

@ -206,7 +206,7 @@
"template": "template", "template": "template",
"label": { "label": {
"template": "テンプレート", "template": "テンプレート",
"property": "設定先", "property": "プロパティ",
"format": "構文", "format": "構文",
"syntax": "形式", "syntax": "形式",
"output": "出力形式", "output": "出力形式",
@ -825,21 +825,6 @@
"pythoncommandnotfound": "nrpgio python コマンドが実行されていません" "pythoncommandnotfound": "nrpgio python コマンドが実行されていません"
} }
}, },
"tail": {
"tail": "tail",
"label": {
"filename": "ファイル名",
"type": "ファイル形式",
"splitlines": "改行でメッセージを分割"
},
"action": {
"text": "文字列",
"binary": "バイナリバッファ"
},
"errors": {
"windowsnotsupport": "現在Windows上での動作は対応していません"
}
},
"file": { "file": {
"label": { "label": {
"filename": "ファイル名", "filename": "ファイル名",

View File

@ -1,25 +0,0 @@
<!--
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.
-->
<script type="text/x-red" data-help-name="tail">
<p>設定したファイルの末尾を出力(追加されたデータを監視)します。(Linux/Macのみ)</p>
<p>このノードは<b>tail -F</b>コマンドを内部で利用しているため、Windowsファイルシステムでは動作しません。</p>
<h3>出力</h3>
<ul>
<li>(UTF-8形式の)テキストファイルは文字列を返却。</li>
<li>バイナルファイルはバッファオブジェクトを返却。</li>
</ul>
</script>

View File

@ -196,7 +196,7 @@
"template": { "template": {
"label": { "label": {
"template": "模版", "template": "模版",
"property": "设定属性", "property": "属性",
"format": "语法高亮", "format": "语法高亮",
"syntax": "格式", "syntax": "格式",
"output": "输出为", "output": "输出为",
@ -784,20 +784,6 @@
"pythoncommandnotfound": "nrpgio python命令未处于运行状态" "pythoncommandnotfound": "nrpgio python命令未处于运行状态"
} }
}, },
"tail": {
"label": {
"filename": "文件名",
"type": "文件类型",
"splitlines": "以\\n来拆分行?"
},
"action": {
"text": "文本 - 返回字符串",
"binary": "二进制 - 返回Buffer"
},
"errors": {
"windowsnotsupport": "Windows目前不支持."
}
},
"file": { "file": {
"label": { "label": {
"filename": "文件名", "filename": "文件名",

View File

@ -1,34 +1,38 @@
{ {
"name": "@node-red/nodes", "name": "@node-red/nodes",
"version": "0.20.0-alpha.0", "version": "0.20.0-beta.2",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/node-red/node-red.git" "url": "https://github.com/node-red/node-red.git"
}, },
"contributors": [ "contributors": [
{ "name": "Nick O'Leary" }, {
{ "name": "Dave Conway-Jones"} "name": "Nick O'Leary"
},
{
"name": "Dave Conway-Jones"
}
], ],
"dependencies": { "dependencies": {
"ajv": "6.5.4", "ajv": "6.6.1",
"body-parser": "1.18.3", "body-parser": "1.18.3",
"cheerio": "0.22.0", "cheerio": "0.22.0",
"cookie-parser": "1.4.3", "cookie-parser": "1.4.3",
"cookie": "0.3.1", "cookie": "0.3.1",
"cors": "2.8.4", "cors": "2.8.5",
"cron": "1.5.0", "cron": "1.5.1",
"denque": "1.3.0", "denque": "1.4.0",
"fs-extra": "5.0.0", "fs-extra": "7.0.1",
"fs.notify": "0.0.4", "fs.notify": "0.0.4",
"hash-sum": "1.0.2", "hash-sum": "1.0.2",
"https-proxy-agent": "2.2.1", "https-proxy-agent": "2.2.1",
"is-utf8": "0.2.1", "is-utf8": "0.2.1",
"js-yaml": "3.12.0", "js-yaml": "3.12.0",
"media-typer": "0.3.0", "media-typer": "1.0.1",
"mqtt": "2.18.8", "mqtt": "2.18.8",
"multer": "1.4.1", "multer": "1.4.1",
"mustache": "2.3.2", "mustache": "3.0.1",
"on-headers": "1.0.1", "on-headers": "1.0.1",
"raw-body": "2.3.3", "raw-body": "2.3.3",
"request": "2.88.0", "request": "2.88.0",

View File

@ -14,6 +14,13 @@
* limitations under the License. * limitations under the License.
**/ **/
/*
* This provides a list of node types that have at one time been included with
* the core of Node-RED but have since moved out to their own module.
*
* If a user has a flow that depends on one of these types and they do not have
* the new module installed, this will help them identify the missing module.
*/
var nodes = { var nodes = {
"irc in": {module:"node-red-node-irc"}, "irc in": {module:"node-red-node-irc"},
"irc out": {module:"node-red-node-irc"}, "irc out": {module:"node-red-node-irc"},
@ -39,7 +46,11 @@ var nodes = {
"e-mail": {module:"node-red-node-email"}, "e-mail": {module:"node-red-node-email"},
"e-mail in": {module:"node-red-node-email"}, "e-mail in": {module:"node-red-node-email"},
"feedparse": {module:"node-red-node-feedparser"} "feedparse": {module:"node-red-node-feedparser"},
"sentiment": {module:"node-red-node-sentiment"},
"tail": {module:"node-red-node-tail"}
} }
module.exports = { module.exports = {

View File

@ -235,13 +235,18 @@ function checkPrereq() {
return Promise.resolve(); return Promise.resolve();
} else { } else {
return new Promise(resolve => { return new Promise(resolve => {
child_process.execFile(npmCommand,['-v'],function(err) { child_process.execFile(npmCommand,['-v'],function(err,stdout) {
if (err) { if (err) {
log.info(log._("server.palette-editor.npm-not-found")); log.info(log._("server.palette-editor.npm-not-found"));
paletteEditorEnabled = false; paletteEditorEnabled = false;
} else {
if (parseInt(stdout.split(".")[0]) < 3) {
log.info(log._("server.palette-editor.npm-too-old"));
paletteEditorEnabled = false;
} else { } else {
paletteEditorEnabled = true; paletteEditorEnabled = true;
} }
}
resolve(); resolve();
}); });
}) })

View File

@ -21,8 +21,8 @@ var semver = require("semver");
var localfilesystem = require("./localfilesystem"); var localfilesystem = require("./localfilesystem");
var registry = require("./registry"); var registry = require("./registry");
var registryUtil = require("./util")
var i18n = require("@node-red/util").i18n; // TODO: separate module var i18n = require("@node-red/util").i18n;
var settings; var settings;
var runtime; var runtime;
@ -31,6 +31,7 @@ function init(_runtime) {
runtime = _runtime; runtime = _runtime;
settings = runtime.settings; settings = runtime.settings;
localfilesystem.init(runtime); localfilesystem.init(runtime);
registryUtil.init(runtime);
} }
function load(defaultNodesDir,disableNodePathScan) { function load(defaultNodesDir,disableNodePathScan) {
@ -44,92 +45,6 @@ function load(defaultNodesDir,disableNodePathScan) {
return loadNodeFiles(nodeFiles); return loadNodeFiles(nodeFiles);
} }
function copyObjectProperties(src,dst,copyList,blockList) {
if (!src) {
return;
}
if (copyList && !blockList) {
copyList.forEach(function(i) {
if (src.hasOwnProperty(i)) {
var propDescriptor = Object.getOwnPropertyDescriptor(src,i);
Object.defineProperty(dst,i,propDescriptor);
}
});
} else if (!copyList && blockList) {
for (var i in src) {
if (src.hasOwnProperty(i) && blockList.indexOf(i) === -1) {
var propDescriptor = Object.getOwnPropertyDescriptor(src,i);
Object.defineProperty(dst,i,propDescriptor);
}
}
}
}
function requireModule(name) {
var moduleInfo = registry.getModuleInfo(name);
if (moduleInfo && moduleInfo.path) {
var relPath = path.relative(__dirname, moduleInfo.path);
return require(relPath);
} else {
var err = new Error(`Cannot find module '${name}'`);
err.code = "MODULE_NOT_FOUND";
throw err;
}
}
function createNodeApi(node) {
var red = {
nodes: {},
log: {},
settings: {},
events: runtime.events,
util: runtime.util,
version: runtime.version,
require: requireModule,
comms: {
publish: function(topic,data,retain) {
runtime.events.emit("comms",{
topic: topic,
data: data,
retain: retain
})
}
},
library: {
register: function(type) {
return runtime.library.register(node.id,type);
}
},
httpNode: runtime.nodeApp,
server: runtime.server
}
copyObjectProperties(runtime.nodes,red.nodes,["createNode","getNode","eachNode","addCredentials","getCredentials","deleteCredentials" ]);
red.nodes.registerType = function(type,constructor,opts) {
runtime.nodes.registerType(node.id,type,constructor,opts);
}
copyObjectProperties(runtime.log,red.log,null,["init"]);
copyObjectProperties(runtime.settings,red.settings,null,["init","load","reset"]);
if (runtime.adminApi) {
red.auth = runtime.adminApi.auth;
red.httpAdmin = runtime.adminApi.adminApp;
} else {
//TODO: runtime.adminApi is always stubbed if not enabled, so this block
// is unused - but may be needed for the unit tests
red.auth = {
needsPermission: function() {}
};
// TODO: stub out httpAdmin/httpNode/server
}
red["_"] = function() {
var args = Array.prototype.slice.call(arguments, 0);
if (args[0].indexOf(":") === -1) {
args[0] = node.namespace+":"+args[0];
}
return i18n._.apply(null,args);
}
return red;
}
function loadNodeFiles(nodeFiles) { function loadNodeFiles(nodeFiles) {
var promises = []; var promises = [];
var nodes = []; var nodes = [];
@ -332,7 +247,7 @@ function loadNodeSet(node) {
var r = require(node.file); var r = require(node.file);
if (typeof r === "function") { if (typeof r === "function") {
var red = createNodeApi(node); var red = registryUtil.createNodeApi(node);
var promise = r(red); var promise = r(red);
if (promise != null && typeof promise.then === "function") { if (promise != null && typeof promise.then === "function") {
loadPromise = promise.then(function() { loadPromise = promise.then(function() {

View File

@ -0,0 +1,110 @@
/**
* 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 i18n = require("@node-red/util").i18n;
var runtime;
function copyObjectProperties(src,dst,copyList,blockList) {
if (!src) {
return;
}
if (copyList && !blockList) {
copyList.forEach(function(i) {
if (src.hasOwnProperty(i)) {
var propDescriptor = Object.getOwnPropertyDescriptor(src,i);
Object.defineProperty(dst,i,propDescriptor);
}
});
} else if (!copyList && blockList) {
for (var i in src) {
if (src.hasOwnProperty(i) && blockList.indexOf(i) === -1) {
var propDescriptor = Object.getOwnPropertyDescriptor(src,i);
Object.defineProperty(dst,i,propDescriptor);
}
}
}
}
function requireModule(name) {
var moduleInfo = registry.getModuleInfo(name);
if (moduleInfo && moduleInfo.path) {
var relPath = path.relative(__dirname, moduleInfo.path);
return require(relPath);
} else {
var err = new Error(`Cannot find module '${name}'`);
err.code = "MODULE_NOT_FOUND";
throw err;
}
}
function createNodeApi(node) {
var red = {
nodes: {},
log: {},
settings: {},
events: runtime.events,
util: runtime.util,
version: runtime.version,
require: requireModule,
comms: {
publish: function(topic,data,retain) {
runtime.events.emit("comms",{
topic: topic,
data: data,
retain: retain
})
}
},
library: {
register: function(type) {
return runtime.library.register(node.id,type);
}
},
httpNode: runtime.nodeApp,
httpAdmin: runtime.adminApp,
server: runtime.server
}
copyObjectProperties(runtime.nodes,red.nodes,["createNode","getNode","eachNode","addCredentials","getCredentials","deleteCredentials" ]);
red.nodes.registerType = function(type,constructor,opts) {
runtime.nodes.registerType(node.id,type,constructor,opts);
}
copyObjectProperties(runtime.log,red.log,null,["init"]);
copyObjectProperties(runtime.settings,red.settings,null,["init","load","reset"]);
if (runtime.adminApi) {
red.auth = runtime.adminApi.auth;
} else {
//TODO: runtime.adminApi is always stubbed if not enabled, so this block
// is unused - but may be needed for the unit tests
red.auth = {
needsPermission: function(v) { return function(req,res,next) {next()} }
};
// TODO: stub out httpAdmin/httpNode/server
}
red["_"] = function() {
var args = Array.prototype.slice.call(arguments, 0);
if (args[0].indexOf(":") === -1) {
args[0] = node.namespace+":"+args[0];
}
return i18n._.apply(null,args);
}
return red;
}
module.exports = {
init: function(_runtime) {
runtime = _runtime;
},
createNodeApi: createNodeApi
}

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/registry", "name": "@node-red/registry",
"version": "0.20.0-alpha.0", "version": "0.20.0-beta.2",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@ -8,11 +8,15 @@
"url": "https://github.com/node-red/node-red.git" "url": "https://github.com/node-red/node-red.git"
}, },
"contributors": [ "contributors": [
{ "name": "Nick O'Leary" }, {
{ "name": "Dave Conway-Jones"} "name": "Nick O'Leary"
},
{
"name": "Dave Conway-Jones"
}
], ],
"dependencies": { "dependencies": {
"@node-red/util": "*", "@node-red/util": "0.20.0-beta.2",
"semver": "5.6.0", "semver": "5.6.0",
"uglify-js": "3.4.9", "uglify-js": "3.4.9",
"when": "3.7.8" "when": "3.7.8"

View File

@ -1,4 +1,4 @@
/** /*!
* Copyright JS Foundation and other contributors, http://js.foundation * Copyright JS Foundation and other contributors, http://js.foundation
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -17,3 +17,27 @@
var events = require("events"); var events = require("events");
module.exports = new events.EventEmitter(); module.exports = new events.EventEmitter();
/**
* Runtime events emitter
* @mixin @node-red/runtime_events
*/
/**
* Register an event listener for a runtime event
* @name on
* @function
* @memberof @node-red/runtime_events
* @param {String} eventName - the name of the event to listen to
* @param {Function} listener - the callback function for the event
*/
/**
* Emit an event to all of its registered listeners
* @name emit
* @function
* @memberof @node-red/runtime_events
* @param {String} eventName - the name of the event to emit
* @param {any} ...args - the arguments to pass in the event
* @return {Boolean} - whether the event had listeners or not
*/

View File

@ -53,6 +53,7 @@ var adminApi = {
} }
var nodeApp; var nodeApp;
var adminApp;
var server; var server;
@ -64,12 +65,13 @@ var server;
* better abstracted. * better abstracted.
* @memberof @node-red/runtime * @memberof @node-red/runtime
*/ */
function init(userSettings,httpServer,_adminApi) { function init(userSettings,httpServer,_adminApi,__util) {
server = httpServer; server = httpServer;
userSettings.version = getVersion(); userSettings.version = getVersion();
settings.init(userSettings); settings.init(userSettings);
nodeApp = express(); nodeApp = express();
adminApp = express();
if (_adminApi) { if (_adminApi) {
adminApi = _adminApi; adminApi = _adminApi;
@ -78,6 +80,13 @@ function init(userSettings,httpServer,_adminApi) {
library.init(runtime); library.init(runtime);
externalAPI.init(runtime); externalAPI.init(runtime);
exec.init(runtime); exec.init(runtime);
if (__util) {
log = __util.log;
i18n = __util.i18n;
} else {
log = redUtil.log;
i18n = redUtil.i18n;
}
} }
var version; var version;
@ -103,7 +112,6 @@ function getVersion() {
* @memberof @node-red/runtime * @memberof @node-red/runtime
*/ */
function start() { function start() {
return i18n.registerMessageCatalog("runtime",path.resolve(path.join(__dirname,"..","locales")),"runtime.json") return i18n.registerMessageCatalog("runtime",path.resolve(path.join(__dirname,"..","locales")),"runtime.json")
.then(function() { return storage.init(runtime)}) .then(function() { return storage.init(runtime)})
.then(function() { return settings.load(storage)}) .then(function() { return settings.load(storage)})
@ -269,6 +277,7 @@ var runtime = {
exec: exec, exec: exec,
util: require("@node-red/util").util, util: require("@node-red/util").util,
get adminApi() { return adminApi }, get adminApi() { return adminApi },
get adminApp() { return adminApp },
get nodeApp() { return nodeApp }, get nodeApp() { return nodeApp },
get server() { return server }, get server() { return server },
isStarted: function() { isStarted: function() {
@ -346,8 +355,12 @@ module.exports = {
storage: storage, storage: storage,
events: events, events: events,
util: require("@node-red/util").util,
get httpNode() { return nodeApp }, get httpNode() { return nodeApp },
get server() { return server } get httpAdmin() { return adminApp },
get server() { return server },
"_": runtime
} }

View File

@ -13,7 +13,8 @@
"loading": "Loading palette nodes", "loading": "Loading palette nodes",
"palette-editor": { "palette-editor": {
"disabled": "Palette editor disabled : user settings", "disabled": "Palette editor disabled : user settings",
"npm-not-found": "Palette editor disabled : npm command not found" "npm-not-found": "Palette editor disabled : npm command not found",
"npm-too-old": "Palette editor disabled : npm version too old. Requires npm >= 3.x"
}, },
"errors": "Failed to register __count__ node type", "errors": "Failed to register __count__ node type",
"errors_plural": "Failed to register __count__ node types", "errors_plural": "Failed to register __count__ node types",

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/runtime", "name": "@node-red/runtime",
"version": "0.20.0-alpha.0", "version": "0.20.0-beta.2",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@ -8,15 +8,19 @@
"url": "https://github.com/node-red/node-red.git" "url": "https://github.com/node-red/node-red.git"
}, },
"contributors": [ "contributors": [
{ "name": "Nick O'Leary" }, {
{ "name": "Dave Conway-Jones"} "name": "Nick O'Leary"
},
{
"name": "Dave Conway-Jones"
}
], ],
"dependencies": { "dependencies": {
"@node-red/registry": "*", "@node-red/registry": "0.20.0-beta.2",
"@node-red/util": "*", "@node-red/util": "0.20.0-beta.2",
"clone": "2.1.2", "clone": "2.1.2",
"express": "4.16.4", "express": "4.16.4",
"fs-extra": "5.0.0", "fs-extra": "7.0.1",
"json-stringify-safe": "5.0.1", "json-stringify-safe": "5.0.1",
"when": "3.7.8" "when": "3.7.8"
} }

View File

@ -1,4 +1,4 @@
/** /*!
* Copyright JS Foundation and other contributors, http://js.foundation * Copyright JS Foundation and other contributors, http://js.foundation
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -14,17 +14,20 @@
* limitations under the License. * limitations under the License.
**/ **/
/**
* @module @node-red/util
*/
const log = require("./lib/log"); const log = require("./lib/log");
const i18n = require("./lib/i18n"); const i18n = require("./lib/i18n");
const util = require("./lib/util"); const util = require("./lib/util");
/**
* This module provides common utilities for the Node-RED runtime and editor
*
* @namespace @node-red/util
*/
module.exports = { module.exports = {
/** /**
* Initialise the module with the runtime settings * Initialise the module with the runtime settings
* @param {Object} settings * @param {Object} settings
* @memberof @node-red/util
*/ */
init: function(settings) { init: function(settings) {
log.init(settings); log.init(settings);
@ -33,19 +36,22 @@ module.exports = {
/** /**
* Logging utilities * Logging utilities
* @see module:@node-red/util.module:log * @mixes @node-red/util_log
* @memberof @node-red/util
*/ */
log: log, log: log,
/** /**
* Internationalization utilities * Internationalization utilities
* @see module:@node-red/util.module:i18n * @mixes @node-red/util_i18n
* @memberof @node-red/util
*/ */
i18n: i18n, i18n: i18n,
/** /**
* General utilities * General utilities
* @see module:@node-red/util.module:util * @mixes @node-red/util_util
* @memberof @node-red/util
*/ */
util: util, util: util,
} }

View File

@ -16,8 +16,8 @@
**/ **/
/** /**
* @module i18n * Internationalization utilities
* @memberof module:@node-red/util * @mixin @node-red/util_i18n
*/ */
var i18n = require("i18next"); var i18n = require("i18next");
@ -34,7 +34,7 @@ var initPromise;
/** /**
* Register multiple message catalogs with i18n. * Register multiple message catalogs with i18n.
* @memberof module:@node-red/util.module:i18n * @memberof @node-red/util_i18n
*/ */
function registerMessageCatalogs(catalogs) { function registerMessageCatalogs(catalogs) {
var promises = catalogs.map(function(catalog) { var promises = catalogs.map(function(catalog) {
@ -45,7 +45,7 @@ function registerMessageCatalogs(catalogs) {
/** /**
* Register a message catalog with i18n. * Register a message catalog with i18n.
* @memberof module:@node-red/util.module:i18n * @memberof @node-red/util_i18n
*/ */
function registerMessageCatalog(namespace,dir,file) { function registerMessageCatalog(namespace,dir,file) {
return initPromise.then(function() { return initPromise.then(function() {
@ -146,7 +146,7 @@ function init() {
* Gets a message catalog. * Gets a message catalog.
* @name catalog * @name catalog
* @function * @function
* @memberof module:@node-red/util.module:i18n * @memberof @node-red/util_i18n
*/ */
function getCatalog(namespace,lang) { function getCatalog(namespace,lang) {
var result = null; var result = null;
@ -182,7 +182,7 @@ var obj = module.exports = {
* Perform a message catalog lookup. * Perform a message catalog lookup.
* @name _ * @name _
* @function * @function
* @memberof module:@node-red/util.module:i18n * @memberof @node-red/util_i18n
*/ */
obj['_'] = function() { obj['_'] = function() {
//var opts = {}; //var opts = {};

View File

@ -16,8 +16,8 @@
**/ **/
/** /**
* @module log * Logging utilities
* @memberof module:@node-red/util * @mixin @node-red/util_log
*/ */
var util = require("util"); var util = require("util");
@ -128,14 +128,16 @@ var log = module.exports = {
}, },
/** /**
* Add a log handler function. * Add a log handler function
* @memberof @node-red/util_log
*/ */
addHandler: function(func) { addHandler: function(func) {
logHandlers.push(func); logHandlers.push(func);
}, },
/** /**
* Remove a log handler function. * Remove a log handler function
* @memberof @node-red/util_log
*/ */
removeHandler: function(func) { removeHandler: function(func) {
var index = logHandlers.indexOf(func); var index = logHandlers.indexOf(func);
@ -145,7 +147,8 @@ var log = module.exports = {
}, },
/** /**
* Log a message object. * Log a message object
* @memberof @node-red/util_log
*/ */
log: function(msg) { log: function(msg) {
msg.timestamp = Date.now(); msg.timestamp = Date.now();
@ -155,42 +158,48 @@ var log = module.exports = {
}, },
/** /**
* Log a message at INFO level. * Log a message at INFO level
* @memberof @node-red/util_log
*/ */
info: function(msg) { info: function(msg) {
log.log({level:log.INFO,msg:msg}); log.log({level:log.INFO,msg:msg});
}, },
/** /**
* Log a message at WARN level. * Log a message at WARN level
* @memberof @node-red/util_log
*/ */
warn: function(msg) { warn: function(msg) {
log.log({level:log.WARN,msg:msg}); log.log({level:log.WARN,msg:msg});
}, },
/** /**
* Log a message at ERROR level. * Log a message at ERROR level
* @memberof @node-red/util_log
*/ */
error: function(msg) { error: function(msg) {
log.log({level:log.ERROR,msg:msg}); log.log({level:log.ERROR,msg:msg});
}, },
/** /**
* Log a message at TRACE level. * Log a message at TRACE level
* @memberof @node-red/util_log
*/ */
trace: function(msg) { trace: function(msg) {
log.log({level:log.TRACE,msg:msg}); log.log({level:log.TRACE,msg:msg});
}, },
/** /**
* Log a message at DEBUG level. * Log a message at DEBUG level
* @memberof @node-red/util_log
*/ */
debug: function(msg) { debug: function(msg) {
log.log({level:log.DEBUG,msg:msg}); log.log({level:log.DEBUG,msg:msg});
}, },
/** /**
* Log a metric event. * Check if metrics are enabled
* @memberof @node-red/util_log
*/ */
metric: function() { metric: function() {
return metricsEnabled; return metricsEnabled;
@ -198,6 +207,7 @@ var log = module.exports = {
/** /**
* Log an audit event. * Log an audit event.
* @memberof @node-red/util_log
*/ */
audit: function(msg,req) { audit: function(msg,req) {
msg.level = log.AUDIT; msg.level = log.AUDIT;
@ -214,6 +224,6 @@ var log = module.exports = {
* Perform a message catalog lookup. * Perform a message catalog lookup.
* @name _ * @name _
* @function * @function
* @memberof module:@node-red/util.module:log * @memberof @node-red/util_log
*/ */
log["_"] = i18n._; log["_"] = i18n._;

View File

@ -16,8 +16,7 @@
**/ **/
/** /**
* @module util * @mixin @node-red/util_util
* @memberof module:@node-red/util
*/ */
@ -29,7 +28,7 @@ const util = require("util");
/** /**
* Generates a psuedo-unique-random id. * Generates a psuedo-unique-random id.
* @return {String} a random-ish id * @return {String} a random-ish id
* @memberof module:@node-red/util.module:util * @memberof @node-red/util_util
*/ */
function generateId() { function generateId() {
return (1+Math.random()*4294967295).toString(16); return (1+Math.random()*4294967295).toString(16);
@ -41,7 +40,7 @@ function generateId() {
* *
* @param {any} o - the property to convert to a String * @param {any} o - the property to convert to a String
* @return {String} the stringified version * @return {String} the stringified version
* @memberof module:@node-red/util.module:util * @memberof @node-red/util_util
*/ */
function ensureString(o) { function ensureString(o) {
if (Buffer.isBuffer(o)) { if (Buffer.isBuffer(o)) {
@ -60,7 +59,7 @@ function ensureString(o) {
* *
* @param {any} o - the property to convert to a Buffer * @param {any} o - the property to convert to a Buffer
* @return {String} the Buffer version * @return {String} the Buffer version
* @memberof module:@node-red/util.module:util * @memberof @node-red/util_util
*/ */
function ensureBuffer(o) { function ensureBuffer(o) {
if (Buffer.isBuffer(o)) { if (Buffer.isBuffer(o)) {
@ -79,7 +78,7 @@ function ensureBuffer(o) {
* *
* @param {any} msg - the message object to clone * @param {any} msg - the message object to clone
* @return {Object} the cloned message * @return {Object} the cloned message
* @memberof module:@node-red/util.module:util * @memberof @node-red/util_util
*/ */
function cloneMessage(msg) { function cloneMessage(msg) {
// Temporary fix for #97 // Temporary fix for #97
@ -106,7 +105,7 @@ function cloneMessage(msg) {
* @param {any} obj1 * @param {any} obj1
* @param {any} obj2 * @param {any} obj2
* @return {boolean} whether the two objects are the same * @return {boolean} whether the two objects are the same
* @memberof module:@node-red/util.module:util * @memberof @node-red/util_util
*/ */
function compareObjects(obj1,obj2) { function compareObjects(obj1,obj2) {
var i; var i;
@ -189,7 +188,7 @@ function createError(code, message) {
* *
* @param {String} str - the property expression * @param {String} str - the property expression
* @return {Array} the normalised expression * @return {Array} the normalised expression
* @memberof module:@node-red/util.module:util * @memberof @node-red/util_util
*/ */
function normalisePropertyExpression(str) { function normalisePropertyExpression(str) {
// This must be kept in sync with validatePropertyExpression // This must be kept in sync with validatePropertyExpression
@ -304,7 +303,7 @@ function normalisePropertyExpression(str) {
* @param {Object} msg - the message object * @param {Object} msg - the message object
* @param {String} str - the property expression * @param {String} str - the property expression
* @return {any} the message property, or undefined if it does not exist * @return {any} the message property, or undefined if it does not exist
* @memberof module:@node-red/util.module:util * @memberof @node-red/util_util
*/ */
function getMessageProperty(msg,expr) { function getMessageProperty(msg,expr) {
if (expr.indexOf('msg.')===0) { if (expr.indexOf('msg.')===0) {
@ -319,7 +318,7 @@ function getMessageProperty(msg,expr) {
* @param {Object} msg - the object * @param {Object} msg - the object
* @param {String} str - the property expression * @param {String} str - the property expression
* @return {any} the object property, or undefined if it does not exist * @return {any} the object property, or undefined if it does not exist
* @memberof module:@node-red/util.module:util * @memberof @node-red/util_util
*/ */
function getObjectProperty(msg,expr) { function getObjectProperty(msg,expr) {
var result = null; var result = null;
@ -342,7 +341,7 @@ function getObjectProperty(msg,expr) {
* @param {String} prop - the property expression * @param {String} prop - the property expression
* @param {any} value - the value to set * @param {any} value - the value to set
* @param {boolean} createMissing - whether to create missing parent properties * @param {boolean} createMissing - whether to create missing parent properties
* @memberof module:@node-red/util.module:util * @memberof @node-red/util_util
*/ */
function setMessageProperty(msg,prop,value,createMissing) { function setMessageProperty(msg,prop,value,createMissing) {
if (prop.indexOf('msg.')===0) { if (prop.indexOf('msg.')===0) {
@ -358,7 +357,7 @@ function setMessageProperty(msg,prop,value,createMissing) {
* @param {String} prop - the property expression * @param {String} prop - the property expression
* @param {any} value - the value to set * @param {any} value - the value to set
* @param {boolean} createMissing - whether to create missing parent properties * @param {boolean} createMissing - whether to create missing parent properties
* @memberof module:@node-red/util.module:util * @memberof @node-red/util_util
*/ */
function setObjectProperty(msg,prop,value,createMissing) { function setObjectProperty(msg,prop,value,createMissing) {
if (typeof createMissing === 'undefined') { if (typeof createMissing === 'undefined') {
@ -422,7 +421,7 @@ function setObjectProperty(msg,prop,value,createMissing) {
* will return `Hello Joe!`. * will return `Hello Joe!`.
* @param {String} value - the string to parse * @param {String} value - the string to parse
* @return {String} The parsed string * @return {String} The parsed string
* @memberof module:@node-red/util.module:util * @memberof @node-red/util_util
*/ */
function evaluateEnvProperty(value) { function evaluateEnvProperty(value) {
if (/^\${[^}]+}$/.test(value)) { if (/^\${[^}]+}$/.test(value)) {
@ -450,7 +449,7 @@ function evaluateEnvProperty(value) {
* *
* @param {String} value - the context property string to parse * @param {String} value - the context property string to parse
* @return {Object} The parsed property * @return {Object} The parsed property
* @memberof module:@node-red/util.module:util * @memberof @node-red/util_util
*/ */
function parseContextStore(key) { function parseContextStore(key) {
var parts = {}; var parts = {};
@ -474,7 +473,7 @@ function parseContextStore(key) {
* @param {Object} msg - the message object to evaluate against * @param {Object} msg - the message object to evaluate against
* @param {Function} callback - (optional) called when the property is evaluated * @param {Function} callback - (optional) called when the property is evaluated
* @return {any} The evaluted property, if no `callback` is provided * @return {any} The evaluted property, if no `callback` is provided
* @memberof module:@node-red/util.module:util * @memberof @node-red/util_util
*/ */
function evaluateNodeProperty(value, type, node, msg, callback) { function evaluateNodeProperty(value, type, node, msg, callback) {
var result = value; var result = value;
@ -531,7 +530,7 @@ function evaluateNodeProperty(value, type, node, msg, callback) {
* @param {String} value - the JSONata expression * @param {String} value - the JSONata expression
* @param {Node} node - the node evaluating the property * @param {Node} node - the node evaluating the property
* @return {Object} The JSONata expression that can be evaluated * @return {Object} The JSONata expression that can be evaluated
* @memberof module:@node-red/util.module:util * @memberof @node-red/util_util
*/ */
function prepareJSONataExpression(value,node) { function prepareJSONataExpression(value,node) {
var expr = jsonata(value); var expr = jsonata(value);
@ -559,7 +558,7 @@ function prepareJSONataExpression(value,node) {
* @param {Object} msg - the message object to evaluate against * @param {Object} msg - the message object to evaluate against
* @param {Function} callback - (optional) called when the expression is evaluated * @param {Function} callback - (optional) called when the expression is evaluated
* @return {any} If no callback was provided, the result of the expression * @return {any} If no callback was provided, the result of the expression
* @memberof module:@node-red/util.module:util * @memberof @node-red/util_util
*/ */
function evaluateJSONataExpression(expr,msg,callback) { function evaluateJSONataExpression(expr,msg,callback) {
var context = msg; var context = msg;
@ -604,7 +603,7 @@ function evaluateJSONataExpression(expr,msg,callback) {
* *
* @param {String} name - the node type * @param {String} name - the node type
* @return {String} The normalised name * @return {String} The normalised name
* @memberof module:@node-red/util.module:util * @memberof @node-red/util_util
*/ */
function normaliseNodeTypeName(name) { function normaliseNodeTypeName(name) {
var result = name.replace(/[^a-zA-Z0-9]/g, " "); var result = name.replace(/[^a-zA-Z0-9]/g, " ");
@ -628,7 +627,7 @@ function normaliseNodeTypeName(name) {
* @param {Object} msg * @param {Object} msg
* @param {Object} opts * @param {Object} opts
* @return {Object} the encoded object * @return {Object} the encoded object
* @memberof module:@node-red/util.module:util * @memberof @node-red/util_util
*/ */
function encodeObject(msg,opts) { function encodeObject(msg,opts) {
var debuglength = 1000; var debuglength = 1000;

View File

@ -1,18 +1,22 @@
{ {
"name": "@node-red/util", "name": "@node-red/util",
"version": "0.20.0-alpha.0", "version": "0.20.0-beta.2",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/node-red/node-red.git" "url": "https://github.com/node-red/node-red.git"
}, },
"contributors": [ "contributors": [
{ "name": "Nick O'Leary" }, {
{ "name": "Dave Conway-Jones"} "name": "Nick O'Leary"
},
{
"name": "Dave Conway-Jones"
}
], ],
"dependencies": { "dependencies": {
"clone": "2.1.2", "clone": "2.1.2",
"i18next": "11.6.0", "i18next": "12.1.0",
"json-stringify-safe": "5.0.1", "json-stringify-safe": "5.0.1",
"jsonata": "1.5.4", "jsonata": "1.5.4",
"when": "3.7.8" "when": "3.7.8"

View File

@ -0,0 +1 @@
CHANGELOG.md

View File

@ -9,11 +9,6 @@ A visual tool for wiring the Internet of Things.
![Node-RED: A visual tool for wiring the Internet of Things](http://nodered.org/images/node-red-screenshot.png) ![Node-RED: A visual tool for wiring the Internet of Things](http://nodered.org/images/node-red-screenshot.png)
### Repository Structure
This is a [monorepo](https://en.wikipedia.org/wiki/Monorepo) containing the source
code for all of the Node-RED component modules.
## Quick Start ## Quick Start
Check out http://nodered.org/docs/getting-started/ for full instructions on getting Check out http://nodered.org/docs/getting-started/ for full instructions on getting
@ -31,24 +26,7 @@ For further help, or general discussion, please use the [Node-RED Forum](https:/
## Developers ## Developers
If you want to run the latest code from git, here's how to get started: The main Node-RED modules are maintained as a monorepo on [GitHub](https://github.com/node-red/node-red).
1. Clone the code:
git clone https://github.com/node-red/node-red.git
cd node-red
2. Install the node-red dependencies
npm install
3. Build the code
npm run build
4. Run
npm start
## Contributing ## Contributing

View File

@ -63,8 +63,13 @@ module.exports = {
} }
redUtil.init(userSettings); redUtil.init(userSettings);
if (userSettings.httpAdminRoot !== false) { if (userSettings.httpAdminRoot !== false) {
// Initialise the runtime
runtime.init(userSettings,httpServer,api); runtime.init(userSettings,httpServer,api);
// Initialise the editor-api
api.init(userSettings,httpServer,runtime.storage,runtime); api.init(userSettings,httpServer,runtime.storage,runtime);
// Attach the runtime admin app to the api admin app
api.httpAdmin.use(runtime.httpAdmin);
apiEnabled = true; apiEnabled = true;
server = httpServer; server = httpServer;
} else { } else {
@ -103,19 +108,46 @@ module.exports = {
}) })
}, },
/**
* Logging utilities
* @see @node-red/util_log
* @memberof node-red
*/
log: redUtil.log, log: redUtil.log,
/**
* General utilities
* @see @node-red/util_util
* @memberof node-red
*/
util: redUtil.util, util: redUtil.util,
get nodes() { console.log("Deprecated use of RED.nodes - refer to API documentation on RED.runtime.nodes"); return runtime._.nodes }, get nodes() { return runtime._.nodes },
get settings() { console.log("Deprecated use of RED.settings - refer to API documentation on RED.runtime.settings"); return runtime._.settings },
get version() { console.log("Deprecated use of RED.version - refer to API documentation on RED.runtime.version"); return runtime._.version }, /**
get events() { console.log("Deprecated use of RED.events - refer to API documentation on RED.runtime.events"); return runtime.events }, * Runtime events emitter
* @see @node-red/runtime_events
* @memberof node-red
*/
events: runtime.events,
get settings() { return runtime._.settings },
/**
* Get the version of the runtime
* @return {String} - the runtime version
* @function
* @memberof node-red
*/
get version() { return runtime._.version },
/** /**
* The express application for the Editor Admin API * The express application for the Editor Admin API
* @memberof node-red * @memberof node-red
*/ */
get httpAdmin() { return api.adminApp }, get httpAdmin() { return api.httpAdmin },
/** /**
* The express application for HTTP Nodes * The express application for HTTP Nodes

View File

@ -1,6 +1,6 @@
{ {
"name": "node-red", "name": "node-red",
"version": "0.20.0-alpha.0", "version": "0.20.0-beta.2",
"description": "A visual tool for wiring the Internet of Things", "description": "A visual tool for wiring the Internet of Things",
"homepage": "http://nodered.org", "homepage": "http://nodered.org",
"license": "Apache-2.0", "license": "Apache-2.0",
@ -17,8 +17,12 @@
"node-red-pi": "bin/node-red-pi" "node-red-pi": "bin/node-red-pi"
}, },
"contributors": [ "contributors": [
{ "name": "Nick O'Leary" }, {
{ "name": "Dave Conway-Jones"} "name": "Nick O'Leary"
},
{
"name": "Dave Conway-Jones"
}
], ],
"keywords": [ "keywords": [
"editor", "editor",
@ -27,17 +31,19 @@
"flow" "flow"
], ],
"dependencies": { "dependencies": {
"@node-red/editor-api": "0.20.0-alpha.0", "@node-red/editor-api": "0.20.0-beta.2",
"@node-red/runtime": "0.20.0-alpha.0", "@node-red/runtime": "0.20.0-beta.2",
"@node-red/util": "0.20.0-alpha.0", "@node-red/util": "0.20.0-beta.2",
"@node-red/nodes": "0.20.0-alpha.0", "@node-red/nodes": "0.20.0-beta.2",
"basic-auth": "2.0.1", "basic-auth": "2.0.1",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"express": "4.16.4", "express": "4.16.4",
"fs-extra": "5.0.0", "fs-extra": "7.0.1",
"node-red-node-email": "0.1.*", "node-red-node-email": "1.0.*",
"node-red-node-feedparser": "^0.1.12", "node-red-node-feedparser": "^0.1.14",
"node-red-node-rbe": "0.2.*", "node-red-node-rbe": "0.2.*",
"node-red-node-sentiment": "^0.1.0",
"node-red-node-tail": "^0.0.1",
"node-red-node-twitter": "^1.1.0", "node-red-node-twitter": "^1.1.0",
"nopt": "4.0.1", "nopt": "4.0.1",
"semver": "5.6.0" "semver": "5.6.0"

View File

@ -1,7 +1,7 @@
#!/usr/bin/env node #!/usr/bin/env node
const path = require("path"); const path = require("path");
const fs = require("fs"); const fs = require("fs-extra");
const should = require("should"); const should = require("should");
const rootPackage = require(path.join("..","package.json")); const rootPackage = require(path.join("..","package.json"));
@ -16,8 +16,11 @@ const packages = [
"@node-red/util" "@node-red/util"
]; ];
const fixFlag = process.argv[2] === '--fix';
function verifyDependencies() { function verifyDependencies() {
let failures = []; let failures = [];
let packageUpdates = {};
packages.forEach(package => { packages.forEach(package => {
let modulePackage = require(path.join("../packages/node_modules",package,"package.json")); let modulePackage = require(path.join("../packages/node_modules",package,"package.json"));
let dependencies = Object.keys(modulePackage.dependencies||{}); let dependencies = Object.keys(modulePackage.dependencies||{});
@ -25,22 +28,48 @@ function verifyDependencies() {
try { try {
if (!/^@node-red\//.test(module)) { if (!/^@node-red\//.test(module)) {
should.exist(rootDependencies[module],`[${package}] '${module}' missing from root package.json`); should.exist(rootDependencies[module],`[${package}] '${module}' missing from root package.json`);
try {
rootDependencies[module].should.eql(modulePackage.dependencies[module],`[${package}] '${module}' version mismatch. Expected '${modulePackage.dependencies[module]}' (got '${rootDependencies[module]}') `); rootDependencies[module].should.eql(modulePackage.dependencies[module],`[${package}] '${module}' version mismatch. Expected '${modulePackage.dependencies[module]}' (got '${rootDependencies[module]}') `);
} catch(err) {
if (fixFlag) {
modulePackage.dependencies[module] = rootDependencies[module];
packageUpdates[package] = modulePackage;
} else {
failures.push(err.toString());
}
}
} }
} catch(err) { } catch(err) {
failures.push(err.toString()); failures.push(err.toString());
} }
}); });
}) })
return failures; if (failures.length === 0 && fixFlag) {
var promises = [];
packages.forEach(package => {
if (packageUpdates.hasOwnProperty(package)) {
promises.push(fs.writeJSON(path.join(__dirname,"../packages/node_modules",package,"package.json"),packageUpdates[package],{spaces:4}));
}
});
return Promise.all(promises).then(r => []).catch(e => {
console.log(e);
process.exit(1);
})
} else {
return Promise.resolve(failures);
}
} }
if (require.main === module) { if (require.main === module) {
let failures = verifyDependencies(); verifyDependencies().then(failures => {
if (failures.length > 0) { if (failures.length > 0) {
failures.forEach(f => console.log(` - ${f}`)); failures.forEach(f => console.log(` - ${f}`));
process.exit(1); process.exit(1);
} }
}).catch(e => {
console.log(e);
process.exit(1);
});
} else { } else {
module.exports = verifyDependencies; module.exports = verifyDependencies;
} }

View File

@ -1,178 +0,0 @@
/**
* 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 should = require("should");
var sentimentNode = require("nr-test-utils").require("@node-red/nodes/core/analysis/72-sentiment.js");
var helper = require("node-red-node-test-helper");
describe('sentiment Node', function() {
before(function(done) {
helper.startServer(done);
});
after(function(done) {
helper.stopServer(done);
});
afterEach(function() {
helper.unload();
});
it('should be loaded', function(done) {
var flow = [{id:"sentimentNode1", type:"sentiment", name: "sentimentNode" }];
helper.load(sentimentNode, flow, function() {
var sentimentNode1 = helper.getNode("sentimentNode1");
sentimentNode1.should.have.property('name', 'sentimentNode');
done();
});
});
it('should pass on msg if no payload', function(done) {
var flow = [{id:"jn1",type:"sentiment",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
helper.load(sentimentNode, flow, function() {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
jn2.on("input", function(msg) {
msg.should.not.have.property('sentiment');
msg.topic.should.equal("pass on");
done();
});
var testString = 'good, great, best, brilliant';
jn1.receive({topic:"pass on"});
});
});
it('should add a positive score for good words', function(done) {
var flow = [{id:"jn1",type:"sentiment",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
helper.load(sentimentNode, flow, function() {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
jn2.on("input", function(msg) {
try {
msg.should.have.property('sentiment');
msg.sentiment.should.have.property('score');
msg.sentiment.score.should.be.a.Number();
msg.sentiment.score.should.be.above(10);
done();
} catch(err) {
done(err);
}
});
var testString = 'good, great, best, brilliant';
jn1.receive({payload:testString});
});
});
it('should add a positive score for good words - alternative property', function(done) {
var flow = [{id:"jn1",type:"sentiment",property:"foo",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
helper.load(sentimentNode, flow, function() {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
jn2.on("input", function(msg) {
try {
msg.should.have.property('sentiment');
msg.sentiment.should.have.property('score');
msg.sentiment.score.should.be.a.Number();
msg.sentiment.score.should.be.above(10);
done();
} catch(err) {
done(err);
}
});
var testString = 'good, great, best, brilliant';
jn1.receive({foo:testString});
});
});
it('should add a negative score for bad words', function(done) {
var flow = [{id:"jn1",type:"sentiment",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
helper.load(sentimentNode, flow, function() {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
jn2.on("input", function(msg) {
msg.should.have.property('sentiment');
msg.sentiment.should.have.property('score');
msg.sentiment.score.should.be.a.Number();
msg.sentiment.score.should.be.below(-10);
done();
});
var testString = 'bad, horrible, negative, awful';
jn1.receive({payload:testString});
});
});
it('should add a negative score for bad words - alternative property', function(done) {
var flow = [{id:"jn1",type:"sentiment",property:"foo",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
helper.load(sentimentNode, flow, function() {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
jn2.on("input", function(msg) {
msg.should.have.property('sentiment');
msg.sentiment.should.have.property('score');
msg.sentiment.score.should.be.a.Number();
msg.sentiment.score.should.be.below(-10);
done();
});
var testString = 'bad, horrible, negative, awful';
jn1.receive({foo:testString});
});
});
it('should allow you to override word scoring', function(done) {
var flow = [{id:"jn1",type:"sentiment",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
helper.load(sentimentNode, flow, function() {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
jn2.on("input", function(msg) {
msg.should.have.property('sentiment');
msg.sentiment.should.have.property('score');
msg.sentiment.score.should.be.a.Number();
msg.sentiment.score.should.equal(20);
done();
});
var testString = 'sick, wicked';
var overrides = {'sick': 10, 'wicked': 10 };
jn1.receive({payload:testString,overrides:overrides});
});
});
it('should allow you to override word scoring - alternative property', function(done) {
var flow = [{id:"jn1",type:"sentiment",property:"foo",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
helper.load(sentimentNode, flow, function() {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
jn2.on("input", function(msg) {
msg.should.have.property('sentiment');
msg.sentiment.should.have.property('score');
msg.sentiment.score.should.be.a.Number();
msg.sentiment.score.should.equal(20);
done();
});
var testString = 'sick, wicked';
var overrides = {'sick': 10, 'wicked': 10 };
jn1.receive({foo:testString,overrides:overrides});
});
});
});

View File

@ -503,16 +503,21 @@ describe('inject node', function() {
done(); done();
}); });
}); });
try {
helper.request() helper.request()
.post('/inject/n1') .post('/inject/n1')
.expect(200).end(function(err) { .expect(200).end(function(err) {
if (err) { if (err) {
console.log(err);
return helper.clearFlows() return helper.clearFlows()
.then(function () { .then(function () {
done(err); done(err);
}); });
} }
}); });
} catch(err) {
done(err);
}
}); });
}); });

View File

@ -207,6 +207,12 @@ describe('HTTP Request Node', function() {
res.cookie('redirectToDifferentDomain','different1'); res.cookie('redirectToDifferentDomain','different1');
res.redirect(getDifferentTestURL('/redirectReturn')); res.redirect(getDifferentTestURL('/redirectReturn'));
}); });
testApp.get('/redirectMultipleTimes', function(req, res) {
var key = req.headers.host + req.url;
receivedCookies[key] = req.cookies;
res.cookie('redirectMultipleTimes','multiple1');
res.redirect(getTestURL('/redirectToDifferentDomain'));
});
testApp.get('/redirectReturn', function(req, res) { testApp.get('/redirectReturn', function(req, res) {
var key = req.headers.host + req.url; var key = req.headers.host + req.url;
receivedCookies[key] = req.cookies; receivedCookies[key] = req.cookies;
@ -277,6 +283,7 @@ describe('HTTP Request Node', function() {
msg.should.have.property('headers'); msg.should.have.property('headers');
msg.headers.should.have.property('content-length',''+('hello'.length)); msg.headers.should.have.property('content-length',''+('hello'.length));
msg.headers.should.have.property('content-type').which.startWith('text/html'); msg.headers.should.have.property('content-type').which.startWith('text/html');
msg.redirectList.length.should.equal(0);
done(); done();
} catch(err) { } catch(err) {
done(err); done(err);
@ -1510,6 +1517,10 @@ describe('HTTP Request Node', function() {
done(new Error('Invalid cookie(path:/rediectReurn)')); done(new Error('Invalid cookie(path:/rediectReurn)'));
return; return;
} }
var redirect1 = msg.redirectList[0];
redirect1.location.should.equal('http://localhost:'+testPort+'/redirectReturn');
redirect1.cookies.redirectToSameDomainCookie.Path.should.equal('/');
redirect1.cookies.redirectToSameDomainCookie.value.should.equal('same1');
done(); done();
}); });
n1.receive({}); n1.receive({});
@ -1533,6 +1544,10 @@ describe('HTTP Request Node', function() {
done(new Error('Invalid cookie(path:/rediectReurn)')); done(new Error('Invalid cookie(path:/rediectReurn)'));
return; return;
} }
var redirect1 = msg.redirectList[0];
redirect1.location.should.equal('http://127.0.0.1:'+testPort+'/redirectReturn');
redirect1.cookies.redirectToDifferentDomain.Path.should.equal('/');
redirect1.cookies.redirectToDifferentDomain.value.should.equal('different1');
done(); done();
}); });
n1.receive({}); n1.receive({});
@ -1559,6 +1574,10 @@ describe('HTTP Request Node', function() {
done(new Error('Invalid cookie(path:/rediectReurn)')); done(new Error('Invalid cookie(path:/rediectReurn)'));
return; return;
} }
var redirect1 = msg.redirectList[0];
redirect1.location.should.equal('http://localhost:'+testPort+'/redirectReturn');
redirect1.cookies.redirectToSameDomainCookie.Path.should.equal('/');
redirect1.cookies.redirectToSameDomainCookie.value.should.equal('same1');
done(); done();
}); });
n1.receive({ n1.receive({
@ -1585,6 +1604,10 @@ describe('HTTP Request Node', function() {
done(new Error('Invalid cookie(path:/rediectReurn)')); done(new Error('Invalid cookie(path:/rediectReurn)'));
return; return;
} }
var redirect1 = msg.redirectList[0];
redirect1.location.should.equal('http://127.0.0.1:'+testPort+'/redirectReturn');
redirect1.cookies.redirectToDifferentDomain.Path.should.equal('/');
redirect1.cookies.redirectToDifferentDomain.value.should.equal('different1');
done(); done();
}); });
n1.receive({ n1.receive({
@ -1613,6 +1636,10 @@ describe('HTTP Request Node', function() {
done(new Error('Invalid cookie(path:/rediectReurn)')); done(new Error('Invalid cookie(path:/rediectReurn)'));
return; return;
} }
var redirect1 = msg.redirectList[0];
redirect1.location.should.equal('http://localhost:'+testPort+'/redirectReturn');
redirect1.cookies.redirectToSameDomainCookie.Path.should.equal('/');
redirect1.cookies.redirectToSameDomainCookie.value.should.equal('same1');
done(); done();
}); });
n1.receive({ n1.receive({
@ -1639,6 +1666,33 @@ describe('HTTP Request Node', function() {
done(new Error('Invalid cookie(path:/rediectReurn)')); done(new Error('Invalid cookie(path:/rediectReurn)'));
return; return;
} }
var redirect1 = msg.redirectList[0];
redirect1.location.should.equal('http://127.0.0.1:'+testPort+'/redirectReturn');
redirect1.cookies.redirectToDifferentDomain.Path.should.equal('/');
redirect1.cookies.redirectToDifferentDomain.value.should.equal('different1');
done();
});
n1.receive({
headers: { cookie: 'requestCookie=request1' }
});
});
});
it('should return all redirect information when redirected multiple times', function(done) {
var flow = [{id:'n1',type:'http request',wires:[['n2']],method:'GET',ret:'obj',url:getTestURL('/redirectMultipleTimes')},
{id:"n2", type:"helper"}];
receivedCookies = {};
helper.load(httpRequestNode, flow, function() {
var n1 = helper.getNode("n1");
var n2 = helper.getNode("n2");
n2.on("input", function(msg) {
var redirect1 = msg.redirectList[0];
redirect1.location.should.equal('http://localhost:'+testPort+'/redirectToDifferentDomain');
redirect1.cookies.redirectMultipleTimes.Path.should.equal('/');
redirect1.cookies.redirectMultipleTimes.value.should.equal('multiple1');
var redirect2 = msg.redirectList[1];
redirect2.location.should.equal('http://127.0.0.1:'+testPort+'/redirectReturn');
redirect2.cookies.redirectToDifferentDomain.Path.should.equal('/');
redirect2.cookies.redirectToDifferentDomain.value.should.equal('different1');
done(); done();
}); });
n1.receive({ n1.receive({

View File

@ -1,206 +0,0 @@
/**
* 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 should = require("should");
var path = require('path');
var os = require('os');
var fs = require('fs-extra');
var sinon = require('sinon');
var tailNode = require("nr-test-utils").require("@node-red/nodes/core/storage/28-tail.js");
var helper = require("node-red-node-test-helper");
describe('tail Node', function() {
var wait = 150;
var resourcesDir = path.join(__dirname,"..","..","..","resources");
var fileToTail = path.join(resourcesDir,"28-tail-test-file.txt");
beforeEach(function(done) {
fs.writeFileSync(fileToTail, "Tail message line 1\nTail message line 2\n");
helper.startServer(done);
});
afterEach(function(done) {
helper.unload().then(function() {
fs.unlinkSync(fileToTail);
helper.stopServer(done);
});
});
if (os.type() !== "Windows_NT") {
it('should be loaded', function(done) {
var flow = [{id:"tailNode1", type:"tail", name: "tailNode", "split":true, "filename":fileToTail}];
helper.load(tailNode, flow, function() {
var tailNode1 = helper.getNode("tailNode1");
tailNode1.should.have.property('name', 'tailNode');
done();
});
});
it('should tail a file', function(done) {
var flow = [{id:"tailNode1", type:"tail", name: "tailNode", "split":true, "filename":fileToTail, "wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(tailNode, flow, function() {
var tailNode1 = helper.getNode("tailNode1");
var helperNode1 = helper.getNode("helperNode1");
var inputCounter = 0;
helperNode1.on("input", function(msg) {
//console.log(msg);
msg.should.have.property('topic', fileToTail);
msg.payload.should.equal("Tail message line " + (++inputCounter + 2));
if (inputCounter === 2) {
done();
}
});
setTimeout( function() {
fs.appendFileSync(fileToTail, "Tail message line 3\n");
fs.appendFileSync(fileToTail, "Tail message line 4\n");
},wait);
});
});
it('should work in non-split mode', function(done) {
var flow = [{id:"tailNode1", type:"tail", name: "tailNode", "split":false, "filename":fileToTail, "wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(tailNode, flow, function() {
var tailNode1 = helper.getNode("tailNode1");
var helperNode1 = helper.getNode("helperNode1");
helperNode1.on("input", function(msg) {
//console.log(msg);
msg.should.have.property('topic', fileToTail);
msg.payload.should.equal("Tail message line 5\nTail message line 6\n");
done();
});
setTimeout( function() {
fs.appendFileSync(fileToTail, "Tail message line 5\nTail message line 6\n");
},wait);
});
});
it('should work in binary mode', function(done) {
var flow = [{id:"tailNode1", type:"tail", name: "tailNode", "filetype":"binary", "filename":fileToTail, "wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(tailNode, flow, function() {
var tailNode1 = helper.getNode("tailNode1");
var helperNode1 = helper.getNode("helperNode1");
helperNode1.on("input", function(msg) {
//console.log(msg);
msg.should.have.property('topic', fileToTail);
msg.payload.toString().should.equal("Tail message line 7\nTail message line 8\n");
done();
});
setTimeout( function() {
fs.appendFileSync(fileToTail, "Tail message line 7\nTail message line 8\n");
},wait);
});
});
it('should handle a non-existent file', function(done) {
fs.writeFileSync(fileToTail, "Tail message line.\n");
var flow = [{id:"tailNode1", type:"tail", name: "tailNode", "split":true, "filename":fileToTail, "wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(tailNode, flow, function() {
var tailNode1 = helper.getNode("tailNode1");
var helperNode1 = helper.getNode("helperNode1");
helperNode1.on("input", function(msg) {
msg.should.have.property('topic', fileToTail);
msg.payload.should.equal("Tail message line");
done();
});
setTimeout(function() {
fs.unlinkSync(fileToTail);
},500);
setTimeout( function() {
fs.writeFile(fileToTail, "Tail message line\n");
},1000);
});
});
}
it('should throw an error if run on Windows', function() {
// Stub os platform so we can make it look like windows
var os = require('os');
var spy = sinon.stub(os, 'platform', function(arg) { return("windows"); });
/*jshint immed: false */
try {
(function() { tailNode("1234"); }).should.throw();
} catch (err) {
throw err;
}
finally {
os.platform.restore();
}
});
/*
it('tail should handle file truncation', function(done) {
var flow = [{id:"tailNode1", type:"tail", name: "tailNode", "split":true, "filename":fileToTail, "wires":[["helperNode1"]]},
{id:"helperNode1", type:"helper", wires:[]}];
helper.load(tailNode, flow, function() {
var tailNode1 = helper.getNode("tailNode1");
var helperNode1 = helper.getNode("helperNode1");
var inputCounter = 0;
var warned = false;
tailNode1.on("log", function(msg) {
if (msg.level == "warn") { warned = true; }
});
helperNode1.on("input", function(msg) {
console.log("inputCounter =",inputCounter);
console.log(msg);
msg.should.have.property('topic', fileToTail);
inputCounter++;
if (inputCounter === 1) {
warned.should.be.false();
msg.payload.should.equal("Tail message line append");
} else if (inputCounter === 2) {
msg.payload.should.equal("Tail message line truncate");
} else {
msg.payload.should.equal("Tail message line append "+inputCounter);
}
if (inputCounter === 5) {
setTimeout(function() {
warned.should.be.true();
done();
},100);
}
});
var actions = [
function() { fs.appendFileSync(fileToTail, "Tail message line append\n");},
function() { fs.writeFileSync(fileToTail, "Tail message line truncate\n");},
function() { fs.appendFileSync(fileToTail, "Tail message line append 3\n");},
function() { fs.appendFileSync(fileToTail, "Tail message line append 4\n");},
function() { fs.appendFileSync(fileToTail, "Tail message line append 5\n");}
];
function processAction() {
var action = actions.shift();
action();
if (actions.length > 0) {
setTimeout(function() {
processAction();
},250);
}
}
setTimeout( function() {
processAction();
},wait);
});
});
*/
});

View File

@ -59,8 +59,8 @@ describe("api/index", function() {
afterEach(afterEach); afterEach(afterEach);
it("does not setup admin api if httpAdminRoot is false", function(done) { it("does not setup admin api if httpAdminRoot is false", function(done) {
api.init({},{ httpAdminRoot: false },{},{}); api.init({ httpAdminRoot: false },{},{},{});
should.not.exist(api.adminApp); should.not.exist(api.httpAdmin);
done(); done();
}); });
describe('initalises admin api without adminAuth', function(done) { describe('initalises admin api without adminAuth', function(done) {
@ -70,30 +70,30 @@ describe("api/index", function() {
}); });
after(afterEach); after(afterEach);
it('exposes the editor',function(done) { it('exposes the editor',function(done) {
request(api.adminApp).get("/editor").expect(200).end(done); request(api.httpAdmin).get("/editor").expect(200).end(done);
}) })
it('exposes the admin api',function(done) { it('exposes the admin api',function(done) {
request(api.adminApp).get("/admin").expect(200).end(done); request(api.httpAdmin).get("/admin").expect(200).end(done);
}) })
it('exposes the auth api',function(done) { it('exposes the auth api',function(done) {
request(api.adminApp).get("/auth/login").expect(200).end(done); request(api.httpAdmin).get("/auth/login").expect(200).end(done);
}) })
}); });
describe('initalises admin api without editor', function(done) { describe('initalises admin api without editor', function(done) {
before(function() { before(function() {
beforeEach(); beforeEach();
api.init({},{ disableEditor: true },{},{}); api.init({ disableEditor: true },{},{},{});
}); });
after(afterEach); after(afterEach);
it('does not expose the editor',function(done) { it('does not expose the editor',function(done) {
request(api.adminApp).get("/editor").expect(404).end(done); request(api.httpAdmin).get("/editor").expect(404).end(done);
}) })
it('exposes the admin api',function(done) { it('exposes the admin api',function(done) {
request(api.adminApp).get("/admin").expect(200).end(done); request(api.httpAdmin).get("/admin").expect(200).end(done);
}) })
it('exposes the auth api',function(done) { it('exposes the auth api',function(done) {
request(api.adminApp).get("/auth/login").expect(200).end(done) request(api.httpAdmin).get("/auth/login").expect(200).end(done)
}) })
}); });
}); });

View File

@ -0,0 +1,20 @@
/**
* 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.
**/
describe("red/nodes/registry/util",function() {
it.skip("NEEDS TESTS");
});

View File

@ -25,6 +25,8 @@ var runtime = NR_TEST_UTILS.require("@node-red/runtime");
var redNodes = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes"); var redNodes = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes");
var storage = NR_TEST_UTILS.require("@node-red/runtime/lib/storage"); var storage = NR_TEST_UTILS.require("@node-red/runtime/lib/storage");
var settings = NR_TEST_UTILS.require("@node-red/runtime/lib/settings"); var settings = NR_TEST_UTILS.require("@node-red/runtime/lib/settings");
var util = NR_TEST_UTILS.require("@node-red/util");
var log = NR_TEST_UTILS.require("@node-red/util").log; var log = NR_TEST_UTILS.require("@node-red/util").log;
describe("runtime", function() { describe("runtime", function() {
@ -41,6 +43,7 @@ describe("runtime", function() {
delete process.env.NODE_RED_HOME; delete process.env.NODE_RED_HOME;
}); });
function mockUtil(metrics) { function mockUtil(metrics) {
return { return {
log:{ log:{
log: sinon.stub(), log: sinon.stub(),
@ -95,6 +98,7 @@ describe("runtime", function() {
var redNodesLoadFlows; var redNodesLoadFlows;
var redNodesStartFlows; var redNodesStartFlows;
var redNodesLoadContextsPlugin; var redNodesLoadContextsPlugin;
var i18nRegisterMessageCatalog;
beforeEach(function() { beforeEach(function() {
storageInit = sinon.stub(storage,"init",function(settings) {return Promise.resolve();}); storageInit = sinon.stub(storage,"init",function(settings) {return Promise.resolve();});
@ -104,6 +108,7 @@ describe("runtime", function() {
redNodesLoadFlows = sinon.stub(redNodes,"loadFlows",function() {return Promise.resolve()}); redNodesLoadFlows = sinon.stub(redNodes,"loadFlows",function() {return Promise.resolve()});
redNodesStartFlows = sinon.stub(redNodes,"startFlows",function() {}); redNodesStartFlows = sinon.stub(redNodes,"startFlows",function() {});
redNodesLoadContextsPlugin = sinon.stub(redNodes,"loadContextsPlugin",function() {return Promise.resolve()}); redNodesLoadContextsPlugin = sinon.stub(redNodes,"loadContextsPlugin",function() {return Promise.resolve()});
i18nRegisterMessageCatalog = sinon.stub(util.i18n,"registerMessageCatalog",function() {return Promise.resolve()});
}); });
afterEach(function() { afterEach(function() {
storageInit.restore(); storageInit.restore();
@ -114,6 +119,7 @@ describe("runtime", function() {
redNodesLoadFlows.restore(); redNodesLoadFlows.restore();
redNodesStartFlows.restore(); redNodesStartFlows.restore();
redNodesLoadContextsPlugin.restore(); redNodesLoadContextsPlugin.restore();
i18nRegisterMessageCatalog.restore();
}); });
it("reports errored/missing modules",function(done) { it("reports errored/missing modules",function(done) {
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function(cb) { redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function(cb) {
@ -199,10 +205,14 @@ describe("runtime", function() {
var stopFlows = sinon.stub(redNodes,"stopFlows",function() { return Promise.resolve();} ); var stopFlows = sinon.stub(redNodes,"stopFlows",function() { return Promise.resolve();} );
redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {return []}); redNodesGetNodeList = sinon.stub(redNodes,"getNodeList", function() {return []});
var util = mockUtil(true); var util = mockUtil(true);
runtime.init({testSettings: true, runtimeMetricInterval:200, httpAdminRoot:"/", load:function() { return Promise.resolve();}},util); runtime.init(
sinon.stub(console,"log"); {testSettings: true, runtimeMetricInterval:200, httpAdminRoot:"/", load:function() { return Promise.resolve();}},
{},
undefined,
util);
// sinon.stub(console,"log");
runtime.start().then(function() { runtime.start().then(function() {
console.log.restore(); // console.log.restore();
setTimeout(function() { setTimeout(function() {
try { try {
util.log.log.args.should.have.lengthOf(3); util.log.log.args.should.have.lengthOf(3);

View File

@ -31,31 +31,31 @@ var api = NR_TEST_UTILS.require("@node-red/runtime/lib/api");
describe("red/red", function() { describe("red/red", function() {
describe("check build", function() { // describe("check build", function() {
beforeEach(function() { // beforeEach(function() {
sinon.stub(runtime,"init",function() {}); // sinon.stub(runtime,"init",function() {});
sinon.stub(api,"init",function() {}); // sinon.stub(api,"init",function() {});
sinon.stub(RED,"version",function() { return "version";}); // // sinon.stub(RED,"version",function() { return "version";});
}); // });
afterEach(function() { // afterEach(function() {
runtime.init.restore(); // runtime.init.restore();
api.init.restore(); // api.init.restore();
fs.statSync.restore(); // fs.statSync.restore();
RED.version.restore(); // // RED.version.restore();
}); // });
it.skip('warns if build has not been run',function() { // it.skip('warns if build has not been run',function() {
sinon.stub(fs,"statSync",function() { throw new Error();}); // sinon.stub(fs,"statSync",function() { throw new Error();});
//
/*jshint immed: false */ // /*jshint immed: false */
(function() { // (function() {
RED.init({},{}); // RED.init({},{});
}).should.throw("Node-RED not built"); // }).should.throw("Node-RED not built");
}); // });
it('passed if build has been run',function() { // it('passed if build has been run',function() {
sinon.stub(fs,"statSync",function() { }); // sinon.stub(fs,"statSync",function() { });
RED.init({},{}); // RED.init({},{});
}); // });
}); // });
describe("externals", function() { describe("externals", function() {
it('reports version', function() { it('reports version', function() {