mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Merge branch 'master' into 0.16
This commit is contained in:
commit
e73216d4c1
35
CHANGELOG.md
35
CHANGELOG.md
@ -1,3 +1,38 @@
|
|||||||
|
#### 0.15.3: Maintenance Release
|
||||||
|
|
||||||
|
- Tcpgetfix: Another small check (#1070)
|
||||||
|
- TCPGet: Ensure done() is called only once (#1068)
|
||||||
|
- Allow $ and _ at start of property identifiers Fixes #1063
|
||||||
|
- TCPGet: Separated the node.connected property for each instance (#1062)
|
||||||
|
- Corrected 'overide' typo in XML node help (#1061)
|
||||||
|
- TCPGet: Last property check (hopefully) (#1059)
|
||||||
|
- Add additional safety checks to avoid acting on non-existent objects (#1057)
|
||||||
|
- add --title for process name to command line options
|
||||||
|
- add indicator for fire once on inject node
|
||||||
|
- reimplement $(env var) replace to share common code.
|
||||||
|
- Fix error message for missing node html file, and add test.
|
||||||
|
- Let credentials also use $(...) substitutions from ENV
|
||||||
|
- Rename insecureRedirect to requireHttps
|
||||||
|
- Add setting to cause insecure redirect (#1054)
|
||||||
|
- Palette editor fixes (#1033)
|
||||||
|
- Close comms on stopServer in test helper (#1020)
|
||||||
|
- Tcpgetfix (#1050)
|
||||||
|
- TCPget: Store incoming messages alongside the client object to keep reference
|
||||||
|
- Merge remote-tracking branch 'upstream/master' into tcpgetfix
|
||||||
|
- TCPget can now handle concurrent sessions (#1042)
|
||||||
|
- Better scope handling
|
||||||
|
- Add security checks
|
||||||
|
- small change to udp httpadmin
|
||||||
|
- Fix comparison to "" in tcpin
|
||||||
|
- Change scope of clients object
|
||||||
|
- Works when connection is left open
|
||||||
|
- First release of multi connection tcpget
|
||||||
|
- Fix node.error() not printing when passed false (#1037)
|
||||||
|
- fix test for CSV array input
|
||||||
|
- different test for Pi (rather than use serial port name)
|
||||||
|
- Fix missing 0 handling for css node with array input
|
||||||
|
|
||||||
|
|
||||||
#### 0.15.2: Maintenance Release
|
#### 0.15.2: Maintenance Release
|
||||||
|
|
||||||
- Revert bidi changes to nodes and hide menu option until fixed Fixes #1024
|
- Revert bidi changes to nodes and hide menu option until fixed Fixes #1024
|
||||||
|
@ -38,21 +38,13 @@ If you want to raise a pull-request with a new feature, or a refactoring
|
|||||||
of existing code, it may well get rejected if you haven't discussed it on
|
of existing code, it may well get rejected if you haven't discussed it on
|
||||||
the [mailing list](https://groups.google.com/forum/#!forum/node-red) first.
|
the [mailing list](https://groups.google.com/forum/#!forum/node-red) first.
|
||||||
|
|
||||||
### Contributor License Agreement
|
All contributors need to sign the JS Foundation's Contributor License Agreement.
|
||||||
|
It is an online process and quick to do. You can read the details of the agreement
|
||||||
|
here: https://cla.js.foundation/node-red/node-red.
|
||||||
|
|
||||||
In order for us to accept pull-requests, the contributor must first complete
|
If you raise a pull-request without having signed the CLA, you will be prompted
|
||||||
a Contributor License Agreement (CLA). This clarifies the intellectual
|
to do so automatically.
|
||||||
property license granted with any contribution. It is for your protection as a
|
|
||||||
Contributor as well as the protection of IBM and its customers; it does not
|
|
||||||
change your rights to use your own Contributions for any other purpose.
|
|
||||||
|
|
||||||
You can download the CLAs here:
|
|
||||||
|
|
||||||
- [individual](http://nodered.org/cla/node-red-cla-individual.pdf)
|
|
||||||
- [corporate](http://nodered.org/cla/node-red-cla-corporate.pdf)
|
|
||||||
|
|
||||||
If you are an IBMer, please contact us directly as the contribution process is
|
|
||||||
slightly different.
|
|
||||||
|
|
||||||
### Coding standards
|
### Coding standards
|
||||||
|
|
||||||
|
@ -14,9 +14,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
(function($) {
|
(function($) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var allOptions = {
|
var allOptions = {
|
||||||
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression},
|
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression},
|
||||||
flow: {value:"flow",label:"flow.",validate:RED.utils.validatePropertyExpression},
|
flow: {value:"flow",label:"flow.",validate:RED.utils.validatePropertyExpression},
|
||||||
|
@ -255,7 +255,9 @@ RED.palette.editor = (function() {
|
|||||||
nodeEntry.removeButton.hide();
|
nodeEntry.removeButton.hide();
|
||||||
} else {
|
} else {
|
||||||
nodeEntry.enableButton.removeClass('disabled');
|
nodeEntry.enableButton.removeClass('disabled');
|
||||||
|
if (moduleInfo.local) {
|
||||||
nodeEntry.removeButton.show();
|
nodeEntry.removeButton.show();
|
||||||
|
}
|
||||||
if (activeTypeCount === 0) {
|
if (activeTypeCount === 0) {
|
||||||
nodeEntry.enableButton.html(RED._('palette.editor.enableall'));
|
nodeEntry.enableButton.html(RED._('palette.editor.enableall'));
|
||||||
} else {
|
} else {
|
||||||
|
@ -201,6 +201,11 @@
|
|||||||
icon: "inject.png",
|
icon: "inject.png",
|
||||||
label: function() {
|
label: function() {
|
||||||
var suffix = "";
|
var suffix = "";
|
||||||
|
// if fire once then add small indication
|
||||||
|
if (this.once) {
|
||||||
|
suffix = " ¹";
|
||||||
|
}
|
||||||
|
// but replace with repeat one if set to repeat
|
||||||
if (this.repeat || this.crontab) {
|
if (this.repeat || this.crontab) {
|
||||||
suffix = " ↻";
|
suffix = " ↻";
|
||||||
}
|
}
|
||||||
|
@ -23,9 +23,9 @@ module.exports = function(RED) {
|
|||||||
var gpioCommand = __dirname+'/nrgpio';
|
var gpioCommand = __dirname+'/nrgpio';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs.statSync("/dev/ttyAMA0"); // unlikely if not on a Pi
|
var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString();
|
||||||
|
if (cpuinfo.indexOf(": BCM") === -1) { throw "Info : "+RED._("rpi-gpio.errors.ignorenode"); }
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
//RED.log.info(RED._("rpi-gpio.errors.ignorenode"));
|
|
||||||
throw "Info : "+RED._("rpi-gpio.errors.ignorenode");
|
throw "Info : "+RED._("rpi-gpio.errors.ignorenode");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
client.on('end', function() {
|
client.on('end', function() {
|
||||||
if (!node.stream || (node.datatype == "utf8" && node.newline != "" && buffer.length > 0)) {
|
if (!node.stream || (node.datatype == "utf8" && node.newline !== "" && buffer.length > 0)) {
|
||||||
var msg = {topic:node.topic, payload:buffer};
|
var msg = {topic:node.topic, payload:buffer};
|
||||||
msg._session = {type:"tcp",id:id};
|
msg._session = {type:"tcp",id:id};
|
||||||
if (buffer.length !== 0) {
|
if (buffer.length !== 0) {
|
||||||
@ -407,6 +407,28 @@ module.exports = function(RED) {
|
|||||||
} // jshint ignore:line
|
} // jshint ignore:line
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var node = this;
|
||||||
|
|
||||||
|
var clients = {};
|
||||||
|
|
||||||
|
this.on("input", function(msg) {
|
||||||
|
var i = 0;
|
||||||
|
if ((!Buffer.isBuffer(msg.payload)) && (typeof msg.payload !== "string")) {
|
||||||
|
msg.payload = msg.payload.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
var host = node.server || msg.host;
|
||||||
|
var port = node.port || msg.port;
|
||||||
|
|
||||||
|
// Store client information independently
|
||||||
|
// the clients object will have:
|
||||||
|
// clients[id].client, clients[id].msg, clients[id].timeout
|
||||||
|
var connection_id = host + ":" + port;
|
||||||
|
clients[connection_id] = clients[connection_id] || {};
|
||||||
|
clients[connection_id].msg = msg;
|
||||||
|
clients[connection_id].connected = clients[connection_id].connected || false;
|
||||||
|
|
||||||
|
if (!clients[connection_id].connected) {
|
||||||
var buf;
|
var buf;
|
||||||
if (this.out == "count") {
|
if (this.out == "count") {
|
||||||
if (this.splitc === 0) { buf = new Buffer(1); }
|
if (this.splitc === 0) { buf = new Buffer(1); }
|
||||||
@ -414,136 +436,178 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
else { buf = new Buffer(65536); } // set it to 64k... hopefully big enough for most TCP packets.... but only hopefully
|
else { buf = new Buffer(65536); } // set it to 64k... hopefully big enough for most TCP packets.... but only hopefully
|
||||||
|
|
||||||
this.connected = false;
|
clients[connection_id].client = net.Socket();
|
||||||
var node = this;
|
if (socketTimeout !== null) { clients[connection_id].client.setTimeout(socketTimeout);}
|
||||||
var client;
|
|
||||||
var m;
|
|
||||||
|
|
||||||
this.on("input", function(msg) {
|
|
||||||
m = msg;
|
|
||||||
var i = 0;
|
|
||||||
if ((!Buffer.isBuffer(msg.payload)) && (typeof msg.payload !== "string")) {
|
|
||||||
msg.payload = msg.payload.toString();
|
|
||||||
}
|
|
||||||
if (!node.connected) {
|
|
||||||
client = net.Socket();
|
|
||||||
if (socketTimeout !== null) { client.setTimeout(socketTimeout); }
|
|
||||||
var host = node.server || msg.host;
|
|
||||||
var port = node.port || msg.port;
|
|
||||||
|
|
||||||
if (host && port) {
|
if (host && port) {
|
||||||
client.connect(port, host, function() {
|
clients[connection_id].client.connect(port, host, function() {
|
||||||
//node.log(RED._("tcpin.errors.client-connected"));
|
//node.log(RED._("tcpin.errors.client-connected"));
|
||||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||||
node.connected = true;
|
if (clients[connection_id] && clients[connection_id].client) {
|
||||||
client.write(msg.payload);
|
clients[connection_id].connected = true;
|
||||||
|
clients[connection_id].client.write(clients[connection_id].msg.payload);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
node.warn(RED._("tcpin.errors.no-host"));
|
node.warn(RED._("tcpin.errors.no-host"));
|
||||||
}
|
}
|
||||||
|
|
||||||
client.on('data', function(data) {
|
clients[connection_id].client.on('data', function(data) {
|
||||||
if (node.out == "sit") { // if we are staying connected just send the buffer
|
if (node.out == "sit") { // if we are staying connected just send the buffer
|
||||||
m.payload = data;
|
if (clients[connection_id]) {
|
||||||
node.send(m);
|
clients[connection_id].msg.payload = data;
|
||||||
|
node.send(clients[connection_id].msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (node.splitc === 0) {
|
else if (node.splitc === 0) {
|
||||||
msg.payload = data;
|
clients[connection_id].msg.payload = data;
|
||||||
node.send(msg);
|
node.send(clients[connection_id].msg);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (var j = 0; j < data.length; j++ ) {
|
for (var j = 0; j < data.length; j++ ) {
|
||||||
if (node.out === "time") {
|
if (node.out === "time") {
|
||||||
|
if (clients[connection_id]) {
|
||||||
// do the timer thing
|
// do the timer thing
|
||||||
if (node.tout) {
|
if (clients[connection_id].timeout) {
|
||||||
i += 1;
|
i += 1;
|
||||||
buf[i] = data[j];
|
buf[i] = data[j];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
node.tout = setTimeout(function () {
|
clients[connection_id].timeout = setTimeout(function () {
|
||||||
node.tout = null;
|
if (clients[connection_id]) {
|
||||||
msg.payload = new Buffer(i+1);
|
clients[connection_id].timeout = null;
|
||||||
buf.copy(msg.payload,0,0,i+1);
|
clients[connection_id].msg.payload = new Buffer(i+1);
|
||||||
node.send(msg);
|
buf.copy(clients[connection_id].msg.payload,0,0,i+1);
|
||||||
if (client) { node.status({}); client.destroy(); }
|
node.send(clients[connection_id].msg);
|
||||||
|
if (clients[connection_id].client) {
|
||||||
|
node.status({}); clients[connection_id].client.destroy();
|
||||||
|
delete clients[connection_id];
|
||||||
|
}
|
||||||
|
}
|
||||||
}, node.splitc);
|
}, node.splitc);
|
||||||
i = 0;
|
i = 0;
|
||||||
buf[0] = data[j];
|
buf[0] = data[j];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// count bytes into a buffer...
|
// count bytes into a buffer...
|
||||||
else if (node.out == "count") {
|
else if (node.out == "count") {
|
||||||
buf[i] = data[j];
|
buf[i] = data[j];
|
||||||
i += 1;
|
i += 1;
|
||||||
if ( i >= node.splitc) {
|
if ( i >= node.splitc) {
|
||||||
msg.payload = new Buffer(i);
|
if (clients[connection_id]) {
|
||||||
buf.copy(msg.payload,0,0,i);
|
clients[connection_id].msg.payload = new Buffer(i);
|
||||||
node.send(msg);
|
buf.copy(clients[connection_id].msg.payload,0,0,i);
|
||||||
if (client) { node.status({}); client.destroy(); }
|
node.send(clients[connection_id].msg);
|
||||||
|
if (clients[connection_id].client) {
|
||||||
|
node.status({}); clients[connection_id].client.destroy();
|
||||||
|
delete clients[connection_id];
|
||||||
|
}
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// look for a char
|
// look for a char
|
||||||
else {
|
else {
|
||||||
buf[i] = data[j];
|
buf[i] = data[j];
|
||||||
i += 1;
|
i += 1;
|
||||||
if (data[j] == node.splitc) {
|
if (data[j] == node.splitc) {
|
||||||
msg.payload = new Buffer(i);
|
if (clients[connection_id]) {
|
||||||
buf.copy(msg.payload,0,0,i);
|
clients[connection_id].msg.payload = new Buffer(i);
|
||||||
node.send(msg);
|
buf.copy(clients[connection_id].msg.payload,0,0,i);
|
||||||
if (client) { node.status({}); client.destroy(); }
|
node.send(clients[connection_id].msg);
|
||||||
|
if (clients[connection_id].client) {
|
||||||
|
node.status({}); clients[connection_id].client.destroy();
|
||||||
|
delete clients[connection_id];
|
||||||
|
}
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('end', function() {
|
clients[connection_id].client.on('end', function() {
|
||||||
//console.log("END");
|
//console.log("END");
|
||||||
node.connected = false;
|
|
||||||
node.status({fill:"grey",shape:"ring",text:"common.status.disconnected"});
|
node.status({fill:"grey",shape:"ring",text:"common.status.disconnected"});
|
||||||
client = null;
|
if (clients[connection_id] && clients[connection_id].client) {
|
||||||
|
clients[connection_id].connected = false;
|
||||||
|
clients[connection_id].client = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('close', function() {
|
clients[connection_id].client.on('close', function() {
|
||||||
//console.log("CLOSE");
|
//console.log("CLOSE");
|
||||||
node.connected = false;
|
if (clients[connection_id]) {
|
||||||
if (node.done) { node.done(); }
|
clients[connection_id].connected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var anyConnected = false;
|
||||||
|
|
||||||
|
for (var client in clients) {
|
||||||
|
if (clients[client].connected) {
|
||||||
|
anyConnected = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node.done && !anyConnected) {
|
||||||
|
clients = {};
|
||||||
|
node.done();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('error', function() {
|
clients[connection_id].client.on('error', function() {
|
||||||
//console.log("ERROR");
|
//console.log("ERROR");
|
||||||
node.connected = false;
|
|
||||||
node.status({fill:"red",shape:"ring",text:"common.status.error"});
|
node.status({fill:"red",shape:"ring",text:"common.status.error"});
|
||||||
node.error(RED._("tcpin.errors.connect-fail"),msg);
|
node.error(RED._("tcpin.errors.connect-fail") + " " + connection_id, msg);
|
||||||
if (client) { client.destroy(); }
|
if (clients[connection_id] && clients[connection_id].client) {
|
||||||
|
clients[connection_id].connected = false;
|
||||||
|
clients[connection_id].client.destroy();
|
||||||
|
delete clients[connection_id];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('timeout',function() {
|
clients[connection_id].client.on('timeout',function() {
|
||||||
//console.log("TIMEOUT");
|
//console.log("TIMEOUT");
|
||||||
node.connected = false;
|
clients[connection_id].connected = false;
|
||||||
node.status({fill:"grey",shape:"dot",text:"tcpin.errors.connect-timeout"});
|
node.status({fill:"grey",shape:"dot",text:"tcpin.errors.connect-timeout"});
|
||||||
//node.warn(RED._("tcpin.errors.connect-timeout"));
|
//node.warn(RED._("tcpin.errors.connect-timeout"));
|
||||||
if (client) {
|
if (clients[connection_id] && clients[connection_id].client) {
|
||||||
client.connect(port, host, function() {
|
clients[connection_id].client.connect(port, host, function() {
|
||||||
node.connected = true;
|
clients[connection_id].connected = true;
|
||||||
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
node.status({fill:"green",shape:"dot",text:"common.status.connected"});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else { client.write(msg.payload); }
|
else {
|
||||||
|
if (clients[connection_id] && clients[connection_id].client) {
|
||||||
|
clients[connection_id].client.write(clients[connection_id].msg.payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on("close", function(done) {
|
this.on("close", function(done) {
|
||||||
node.done = done;
|
node.done = done;
|
||||||
if (client) {
|
for (var client in clients) {
|
||||||
client.destroy();
|
clients[client].client.destroy();
|
||||||
}
|
}
|
||||||
node.status({});
|
node.status({});
|
||||||
if (!node.connected) { done(); }
|
|
||||||
|
var anyConnected = false;
|
||||||
|
for (var c in clients) {
|
||||||
|
if (clients[c].connected) {
|
||||||
|
anyConnected = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!anyConnected) {
|
||||||
|
clients = {};
|
||||||
|
done();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ module.exports = function(RED) {
|
|||||||
try { server.bind(node.port,node.iface); }
|
try { server.bind(node.port,node.iface); }
|
||||||
catch(e) { } // Don't worry if already bound
|
catch(e) { } // Don't worry if already bound
|
||||||
}
|
}
|
||||||
RED.httpAdmin.get('/udp-ports/:id', RED.auth.needsPermission('udp-in.read'), function(req,res) {
|
RED.httpAdmin.get('/udp-ports/:id', RED.auth.needsPermission('udp-ports.read'), function(req,res) {
|
||||||
res.json(Object.keys(udpInputPortsInUse));
|
res.json(Object.keys(udpInputPortsInUse));
|
||||||
});
|
});
|
||||||
RED.nodes.registerType("udp in",UDPin);
|
RED.nodes.registerType("udp in",UDPin);
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
<p>A function that parses the <code>msg.payload</code> to convert xml to/from a javascript object. Places the result in the payload.</p>
|
<p>A function that parses the <code>msg.payload</code> to convert xml to/from a javascript object. Places the result in the payload.</p>
|
||||||
<p>If the input is a string it tries to parse it as XML and creates a javascript object.</p>
|
<p>If the input is a string it tries to parse it as XML and creates a javascript object.</p>
|
||||||
<p>If the input is a javascript object it tries to build an XML string.</p>
|
<p>If the input is a javascript object it tries to build an XML string.</p>
|
||||||
<p>You can also pass in a <code>msg.options</code> object to overide all the multitude of parameters. See
|
<p>You can also pass in a <code>msg.options</code> object to override all the multitude of parameters. See
|
||||||
<a href="https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md#options" target="_blank">the xml2js docs</a>
|
<a href="https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/README.md#options" target="_blank">the xml2js docs</a>
|
||||||
for more information.</p>
|
for more information.</p>
|
||||||
<p>If set, options in the edit dialogue override those passed in on the msg.options object.</p>
|
<p>If set, options in the edit dialogue override those passed in on the msg.options object.</p>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name" : "node-red",
|
"name" : "node-red",
|
||||||
"version" : "0.15.2",
|
"version" : "0.15.3",
|
||||||
"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",
|
||||||
|
32
red.js
32
red.js
@ -33,17 +33,20 @@ var settingsFile;
|
|||||||
var flowFile;
|
var flowFile;
|
||||||
|
|
||||||
var knownOpts = {
|
var knownOpts = {
|
||||||
"settings":[path],
|
"help": Boolean,
|
||||||
"userDir":[path],
|
|
||||||
"port": Number,
|
"port": Number,
|
||||||
"v": Boolean,
|
"settings": [path],
|
||||||
"help": Boolean
|
"title": String,
|
||||||
|
"userDir": [path],
|
||||||
|
"verbose": Boolean
|
||||||
};
|
};
|
||||||
var shortHands = {
|
var shortHands = {
|
||||||
"s":["--settings"],
|
"?":["--help"],
|
||||||
"u":["--userDir"],
|
|
||||||
"p":["--port"],
|
"p":["--port"],
|
||||||
"?":["--help"]
|
"s":["--settings"],
|
||||||
|
"t":["--help"],
|
||||||
|
"u":["--userDir"],
|
||||||
|
"v":["--verbose"]
|
||||||
};
|
};
|
||||||
nopt.invalidHandler = function(k,v,t) {
|
nopt.invalidHandler = function(k,v,t) {
|
||||||
// TODO: console.log(k,v,t);
|
// TODO: console.log(k,v,t);
|
||||||
@ -54,14 +57,15 @@ var parsedArgs = nopt(knownOpts,shortHands,process.argv,2)
|
|||||||
if (parsedArgs.help) {
|
if (parsedArgs.help) {
|
||||||
console.log("Node-RED v"+RED.version());
|
console.log("Node-RED v"+RED.version());
|
||||||
console.log("Usage: node-red [-v] [-?] [--settings settings.js] [--userDir DIR]");
|
console.log("Usage: node-red [-v] [-?] [--settings settings.js] [--userDir DIR]");
|
||||||
console.log(" [--port PORT] [flows.json]");
|
console.log(" [--port PORT] [--title TITLE] [flows.json]");
|
||||||
console.log("");
|
console.log("");
|
||||||
console.log("Options:");
|
console.log("Options:");
|
||||||
console.log(" -s, --settings FILE use specified settings file");
|
|
||||||
console.log(" -u, --userDir DIR use specified user directory");
|
|
||||||
console.log(" -p, --port PORT port to listen on");
|
console.log(" -p, --port PORT port to listen on");
|
||||||
console.log(" -v enable verbose output");
|
console.log(" -s, --settings FILE use specified settings file");
|
||||||
console.log(" -?, --help show usage");
|
console.log(" --title TITLE process window title");
|
||||||
|
console.log(" -u, --userDir DIR use specified user directory");
|
||||||
|
console.log(" -v, --verbose enable verbose output");
|
||||||
|
console.log(" -?, --help show this help");
|
||||||
console.log("");
|
console.log("");
|
||||||
console.log("Documentation can be found at http://nodered.org");
|
console.log("Documentation can be found at http://nodered.org");
|
||||||
process.exit();
|
process.exit();
|
||||||
@ -224,7 +228,6 @@ if (settings.httpNodeRoot !== false && settings.httpNodeAuth) {
|
|||||||
if (settings.httpNodeRoot !== false) {
|
if (settings.httpNodeRoot !== false) {
|
||||||
app.use(settings.httpNodeRoot,RED.httpNode);
|
app.use(settings.httpNodeRoot,RED.httpNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.httpStatic) {
|
if (settings.httpStatic) {
|
||||||
settings.httpStaticAuth = settings.httpStaticAuth || settings.httpAuth;
|
settings.httpStaticAuth = settings.httpStaticAuth || settings.httpAuth;
|
||||||
if (settings.httpStaticAuth) {
|
if (settings.httpStaticAuth) {
|
||||||
@ -265,7 +268,7 @@ RED.start().then(function() {
|
|||||||
if (settings.httpAdminRoot === false) {
|
if (settings.httpAdminRoot === false) {
|
||||||
RED.log.info(RED.log._("server.admin-ui-disabled"));
|
RED.log.info(RED.log._("server.admin-ui-disabled"));
|
||||||
}
|
}
|
||||||
process.title = 'node-red';
|
process.title = parsedArgs.title || 'node-red';
|
||||||
RED.log.info(RED.log._("server.now-running", {listenpath:getListenPath()}));
|
RED.log.info(RED.log._("server.now-running", {listenpath:getListenPath()}));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -280,7 +283,6 @@ RED.start().then(function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
process.on('uncaughtException',function(err) {
|
process.on('uncaughtException',function(err) {
|
||||||
util.log('[red] Uncaught Exception:');
|
util.log('[red] Uncaught Exception:');
|
||||||
if (err.stack) {
|
if (err.stack) {
|
||||||
|
@ -87,6 +87,16 @@ function init(_server,_runtime) {
|
|||||||
if (!settings.disableEditor) {
|
if (!settings.disableEditor) {
|
||||||
ui.init(runtime);
|
ui.init(runtime);
|
||||||
var editorApp = express();
|
var editorApp = express();
|
||||||
|
if (settings.requireHttps === true) {
|
||||||
|
editorApp.enable('trust proxy');
|
||||||
|
editorApp.use(function (req, res, next) {
|
||||||
|
if (req.secure) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
res.redirect('https://' + req.headers.host + req.originalUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
editorApp.get("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor);
|
editorApp.get("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor);
|
||||||
editorApp.get("/icons/:icon",ui.icon);
|
editorApp.get("/icons/:icon",ui.icon);
|
||||||
theme.init(runtime);
|
theme.init(runtime);
|
||||||
|
@ -236,7 +236,9 @@ Node.prototype.warn = function(msg) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Node.prototype.error = function(logMessage,msg) {
|
Node.prototype.error = function(logMessage,msg) {
|
||||||
|
if (typeof logMessage != 'boolean') {
|
||||||
logMessage = logMessage || "";
|
logMessage = logMessage || "";
|
||||||
|
}
|
||||||
log_helper(this, Log.ERROR, logMessage);
|
log_helper(this, Log.ERROR, logMessage);
|
||||||
/* istanbul ignore else */
|
/* istanbul ignore else */
|
||||||
if (msg) {
|
if (msg) {
|
||||||
|
@ -259,32 +259,6 @@ function Flow(global,flow) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var EnvVarPropertyRE = /^\$\((\S+)\)$/;
|
|
||||||
|
|
||||||
function mapEnvVarProperties(obj,prop) {
|
|
||||||
if (Buffer.isBuffer(obj[prop])) {
|
|
||||||
return;
|
|
||||||
} else if (Array.isArray(obj[prop])) {
|
|
||||||
for (var i=0;i<obj[prop].length;i++) {
|
|
||||||
mapEnvVarProperties(obj[prop],i);
|
|
||||||
}
|
|
||||||
} else if (typeof obj[prop] === 'string') {
|
|
||||||
var m;
|
|
||||||
if ( (m = EnvVarPropertyRE.exec(obj[prop])) !== null) {
|
|
||||||
if (process.env.hasOwnProperty(m[1])) {
|
|
||||||
obj[prop] = process.env[m[1]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (var p in obj[prop]) {
|
|
||||||
if (obj[prop].hasOwnProperty(p)) {
|
|
||||||
mapEnvVarProperties(obj[prop],p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNode(type,config) {
|
function createNode(type,config) {
|
||||||
@ -295,7 +269,7 @@ function createNode(type,config) {
|
|||||||
delete conf.credentials;
|
delete conf.credentials;
|
||||||
for (var p in conf) {
|
for (var p in conf) {
|
||||||
if (conf.hasOwnProperty(p)) {
|
if (conf.hasOwnProperty(p)) {
|
||||||
mapEnvVarProperties(conf,p);
|
flowUtil.mapEnvVarProperties(conf,p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -37,9 +37,35 @@ function diffNodes(oldNode,newNode) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var EnvVarPropertyRE = /^\$\((\S+)\)$/;
|
||||||
|
|
||||||
|
function mapEnvVarProperties(obj,prop) {
|
||||||
|
if (Buffer.isBuffer(obj[prop])) {
|
||||||
|
return;
|
||||||
|
} else if (Array.isArray(obj[prop])) {
|
||||||
|
for (var i=0;i<obj[prop].length;i++) {
|
||||||
|
mapEnvVarProperties(obj[prop],i);
|
||||||
|
}
|
||||||
|
} else if (typeof obj[prop] === 'string') {
|
||||||
|
var m;
|
||||||
|
if ( (m = EnvVarPropertyRE.exec(obj[prop])) !== null) {
|
||||||
|
if (process.env.hasOwnProperty(m[1])) {
|
||||||
|
obj[prop] = process.env[m[1]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (var p in obj[prop]) {
|
||||||
|
if (obj[prop].hasOwnProperty(p)) {
|
||||||
|
mapEnvVarProperties(obj[prop],p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
||||||
diffNodes: diffNodes,
|
diffNodes: diffNodes,
|
||||||
|
mapEnvVarProperties: mapEnvVarProperties,
|
||||||
|
|
||||||
parseConfig: function(config) {
|
parseConfig: function(config) {
|
||||||
var flow = {};
|
var flow = {};
|
||||||
|
@ -21,6 +21,7 @@ var fs = require("fs");
|
|||||||
var registry = require("./registry");
|
var registry = require("./registry");
|
||||||
var credentials = require("./credentials");
|
var credentials = require("./credentials");
|
||||||
var flows = require("./flows");
|
var flows = require("./flows");
|
||||||
|
var flowUtil = require("./flows/util")
|
||||||
var context = require("./context");
|
var context = require("./context");
|
||||||
var Node = require("./Node");
|
var Node = require("./Node");
|
||||||
var log = require("../log");
|
var log = require("../log");
|
||||||
@ -69,6 +70,12 @@ function createNode(node,def) {
|
|||||||
var creds = credentials.get(id);
|
var creds = credentials.get(id);
|
||||||
if (creds) {
|
if (creds) {
|
||||||
//console.log("Attaching credentials to ",node.id);
|
//console.log("Attaching credentials to ",node.id);
|
||||||
|
// allow $(foo) syntax to substitute env variables for credentials also...
|
||||||
|
for (var p in creds) {
|
||||||
|
if (creds.hasOwnProperty(p)) {
|
||||||
|
flowUtil.mapEnvVarProperties(creds,p);
|
||||||
|
}
|
||||||
|
}
|
||||||
node.credentials = creds;
|
node.credentials = creds;
|
||||||
} else if (credentials.getDefinition(node.type)) {
|
} else if (credentials.getDefinition(node.type)) {
|
||||||
node.credentials = {};
|
node.credentials = {};
|
||||||
@ -146,7 +153,6 @@ module.exports = {
|
|||||||
// disableFlow: flows.disableFlow,
|
// disableFlow: flows.disableFlow,
|
||||||
// enableFlow: flows.enableFlow,
|
// enableFlow: flows.enableFlow,
|
||||||
|
|
||||||
|
|
||||||
// Credentials
|
// Credentials
|
||||||
addCredentials: credentials.add,
|
addCredentials: credentials.add,
|
||||||
getCredentials: credentials.get,
|
getCredentials: credentials.get,
|
||||||
|
@ -203,7 +203,7 @@ function loadNodeConfig(fileInfo) {
|
|||||||
if (!node.types) {
|
if (!node.types) {
|
||||||
node.types = [];
|
node.types = [];
|
||||||
}
|
}
|
||||||
node.err = "Error: "+file+" does not exist";
|
node.err = "Error: "+node.template+" does not exist";
|
||||||
} else {
|
} else {
|
||||||
node.types = [];
|
node.types = [];
|
||||||
node.err = err.toString();
|
node.err = err.toString();
|
||||||
@ -382,7 +382,6 @@ function getNodeHelp(node,lang) {
|
|||||||
} else {
|
} else {
|
||||||
node.help[lang] = node.help[runtime.i18n.defaultLang];
|
node.help[lang] = node.help[runtime.i18n.defaultLang];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return node.help[lang];
|
return node.help[lang];
|
||||||
}
|
}
|
||||||
|
@ -281,6 +281,7 @@ function getModuleFiles(module) {
|
|||||||
}
|
}
|
||||||
nodeModuleFiles.forEach(function(node) {
|
nodeModuleFiles.forEach(function(node) {
|
||||||
nodeList[moduleFile.package.name].nodes[node.name] = node;
|
nodeList[moduleFile.package.name].nodes[node.name] = node;
|
||||||
|
nodeList[moduleFile.package.name].nodes[node.name].local = moduleFile.local || false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return nodeList;
|
return nodeList;
|
||||||
|
@ -165,7 +165,7 @@ function normalisePropertyExpression(str) {
|
|||||||
throw new Error("Invalid property expression: unterminated expression");
|
throw new Error("Invalid property expression: unterminated expression");
|
||||||
}
|
}
|
||||||
// Next char is a-z
|
// Next char is a-z
|
||||||
if (!/[a-z0-9]/i.test(str[i+1])) {
|
if (!/[a-z0-9\$\_]/i.test(str[i+1])) {
|
||||||
throw new Error("Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
|
throw new Error("Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
|
||||||
}
|
}
|
||||||
start = i+1;
|
start = i+1;
|
||||||
|
@ -129,6 +129,10 @@ module.exports = {
|
|||||||
// cert: fs.readFileSync('certificate.pem')
|
// cert: fs.readFileSync('certificate.pem')
|
||||||
//},
|
//},
|
||||||
|
|
||||||
|
// The following property can be used to cause insecure HTTP connections to
|
||||||
|
// be redirected to HTTPS.
|
||||||
|
//requireHttps: true
|
||||||
|
|
||||||
// The following property can be used to disable the editor. The admin API
|
// The following property can be used to disable the editor. The admin API
|
||||||
// is not affected by this option. To disable both the editor and the admin
|
// is not affected by this option. To disable both the editor and the admin
|
||||||
// API, use either the httpRoot or httpAdminRoot properties
|
// API, use either the httpRoot or httpAdminRoot properties
|
||||||
|
@ -227,12 +227,12 @@ describe('CSV node', function() {
|
|||||||
var n2 = helper.getNode("n2");
|
var n2 = helper.getNode("n2");
|
||||||
n2.on("input", function(msg) {
|
n2.on("input", function(msg) {
|
||||||
try {
|
try {
|
||||||
msg.should.have.property('payload', '1,2,3,4\n');
|
msg.should.have.property('payload', '0,1,2,3,4\n');
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
catch(e) { done(e); }
|
catch(e) { done(e); }
|
||||||
});
|
});
|
||||||
var testJson = [1,2,3,4];
|
var testJson = [0,1,2,3,4];
|
||||||
n1.emit("input", {payload:testJson});
|
n1.emit("input", {payload:testJson});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -245,12 +245,12 @@ describe('CSV node', function() {
|
|||||||
var n2 = helper.getNode("n2");
|
var n2 = helper.getNode("n2");
|
||||||
n2.on("input", function(msg) {
|
n2.on("input", function(msg) {
|
||||||
try {
|
try {
|
||||||
msg.should.have.property('payload', '1,2,3,4\n4,3,2,1\n');
|
msg.should.have.property('payload', '0,1,2,3,4\n4,3,2,1,0\n');
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
catch(e) { done(e); }
|
catch(e) { done(e); }
|
||||||
});
|
});
|
||||||
var testJson = [[1,2,3,4],[4,3,2,1]];
|
var testJson = [[0,1,2,3,4],[4,3,2,1,0]];
|
||||||
n1.emit("input", {payload:testJson});
|
n1.emit("input", {payload:testJson});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -149,6 +149,9 @@ module.exports = {
|
|||||||
stopServer: function(done) {
|
stopServer: function(done) {
|
||||||
if (server) {
|
if (server) {
|
||||||
try {
|
try {
|
||||||
|
server.on('close', function() {
|
||||||
|
comms.stop();
|
||||||
|
});
|
||||||
server.close(done);
|
server.close(done);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
done();
|
done();
|
||||||
|
@ -34,7 +34,20 @@ describe('flows/util', function() {
|
|||||||
getType.restore();
|
getType.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#mapEnvVarProperties',function() {
|
||||||
|
it('handles ENV substitutions in an object', function() {
|
||||||
|
process.env.foo1 = "bar1";
|
||||||
|
process.env.foo2 = "bar2";
|
||||||
|
process.env.foo3 = "bar3";
|
||||||
|
var foo = {a:"$(foo1)",b:"$(foo2)",c:{d:"$(foo3)"}};
|
||||||
|
for (var p in foo) {
|
||||||
|
if (foo.hasOwnProperty(p)) {
|
||||||
|
flowUtil.mapEnvVarProperties(foo,p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foo.should.eql({ a: 'bar1', b: 'bar2', c: { d: 'bar3' } } );
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('#diffNodes',function() {
|
describe('#diffNodes',function() {
|
||||||
it('handles a null old node', function() {
|
it('handles a null old node', function() {
|
||||||
|
@ -38,8 +38,9 @@ describe("red/nodes/index", function() {
|
|||||||
index.clearRegistry();
|
index.clearRegistry();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
process.env.foo="bar";
|
||||||
var testFlows = [{"type":"test","id":"tab1","label":"Sheet 1"}];
|
var testFlows = [{"type":"test","id":"tab1","label":"Sheet 1"}];
|
||||||
var testCredentials = {"tab1":{"b":1,"c":2}};
|
var testCredentials = {"tab1":{"b":1, "c":"2", "d":"$(foo)"}};
|
||||||
var storage = {
|
var storage = {
|
||||||
getFlows: function() {
|
getFlows: function() {
|
||||||
return when({red:123,flows:testFlows,credentials:testCredentials});
|
return when({red:123,flows:testFlows,credentials:testCredentials});
|
||||||
@ -75,7 +76,8 @@ describe("red/nodes/index", function() {
|
|||||||
index.loadFlows().then(function() {
|
index.loadFlows().then(function() {
|
||||||
var testnode = new TestNode({id:'tab1',type:'test',name:'barney'});
|
var testnode = new TestNode({id:'tab1',type:'test',name:'barney'});
|
||||||
testnode.credentials.should.have.property('b',1);
|
testnode.credentials.should.have.property('b',1);
|
||||||
testnode.credentials.should.have.property('c',2);
|
testnode.credentials.should.have.property('c',"2");
|
||||||
|
testnode.credentials.should.have.property('d',"bar");
|
||||||
done();
|
done();
|
||||||
}).otherwise(function(err) {
|
}).otherwise(function(err) {
|
||||||
done(err);
|
done(err);
|
||||||
|
@ -317,6 +317,53 @@ describe("red/nodes/registry/loader",function() {
|
|||||||
done(err);
|
done(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("load core node files scanned by lfs - missing html file", function(done) {
|
||||||
|
stubs.push(sinon.stub(localfilesystem,"getNodeFiles", function(){
|
||||||
|
var result = {};
|
||||||
|
result["node-red"] = {
|
||||||
|
"name": "node-red",
|
||||||
|
"nodes": {
|
||||||
|
"DuffNode": {
|
||||||
|
"file": path.join(resourcesDir,"DuffNode","DuffNode.js"),
|
||||||
|
"module": "node-red",
|
||||||
|
"name": "DuffNode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return result;
|
||||||
|
}));
|
||||||
|
|
||||||
|
stubs.push(sinon.stub(registry,"saveNodeList", function(){ return }));
|
||||||
|
stubs.push(sinon.stub(registry,"addNodeSet", function(){ return }));
|
||||||
|
// This module isn't already loaded
|
||||||
|
stubs.push(sinon.stub(registry,"getNodeInfo", function(){ return null; }));
|
||||||
|
|
||||||
|
stubs.push(sinon.stub(nodes,"registerType"));
|
||||||
|
loader.init({nodes:nodes,i18n:{defaultLang:"en-US"},events:{on:function(){},removeListener:function(){}},log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
|
||||||
|
loader.load().then(function(result) {
|
||||||
|
registry.addNodeSet.called.should.be.true();
|
||||||
|
registry.addNodeSet.lastCall.args[0].should.eql("node-red/DuffNode");
|
||||||
|
registry.addNodeSet.lastCall.args[1].should.have.a.property('id',"node-red/DuffNode");
|
||||||
|
registry.addNodeSet.lastCall.args[1].should.have.a.property('module',"node-red");
|
||||||
|
registry.addNodeSet.lastCall.args[1].should.have.a.property('enabled',true);
|
||||||
|
registry.addNodeSet.lastCall.args[1].should.have.a.property('loaded',false);
|
||||||
|
registry.addNodeSet.lastCall.args[1].should.have.a.property('version',undefined);
|
||||||
|
registry.addNodeSet.lastCall.args[1].should.have.a.property('types');
|
||||||
|
registry.addNodeSet.lastCall.args[1].types.should.have.a.length(0);
|
||||||
|
registry.addNodeSet.lastCall.args[1].should.not.have.a.property('config');
|
||||||
|
registry.addNodeSet.lastCall.args[1].should.not.have.a.property('help');
|
||||||
|
registry.addNodeSet.lastCall.args[1].should.not.have.a.property('namespace','node-red');
|
||||||
|
registry.addNodeSet.lastCall.args[1].should.have.a.property('err');
|
||||||
|
registry.addNodeSet.lastCall.args[1].err.should.endWith("DuffNode.html does not exist");
|
||||||
|
|
||||||
|
nodes.registerType.calledOnce.should.be.false();
|
||||||
|
|
||||||
|
done();
|
||||||
|
}).otherwise(function(err) {
|
||||||
|
done(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#addModule",function() {
|
describe("#addModule",function() {
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
// A test node that exports a function
|
||||||
|
module.exports = function(RED) {
|
||||||
|
function DuffNode(n) {}
|
||||||
|
RED.nodes.registerType("duff-node",DuffNode);
|
||||||
|
}
|
@ -337,6 +337,11 @@ describe("red/util", function() {
|
|||||||
it("pass 'a.b'.c",function() { testABC("'a.b'.c",['a.b','c']); })
|
it("pass 'a.b'.c",function() { testABC("'a.b'.c",['a.b','c']); })
|
||||||
|
|
||||||
|
|
||||||
|
it('pass a.$b.c',function() { testABC('a.$b.c',['a','$b','c']); })
|
||||||
|
it('pass a["$b"].c',function() { testABC('a["$b"].c',['a','$b','c']); })
|
||||||
|
it('pass a._b.c',function() { testABC('a._b.c',['a','_b','c']); })
|
||||||
|
it('pass a["_b"].c',function() { testABC('a["_b"].c',['a','_b','c']); })
|
||||||
|
|
||||||
it("fail a'b'.c",function() { testInvalid("a'b'.c"); })
|
it("fail a'b'.c",function() { testInvalid("a'b'.c"); })
|
||||||
it("fail a['b'.c",function() { testInvalid("a['b'.c"); })
|
it("fail a['b'.c",function() { testInvalid("a['b'.c"); })
|
||||||
it("fail a[]",function() { testInvalid("a[]"); })
|
it("fail a[]",function() { testInvalid("a[]"); })
|
||||||
|
Loading…
Reference in New Issue
Block a user