diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js b/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js
index 2787a5c36..b78de9d75 100644
--- a/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js
+++ b/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js
@@ -44,6 +44,7 @@ module.exports = {
user: req.user,
module: req.body.module,
version: req.body.version,
+ url: req.body.url,
req: apiUtils.getRequestLogObject(req)
}
runtimeAPI.nodes.addModule(opts).then(function(info) {
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js
index 074a54469..a89003856 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js
@@ -75,13 +75,16 @@ RED.palette.editor = (function() {
});
})
}
- function installNodeModule(id,version,callback) {
+ function installNodeModule(id,version,url,callback) {
var requestBody = {
module: id
};
if (version) {
requestBody.version = version;
}
+ if (url) {
+ requestBody.url = url;
+ }
$.ajax({
url:"nodes",
type: "POST",
@@ -622,7 +625,7 @@ RED.palette.editor = (function() {
if ($(this).hasClass('disabled')) {
return;
}
- update(entry,loadedIndex[entry.name].version,container,function(err){});
+ update(entry,loadedIndex[entry.name].version,loadedIndex[entry.name].pkg_url,container,function(err){});
})
@@ -872,7 +875,7 @@ RED.palette.editor = (function() {
$('
').appendTo(installTab);
}
- function update(entry,version,container,done) {
+ function update(entry,version,url,container,done) {
if (RED.settings.theme('palette.editable') === false) {
done(new Error('Palette not editable'));
return;
@@ -898,7 +901,7 @@ RED.palette.editor = (function() {
RED.actions.invoke("core:show-event-log");
});
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.name+" "+version);
- installNodeModule(entry.name,version,function(xhr) {
+ installNodeModule(entry.name,version,url,function(xhr) {
spinner.remove();
if (xhr) {
if (xhr.responseJSON) {
@@ -1023,7 +1026,7 @@ RED.palette.editor = (function() {
RED.actions.invoke("core:show-event-log");
});
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.id+" "+entry.version);
- installNodeModule(entry.id,entry.version,function(xhr) {
+ installNodeModule(entry.id,entry.version,entry.pkg_url,function(xhr) {
spinner.remove();
if (xhr) {
if (xhr.responseJSON) {
diff --git a/packages/node_modules/@node-red/registry/lib/installer.js b/packages/node_modules/@node-red/registry/lib/installer.js
index 3562dfb47..e70952832 100644
--- a/packages/node_modules/@node-red/registry/lib/installer.js
+++ b/packages/node_modules/@node-red/registry/lib/installer.js
@@ -32,6 +32,7 @@ var paletteEditorEnabled = false;
var settings;
var moduleRe = /^(@[^/]+?[/])?[^/]+?$/;
var slashRe = process.platform === "win32" ? /\\|[/]/ : /[/]/;
+var pkgurlRe = /^(https?|git(|\+https?|\+ssh|\+file)):\/\//;
function init(runtime) {
events = runtime.events;
@@ -76,14 +77,17 @@ function checkExistingModule(module,version) {
}
return false;
}
-function installModule(module,version) {
+function installModule(module,version,url) {
activePromise = activePromise.then(() => {
//TODO: ensure module is 'safe'
return new Promise((resolve,reject) => {
var installName = module;
var isUpgrade = false;
try {
- if (moduleRe.test(module)) {
+ if (url && pkgurlRe.test(url)) {
+ // Git remote url or Tarball url - check the valid package url
+ installName = url;
+ } else if (moduleRe.test(module)) {
// Simple module name - assume it can be npm installed
if (version) {
installName += "@"+version;
diff --git a/packages/node_modules/@node-red/runtime/lib/api/nodes.js b/packages/node_modules/@node-red/runtime/lib/api/nodes.js
index ee4d3bc1a..fb16cc631 100644
--- a/packages/node_modules/@node-red/runtime/lib/api/nodes.js
+++ b/packages/node_modules/@node-red/runtime/lib/api/nodes.js
@@ -159,6 +159,7 @@ var api = module.exports = {
* @param {User} opts.user - the user calling the api
* @param {String} opts.module - the id of the module to install
* @param {String} opts.version - (optional) the version of the module to install
+ * @param {String} opts.url - (optional) url to install
* @param {Object} opts.req - the request to log (optional)
* @return {Promise} - the node module info
* @memberof @node-red/runtime_nodes
@@ -183,20 +184,20 @@ var api = module.exports = {
return reject(err);
}
}
- runtime.nodes.installModule(opts.module,opts.version).then(function(info) {
- runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version}, opts.req);
+ runtime.nodes.installModule(opts.module,opts.version,opts.url).then(function(info) {
+ runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url}, opts.req);
return resolve(info);
}).catch(function(err) {
if (err.code === 404) {
- runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:"not_found"}, opts.req);
+ runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:"not_found"}, opts.req);
// TODO: code/status
err.status = 404;
} else if (err.code) {
err.status = 400;
- runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:err.code}, opts.req);
+ runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code}, opts.req);
} else {
err.status = 400;
- runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,error:err.code||"unexpected_error",message:err.toString()}, opts.req);
+ runtime.log.audit({event: "nodes.install",module:opts.module,version:opts.version,url:opts.url,error:err.code||"unexpected_error",message:err.toString()}, opts.req);
}
return reject(err);
})
diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/index.js
index fcb153fe1..09aebe6eb 100644
--- a/packages/node_modules/@node-red/runtime/lib/nodes/index.js
+++ b/packages/node_modules/@node-red/runtime/lib/nodes/index.js
@@ -150,10 +150,10 @@ function reportNodeStateChange(info,enabled) {
}
}
-function installModule(module,version) {
+function installModule(module,version,url) {
var existingModule = registry.getModuleInfo(module);
var isUpgrade = !!existingModule;
- return registry.installModule(module,version).then(function(info) {
+ return registry.installModule(module,version,url).then(function(info) {
if (isUpgrade) {
events.emit("runtime-event",{id:"node/upgraded",retain:false,payload:{module:module,version:version}});
} else {
diff --git a/test/unit/@node-red/editor-api/lib/admin/nodes_spec.js b/test/unit/@node-red/editor-api/lib/admin/nodes_spec.js
index ef98fb677..0d373b8d0 100644
--- a/test/unit/@node-red/editor-api/lib/admin/nodes_spec.js
+++ b/test/unit/@node-red/editor-api/lib/admin/nodes_spec.js
@@ -227,7 +227,7 @@ describe("api/admin/nodes", function() {
});
request(app)
.post('/nodes')
- .send({module: 'foo',version:"1.2.3"})
+ .send({module: 'foo',version:"1.2.3",url:"https://example/foo-1.2.3.tgz"})
.expect(200)
.end(function(err,res) {
if (err) {
@@ -238,6 +238,7 @@ describe("api/admin/nodes", function() {
res.body.nodes[0].should.have.property("id","123");
opts.should.have.property("module","foo");
opts.should.have.property("version","1.2.3");
+ opts.should.have.property("url","https://example/foo-1.2.3.tgz");
done();
});
});
@@ -256,7 +257,7 @@ describe("api/admin/nodes", function() {
});
request(app)
.post('/nodes')
- .send({module: 'foo',version:"1.2.3"})
+ .send({module: 'foo',version:"1.2.3",url:"https://example/foo-1.2.3.tgz"})
.expect(400)
.end(function(err,res) {
if (err) {
diff --git a/test/unit/@node-red/registry/lib/installer_spec.js b/test/unit/@node-red/registry/lib/installer_spec.js
index f3bae8b3a..045a81e0d 100644
--- a/test/unit/@node-red/registry/lib/installer_spec.js
+++ b/test/unit/@node-red/registry/lib/installer_spec.js
@@ -121,6 +121,17 @@ describe('nodes/registry/installer', function() {
done();
});
});
+ it("rejects when update requested to existing version and url", function(done) {
+ sinon.stub(typeRegistry,"getModuleInfo", function() {
+ return {
+ version: "0.1.1"
+ }
+ });
+ installer.installModule("this_wont_exist","0.1.1","https://example/foo-0.1.1.tgz").catch(function(err) {
+ err.code.should.be.eql('module_already_loaded');
+ done();
+ });
+ });
it("rejects with generic error", function(done) {
var res = {
code: 1,
@@ -201,6 +212,29 @@ describe('nodes/registry/installer', function() {
done(err);
});
});
+ it("succeeds when url is valid node-red module", function(done) {
+ var nodeInfo = {nodes:{module:"foo",types:["a"]}};
+
+ var res = {
+ code: 0,
+ stdout:"",
+ stderr:""
+ }
+ var p = Promise.resolve(res);
+ p.catch((err)=>{});
+ initInstaller(p)
+
+ var addModule = sinon.stub(registry,"addModule",function(md) {
+ return when.resolve(nodeInfo);
+ });
+
+ installer.installModule("this_wont_exist",null,"https://example/foo-0.1.1.tgz").then(function(info) {
+ info.should.eql(nodeInfo);
+ done();
+ }).catch(function(err) {
+ done(err);
+ });
+ });
});
describe("uninstalls module", function() {