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

Add event log to editor

Shows output from git pull/push and npm install in the editor
This commit is contained in:
Nick O'Leary 2018-10-18 23:49:47 +01:00
parent 2816b3edae
commit b2516117f5
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
17 changed files with 535 additions and 224 deletions

View File

@ -156,6 +156,7 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js", "packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/editor.js", "packages/node_modules/@node-red/editor-client/src/js/ui/editor.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/editors/*.js", "packages/node_modules/@node-red/editor-client/src/js/ui/editors/*.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/event-log.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/tray.js", "packages/node_modules/@node-red/editor-client/src/js/ui/tray.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js", "packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/library.js", "packages/node_modules/@node-red/editor-client/src/js/ui/library.js",

View File

@ -215,6 +215,10 @@
"plusNMore": "+ __count__ more" "plusNMore": "+ __count__ more"
} }
}, },
"eventLog": {
"title": "Event log",
"view": "View log"
},
"diff": { "diff": {
"unresolvedCount": "__count__ unresolved conflict", "unresolvedCount": "__count__ unresolved conflict",
"unresolvedCount_plural": "__count__ unresolved conflicts", "unresolvedCount_plural": "__count__ unresolved conflicts",

View File

@ -19,7 +19,8 @@
"ctrl-alt-r": "core:show-remote-diff", "ctrl-alt-r": "core:show-remote-diff",
"ctrl-alt-n": "core:new-project", "ctrl-alt-n": "core:new-project",
"ctrl-alt-o": "core:open-project", "ctrl-alt-o": "core:open-project",
"ctrl-g v": "core:show-version-control-tab" "ctrl-g v": "core:show-version-control-tab",
"ctrl-shift-l": "core:show-event-log"
}, },
"workspace": { "workspace": {
"backspace": "core:delete-selection", "backspace": "core:delete-selection",

View File

@ -398,6 +398,10 @@ var RED = (function() {
// Refresh flow library to ensure any examples are updated // Refresh flow library to ensure any examples are updated
RED.library.loadFlowLibrary(); RED.library.loadFlowLibrary();
}); });
RED.comms.subscribe("event-log/#", function(topic,payload) {
var id = topic.substring(9);
RED.eventLog.log(id,payload);
});
} }
function showAbout() { function showAbout() {
@ -436,6 +440,7 @@ var RED = (function() {
// null, // null,
{id:"menu-item-palette",label:RED._("menu.label.palette.show"),toggle:true,onselect:"core:toggle-palette", selected: true}, {id:"menu-item-palette",label:RED._("menu.label.palette.show"),toggle:true,onselect:"core:toggle-palette", selected: true},
{id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:"core:toggle-sidebar", selected: true}, {id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:"core:toggle-sidebar", selected: true},
{id:"menu-item-event-log",label:RED._("eventLog.title"),onselect:"core:show-event-log"},
null null
]}); ]});
menuOptions.push(null); menuOptions.push(null);
@ -483,6 +488,7 @@ var RED = (function() {
RED.library.init(); RED.library.init();
RED.keyboard.init(); RED.keyboard.init();
RED.palette.init(); RED.palette.init();
RED.eventLog.init();
if (RED.settings.theme('palette.editable') !== false) { if (RED.settings.theme('palette.editable') !== false) {
RED.palette.editor.init(); RED.palette.editor.init();
} else { } else {

View File

@ -0,0 +1,121 @@
/**
* 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.
**/
RED.eventLog = (function() {
var template = '<script type="text/x-red" data-template-name="_eventLog"><div class="form-row node-text-editor-row"><div style="height: 100%;min-height: 150px;" class="node-text-editor" id="event-log-editor"></div></div></script>';
var eventLogEditor;
var backlog = [];
var shown = false;
function appendLogLine(line) {
backlog.push(line);
if (backlog.length > 500) {
backlog = backlog.slice(-500);
}
if (eventLogEditor) {
eventLogEditor.getSession().insert({
row: eventLogEditor.getSession().getLength(),
column: 0
}, "\n" + line);
eventLogEditor.scrollToLine(eventLogEditor.getSession().getLength());
}
}
return {
init: function() {
$(template).appendTo(document.body);
RED.actions.add("core:show-event-log",RED.eventLog.show);
},
show: function() {
if (shown) {
return;
}
shown = true;
var type = "_eventLog"
var trayOptions = {
title: RED._("eventLog.title"),
width: Infinity,
buttons: [
{
id: "node-dialog-close",
text: RED._("common.label.close"),
click: function() {
RED.tray.close();
}
}
],
resize: function(dimensions) {
var rows = $("#dialog-form>div:not(.node-text-editor-row)");
var editorRow = $("#dialog-form>div.node-text-editor-row");
var height = $("#dialog-form").height();
for (var i=0;i<rows.size();i++) {
height -= $(rows[i]).outerHeight(true);
}
height -= (parseInt($("#dialog-form").css("marginTop"))+parseInt($("#dialog-form").css("marginBottom")));
$(".node-text-editor").css("height",height+"px");
eventLogEditor.resize();
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
var dialogForm = RED.editor.buildEditForm(tray.find('.editor-tray-body'),'dialog-form',type,'editor');
eventLogEditor = RED.editor.createEditor({
id: 'event-log-editor',
value: backlog.join("\n"),
lineNumbers: false,
readOnly: true,
options: {
showPrintMargin: false
}
});
setTimeout(function() {
eventLogEditor.scrollToLine(eventLogEditor.getSession().getLength());
},200);
dialogForm.i18n();
},
close: function() {
eventLogEditor.destroy();
eventLogEditor = null;
shown = false;
},
show: function() {}
}
RED.tray.show(trayOptions);
},
log: function(id,payload) {
var ts = (new Date(payload.ts)).toISOString()+" ";
if (payload.type) {
ts += "["+payload.type+"] "
}
if (payload.data) {
var data = payload.data;
if (data.endsWith('\n')) {
data = data.substring(0,data.length-1);
}
var lines = data.split(/\n/);
lines.forEach(function(line) {
appendLogLine(ts+line);
})
}
},
startEvent: function(name) {
backlog.push("");
backlog.push("-----------------------------------------------------------");
backlog.push((new Date()).toISOString()+" "+name);
backlog.push("");
}
}
})();

View File

@ -863,11 +863,35 @@ RED.palette.editor = (function() {
class: "primary palette-module-install-confirm-button-update", class: "primary palette-module-install-confirm-button-update",
click: function() { click: function() {
var spinner = RED.utils.addSpinnerOverlay(container, true); var spinner = RED.utils.addSpinnerOverlay(container, true);
var buttonRow = $('<div style="position: relative;bottom: calc(50% + 17px); padding-right: 10px;text-align: right;"></div>').appendTo(spinner);
$('<button class="editor-button"></button>').text(RED._("eventLog.view")).appendTo(buttonRow).click(function(evt) {
evt.preventDefault();
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,function(xhr) {
spinner.remove(); spinner.remove();
if (xhr) { if (xhr) {
if (xhr.responseJSON) { if (xhr.responseJSON) {
RED.notify(RED._('palette.editor.errors.updateFailed',{module: entry.name,message:xhr.responseJSON.message})); var notification = RED.notify(RED._('palette.editor.errors.updateFailed',{module: entry.name,message:xhr.responseJSON.message}),{
type: 'error',
modal: true,
fixed: true,
buttons: [
{
text: RED._("common.label.close"),
click: function() {
notification.close();
}
},{
text: RED._("eventLog.view"),
click: function() {
notification.close();
RED.actions.invoke("core:show-event-log");
}
}
]
});
} }
} }
done(xhr); done(xhr);
@ -898,12 +922,35 @@ RED.palette.editor = (function() {
class: "primary palette-module-install-confirm-button-remove", class: "primary palette-module-install-confirm-button-remove",
click: function() { click: function() {
var spinner = RED.utils.addSpinnerOverlay(container, true); var spinner = RED.utils.addSpinnerOverlay(container, true);
var buttonRow = $('<div style="position: relative;bottom: calc(50% + 17px); padding-right: 10px;text-align: right;"></div>').appendTo(spinner);
$('<button class="editor-button"></button>').text(RED._("eventLog.view")).appendTo(buttonRow).click(function(evt) {
evt.preventDefault();
RED.actions.invoke("core:show-event-log");
});
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.remove")+" : "+entry.name);
removeNodeModule(entry.name, function(xhr) { removeNodeModule(entry.name, function(xhr) {
spinner.remove(); spinner.remove();
if (xhr) { if (xhr) {
if (xhr.responseJSON) { if (xhr.responseJSON) {
RED.notify(RED._('palette.editor.errors.removeFailed',{module: entry.name,message:xhr.responseJSON.message})); var notification = RED.notify(RED._('palette.editor.errors.removeFailed',{module: entry.name,message:xhr.responseJSON.message}),{
} type: 'error',
modal: true,
fixed: true,
buttons: [
{
text: RED._("common.label.close"),
click: function() {
notification.close();
}
},{
text: RED._("eventLog.view"),
click: function() {
notification.close();
RED.actions.invoke("core:show-event-log");
}
}
]
}); }
} }
}) })
notification.close(); notification.close();
@ -940,11 +987,36 @@ RED.palette.editor = (function() {
class: "primary palette-module-install-confirm-button-install", class: "primary palette-module-install-confirm-button-install",
click: function() { click: function() {
var spinner = RED.utils.addSpinnerOverlay(container, true); var spinner = RED.utils.addSpinnerOverlay(container, true);
var buttonRow = $('<div style="position: relative;bottom: calc(50% + 17px); padding-right: 10px;text-align: right;"></div>').appendTo(spinner);
$('<button class="editor-button"></button>').text(RED._("eventLog.view")).appendTo(buttonRow).click(function(evt) {
evt.preventDefault();
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,function(xhr) {
spinner.remove(); spinner.remove();
if (xhr) { if (xhr) {
if (xhr.responseJSON) { if (xhr.responseJSON) {
RED.notify(RED._('palette.editor.errors.installFailed',{module: entry.id,message:xhr.responseJSON.message})); var notification = RED.notify(RED._('palette.editor.errors.installFailed',{module: entry.id,message:xhr.responseJSON.message}),{
type: 'error',
modal: true,
fixed: true,
buttons: [
{
text: RED._("common.label.close"),
click: function() {
notification.close();
}
},{
text: RED._("eventLog.view"),
click: function() {
notification.close();
RED.actions.invoke("core:show-event-log");
}
}
]
});
} }
} }
done(xhr); done(xhr);

View File

@ -870,7 +870,13 @@ RED.sidebar.versionControl = (function() {
.click(function(e) { .click(function(e) {
e.preventDefault(); e.preventDefault();
var spinner = utils.addSpinnerOverlay(remoteBox).addClass("projects-dialog-spinner-contain"); var spinner = utils.addSpinnerOverlay(remoteBox).addClass("projects-dialog-spinner-contain");
var buttonRow = $('<div style="position: relative; bottom: 60px;"></div>').appendTo(spinner);
$('<button class="editor-button"></button>').text(RED._("eventLog.view")).appendTo(buttonRow).click(function(evt) {
evt.preventDefault();
RED.actions.invoke("core:show-event-log");
});
var activeProject = RED.projects.getActiveProject(); var activeProject = RED.projects.getActiveProject();
RED.eventLog.startEvent("Push changes"+(activeProject.git.branches.remoteAlt?(" : "+activeProject.git.branches.remoteAlt):""));
var url = "projects/"+activeProject.name+"/push"; var url = "projects/"+activeProject.name+"/push";
if (activeProject.git.branches.remoteAlt) { if (activeProject.git.branches.remoteAlt) {
url+="/"+activeProject.git.branches.remoteAlt; url+="/"+activeProject.git.branches.remoteAlt;
@ -914,7 +920,13 @@ RED.sidebar.versionControl = (function() {
var pullRemote = function(options) { var pullRemote = function(options) {
options = options || {}; options = options || {};
var spinner = utils.addSpinnerOverlay(remoteBox).addClass("projects-dialog-spinner-contain"); var spinner = utils.addSpinnerOverlay(remoteBox).addClass("projects-dialog-spinner-contain");
var buttonRow = $('<div style="position: relative; bottom: 60px;"></div>').appendTo(spinner);
$('<button class="editor-button"></button>').text(RED._("eventLog.view")).appendTo(buttonRow).click(function(evt) {
evt.preventDefault();
RED.actions.invoke("core:show-event-log");
});
var activeProject = RED.projects.getActiveProject(); var activeProject = RED.projects.getActiveProject();
RED.eventLog.startEvent("Pull changes"+(activeProject.git.branches.remoteAlt?(" : "+activeProject.git.branches.remoteAlt):""));
var url = "projects/"+activeProject.name+"/pull"; var url = "projects/"+activeProject.name+"/pull";
if (activeProject.git.branches.remoteAlt) { if (activeProject.git.branches.remoteAlt) {
url+="/"+activeProject.git.branches.remoteAlt; url+="/"+activeProject.git.branches.remoteAlt;

View File

@ -6,3 +6,16 @@
border-top-right-radius: 4px; border-top-right-radius: 4px;
border-bottom-right-radius: 4px; border-bottom-right-radius: 4px;
} }
#event-log-editor {
.ace_scroller {
background: #444;
color: #dd9;
}
.ace_active-line {
background: #666 !important;
}
.ace_selection {
background: #999 !important;
}
}

View File

@ -21,6 +21,7 @@ var fs = require("fs");
var registry = require("./registry"); var registry = require("./registry");
var library = require("./library"); var library = require("./library");
var log; var log;
var exec;
var events; var events;
@ -36,6 +37,7 @@ function init(runtime) {
events = runtime.events; events = runtime.events;
settings = runtime.settings; settings = runtime.settings;
log = runtime.log; log = runtime.log;
exec = runtime.exec;
} }
var activePromise = Promise.resolve(); var activePromise = Promise.resolve();
@ -104,50 +106,40 @@ function installModule(module,version) {
var installDir = settings.userDir || process.env.NODE_RED_HOME || "."; var installDir = settings.userDir || process.env.NODE_RED_HOME || ".";
var args = ['install','--save','--save-prefix="~"','--production',installName]; var args = ['install','--save','--save-prefix="~"','--production',installName];
log.trace(npmCommand + JSON.stringify(args)); log.trace(npmCommand + JSON.stringify(args));
var child = child_process.spawn(npmCommand,args,{ exec.run(npmCommand,args,{
cwd: installDir, cwd: installDir
shell: true }, true).then(result => {
}); if (!isUpgrade) {
var output = ""; log.info(log._("server.install.installed",{name:module}));
child.stdout.on('data', (data) => { resolve(require("./index").addModule(module).then(reportAddedModules));
output += data;
});
child.stderr.on('data', (data) => {
output += data;
});
child.on('close', (code) => {
if (code !== 0) {
var e;
var lookFor404 = new RegExp(" 404 .*"+module,"m");
var lookForVersionNotFound = new RegExp("version not found: "+module+"@"+version,"m");
if (lookFor404.test(output)) {
log.warn(log._("server.install.install-failed-not-found",{name:module}));
e = new Error("Module not found");
e.code = 404;
reject(e);
} else if (isUpgrade && lookForVersionNotFound.test(output)) {
log.warn(log._("server.install.upgrade-failed-not-found",{name:module}));
e = new Error("Module not found");
e.code = 404;
reject(e);
} else {
log.warn(log._("server.install.install-failed-long",{name:module}));
log.warn("------------------------------------------");
log.warn(output);
log.warn("------------------------------------------");
reject(new Error(log._("server.install.install-failed")));
}
} else { } else {
if (!isUpgrade) { log.info(log._("server.install.upgraded",{name:module, version:version}));
log.info(log._("server.install.installed",{name:module})); events.emit("runtime-event",{id:"restart-required",payload:{type:"warning",text:"notification.warnings.restartRequired"},retain:true});
resolve(require("./index").addModule(module).then(reportAddedModules)); resolve(require("./registry").setModulePendingUpdated(module,version));
} else {
log.info(log._("server.install.upgraded",{name:module, version:version}));
events.emit("runtime-event",{id:"restart-required",payload:{type:"warning",text:"notification.warnings.restartRequired"},retain:true});
resolve(require("./registry").setModulePendingUpdated(module,version));
}
} }
}); }).catch(result => {
var output = result.stderr;
var e;
var lookFor404 = new RegExp(" 404 .*"+module,"m");
var lookForVersionNotFound = new RegExp("version not found: "+module+"@"+version,"m");
if (lookFor404.test(output)) {
log.warn(log._("server.install.install-failed-not-found",{name:module}));
e = new Error("Module not found");
e.code = 404;
reject(e);
} else if (isUpgrade && lookForVersionNotFound.test(output)) {
log.warn(log._("server.install.upgrade-failed-not-found",{name:module}));
e = new Error("Module not found");
e.code = 404;
reject(e);
} else {
log.warn(log._("server.install.install-failed-long",{name:module}));
log.warn("------------------------------------------");
log.warn(output);
log.warn("------------------------------------------");
reject(new Error(log._("server.install.install-failed")));
}
})
}); });
}).catch(err => { }).catch(err => {
// In case of error, reset activePromise to be resolvable // In case of error, reset activePromise to be resolvable
@ -208,25 +200,21 @@ function uninstallModule(module) {
var args = ['remove','--save',module]; var args = ['remove','--save',module];
log.trace(npmCommand + JSON.stringify(args)); log.trace(npmCommand + JSON.stringify(args));
var child = child_process.execFile(npmCommand,args, exec.run(npmCommand,args,{
{ cwd: installDir,
cwd: installDir },true).then(result => {
}, log.info(log._("server.install.uninstalled",{name:module}));
function(err, stdin, stdout) { reportRemovedModules(list);
if (err) { library.removeExamplesDir(module);
log.warn(log._("server.install.uninstall-failed-long",{name:module})); resolve(list);
log.warn("------------------------------------------"); }).catch(result => {
log.warn(err.toString()); var output = result.stderr;
log.warn("------------------------------------------"); log.warn(log._("server.install.uninstall-failed-long",{name:module}));
reject(new Error(log._("server.install.uninstall-failed",{name:module}))); log.warn("------------------------------------------");
} else { log.warn(output.toString());
log.info(log._("server.install.uninstalled",{name:module})); log.warn("------------------------------------------");
reportRemovedModules(list); reject(new Error(log._("server.install.uninstall-failed",{name:module})));
library.removeExamplesDir(module); });
resolve(list);
}
}
);
}); });
}).catch(err => { }).catch(err => {
// In case of error, reset activePromise to be resolvable // In case of error, reset activePromise to be resolvable

View File

@ -42,6 +42,21 @@ function handleRuntimeEvent(event) {
runtime.log.trace("runtime event: "+JSON.stringify(event)); runtime.log.trace("runtime event: "+JSON.stringify(event));
publish("notification/"+event.id,event.payload||{},event.retain); publish("notification/"+event.id,event.payload||{},event.retain);
} }
function handleEventLog(event) {
var type = event.payload.type;
var id = event.id;
if (event.payload.data) {
var data = event.payload.data;
if (data.endsWith('\n')) {
data = data.substring(0,data.length-1);
}
var lines = data.split(/\n/);
lines.forEach(line => {
runtime.log.debug((type?("["+type+"] "):"")+line)
})
}
publish("event-log/"+event.id,event.payload||{});
}
function publish(topic,data,retain) { function publish(topic,data,retain) {
if (retain) { if (retain) {
@ -64,6 +79,8 @@ var api = module.exports = {
runtime.events.on("runtime-event",handleRuntimeEvent); runtime.events.on("runtime-event",handleRuntimeEvent);
runtime.events.removeListener("comms",handleCommsEvent); runtime.events.removeListener("comms",handleCommsEvent);
runtime.events.on("comms",handleCommsEvent); runtime.events.on("comms",handleCommsEvent);
runtime.events.removeListener("event-log",handleEventLog);
runtime.events.on("event-log",handleEventLog);
}, },
/** /**

View File

@ -0,0 +1,69 @@
/**
* 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.
**/
const child_process = require('child_process');
const { util } = require('@node-red/util');
var events;
function logLines(id,type,data) {
events.emit("event-log", {id:id,payload:{ts: Date.now(),data:data,type:type}});
}
module.exports = {
init: function(_runtime) {
events = _runtime.events;
},
run: function(command,args,options,emit) {
var invocationId = util.generateId();
emit && events.emit("event-log", {ts: Date.now(),id:invocationId,payload:{ts: Date.now(),data:command+" "+args.join(" ")}});
return new Promise((resolve, reject) => {
let stdout = "";
let stderr = "";
const child = child_process.spawn(command,args,options);
child.stdout.on('data', (data) => {
const str = ""+data;
stdout += str;
emit && logLines(invocationId,"out",str);
});
child.stderr.on('data', (data) => {
const str = ""+data;
stderr += str;
emit && logLines(invocationId,"err",str);
});
child.on('error', function(err) {
stderr = err.toString();
emit && logLines(invocationId,"err",stderr);
})
child.on('close', (code) => {
let result = {
code: code,
stdout: stdout,
stderr: stderr
}
emit && events.emit("event-log", {id:invocationId,payload:{ts: Date.now(),data:"rc="+code}});
if (code === 0) {
resolve(result)
} else {
reject(result);
}
});
})
}
}

View File

@ -23,6 +23,7 @@ var storage = require("./storage");
var library = require("./library"); var library = require("./library");
var events = require("./events"); var events = require("./events");
var settings = require("./settings"); var settings = require("./settings");
var exec = require("./exec");
var express = require("express"); var express = require("express");
var path = require('path'); var path = require('path');
@ -71,6 +72,7 @@ function init(userSettings,_redUtil,_adminApi) {
redNodes.init(runtime); redNodes.init(runtime);
library.init(runtime); library.init(runtime);
externalAPI.init(runtime); externalAPI.init(runtime);
exec.init(runtime);
} }
var version; var version;
@ -248,6 +250,7 @@ var runtime = {
events: events, events: events,
nodes: redNodes, nodes: redNodes,
library: library, library: library,
exec: exec,
util: require("@node-red/util").util, util: require("@node-red/util").util,
get adminApi() { return adminApi }, get adminApi() { return adminApi },
get nodeApp() { return nodeApp }, get nodeApp() { return nodeApp },

View File

@ -15,8 +15,9 @@
**/ **/
var when = require('when'); var when = require('when');
var exec = require('child_process').exec;
var spawn = require('child_process').spawn; var exec = require("../../../../exec");
var authResponseServer = require('./authServer').ResponseServer; var authResponseServer = require('./authServer').ResponseServer;
var sshResponseServer = require('./authServer').ResponseSSHServer; var sshResponseServer = require('./authServer').ResponseSSHServer;
var clone = require('clone'); var clone = require('clone');
@ -26,83 +27,70 @@ var gitCommand = "git";
var gitVersion; var gitVersion;
var log; var log;
function runGitCommand(args,cwd,env) { function runGitCommand(args,cwd,env,emit) {
log.trace(gitCommand + JSON.stringify(args)); log.trace(gitCommand + JSON.stringify(args));
return when.promise(function(resolve,reject) { args.unshift("credential.helper=")
args.unshift("credential.helper=") args.unshift("-c");
args.unshift("-c"); return exec.run(gitCommand, args, {cwd:cwd, env:env}, emit).then(result => {
var child = spawn(gitCommand, args, {cwd:cwd, detached:true, env:env}); return result.stdout;
var stdout = ""; }).catch(result => {
var stderr = ""; var err = new Error(stderr);
child.stdout.on('data', function(data) { var stdout = result.stdout;
stdout += data; var stderr = result.stderr;
}); err.stdout = stdout;
child.stderr.on('data', function(data) { err.stderr = stderr;
stderr += data; if (/Connection refused/i.test(stderr)) {
}); err.code = "git_connection_failed";
child.on('error', function(err) { } else if (/Connection timed out/i.test(stderr)) {
stderr = err.toString(); err.code = "git_connection_failed";
}) } else if (/fatal: could not read/i.test(stderr)) {
child.on('close', function(code) { // Username/Password
if (code !== 0) { err.code = "git_auth_failed";
var err = new Error(stderr); } else if(/HTTP Basic: Access denied/i.test(stderr)) {
err.stdout = stdout; err.code = "git_auth_failed";
err.stderr = stderr; } else if(/Permission denied \(publickey\)/i.test(stderr)) {
if (/Connection refused/i.test(stderr)) { err.code = "git_auth_failed";
err.code = "git_connection_failed"; } else if(/Host key verification failed/i.test(stderr)) {
} else if (/Connection timed out/i.test(stderr)) { // TODO: handle host key verification errors separately
err.code = "git_connection_failed"; err.code = "git_auth_failed";
} else if (/fatal: could not read/i.test(stderr)) { } else if (/commit your changes or stash/i.test(stderr)) {
// Username/Password err.code = "git_local_overwrite";
err.code = "git_auth_failed"; } else if (/CONFLICT/.test(err.stdout)) {
} else if(/HTTP Basic: Access denied/i.test(stderr)) { err.code = "git_pull_merge_conflict";
err.code = "git_auth_failed"; } else if (/not fully merged/i.test(stderr)) {
} else if(/Permission denied \(publickey\)/i.test(stderr)) { err.code = "git_delete_branch_unmerged";
err.code = "git_auth_failed"; } else if (/remote .* already exists/i.test(stderr)) {
} else if(/Host key verification failed/i.test(stderr)) { err.code = "git_remote_already_exists";
// TODO: handle host key verification errors separately } else if (/does not appear to be a git repository/i.test(stderr)) {
err.code = "git_auth_failed"; err.code = "git_not_a_repository";
} else if (/commit your changes or stash/i.test(stderr)) { } else if (/Repository not found/i.test(stderr)) {
err.code = "git_local_overwrite"; err.code = "git_repository_not_found";
} else if (/CONFLICT/.test(err.stdout)) { } else if (/repository '.*' does not exist/i.test(stderr)) {
err.code = "git_pull_merge_conflict"; err.code = "git_repository_not_found";
} else if (/not fully merged/i.test(stderr)) { } else if (/refusing to merge unrelated histories/i.test(stderr)) {
err.code = "git_delete_branch_unmerged"; err.code = "git_pull_unrelated_history"
} else if (/remote .* already exists/i.test(stderr)) { } else if (/Please tell me who you are/i.test(stderr)) {
err.code = "git_remote_already_exists"; err.code = "git_missing_user";
} else if (/does not appear to be a git repository/i.test(stderr)) { } else if (/name consists only of disallowed characters/i.test(stderr)) {
err.code = "git_not_a_repository"; err.code = "git_missing_user";
} else if (/Repository not found/i.test(stderr)) { }
err.code = "git_repository_not_found"; throw err;
} else if (/repository '.*' does not exist/i.test(stderr)) { })
err.code = "git_repository_not_found";
} else if (/refusing to merge unrelated histories/i.test(stderr)) {
err.code = "git_pull_unrelated_history"
} else if (/Please tell me who you are/i.test(stderr)) {
err.code = "git_missing_user";
} else if (/name consists only of disallowed characters/i.test(stderr)) {
err.code = "git_missing_user";
}
return reject(err);
}
resolve(stdout);
});
});
} }
function runGitCommandWithAuth(args,cwd,auth) { function runGitCommandWithAuth(args,cwd,auth,emit) {
return authResponseServer(auth).then(function(rs) { return authResponseServer(auth).then(function(rs) {
var commandEnv = clone(process.env); var commandEnv = clone(process.env);
commandEnv.GIT_ASKPASS = path.join(__dirname,"node-red-ask-pass.sh"); commandEnv.GIT_ASKPASS = path.join(__dirname,"node-red-ask-pass.sh");
commandEnv.NODE_RED_GIT_NODE_PATH = process.execPath; commandEnv.NODE_RED_GIT_NODE_PATH = process.execPath;
commandEnv.NODE_RED_GIT_SOCK_PATH = rs.path; commandEnv.NODE_RED_GIT_SOCK_PATH = rs.path;
commandEnv.NODE_RED_GIT_ASKPASS_PATH = path.join(__dirname,"authWriter.js"); commandEnv.NODE_RED_GIT_ASKPASS_PATH = path.join(__dirname,"authWriter.js");
return runGitCommand(args,cwd,commandEnv).finally(function() { return runGitCommand(args,cwd,commandEnv,emit).finally(function() {
rs.close(); rs.close();
}); });
}) })
} }
function runGitCommandWithSSHCommand(args,cwd,auth) { function runGitCommandWithSSHCommand(args,cwd,auth,emit) {
return sshResponseServer(auth).then(function(rs) { return sshResponseServer(auth).then(function(rs) {
var commandEnv = clone(process.env); var commandEnv = clone(process.env);
commandEnv.SSH_ASKPASS = path.join(__dirname,"node-red-ask-pass.sh"); commandEnv.SSH_ASKPASS = path.join(__dirname,"node-red-ask-pass.sh");
@ -116,7 +104,7 @@ function runGitCommandWithSSHCommand(args,cwd,auth) {
// GIT_SSH_COMMAND - added in git 2.3.0 // GIT_SSH_COMMAND - added in git 2.3.0
commandEnv.GIT_SSH_COMMAND = "ssh -i " + auth.key_path + " -F /dev/null"; commandEnv.GIT_SSH_COMMAND = "ssh -i " + auth.key_path + " -F /dev/null";
// console.log('commandEnv:', commandEnv); // console.log('commandEnv:', commandEnv);
return runGitCommand(args,cwd,commandEnv).finally(function() { return runGitCommand(args,cwd,commandEnv,emit).finally(function() {
rs.close(); rs.close();
}); });
}) })
@ -448,13 +436,13 @@ module.exports = {
var promise; var promise;
if (auth) { if (auth) {
if ( auth.key_path ) { if ( auth.key_path ) {
promise = runGitCommandWithSSHCommand(args,cwd,auth); promise = runGitCommandWithSSHCommand(args,cwd,auth,true);
} }
else { else {
promise = runGitCommandWithAuth(args,cwd,auth); promise = runGitCommandWithAuth(args,cwd,auth,true);
} }
} else { } else {
promise = runGitCommand(args,cwd) promise = runGitCommand(args,cwd,undefined,true)
} }
return promise; return promise;
// .catch(function(err) { // .catch(function(err) {
@ -485,13 +473,13 @@ module.exports = {
var promise; var promise;
if (auth) { if (auth) {
if ( auth.key_path ) { if ( auth.key_path ) {
promise = runGitCommandWithSSHCommand(args,cwd,auth); promise = runGitCommandWithSSHCommand(args,cwd,auth,true);
} }
else { else {
promise = runGitCommandWithAuth(args,cwd,auth); promise = runGitCommandWithAuth(args,cwd,auth,true);
} }
} else { } else {
promise = runGitCommand(args,cwd) promise = runGitCommand(args,cwd,undefined,true)
} }
return promise.catch(function(err) { return promise.catch(function(err) {
if (err.code === 'git_error') { if (err.code === 'git_error') {
@ -517,13 +505,13 @@ module.exports = {
args.push("."); args.push(".");
if (auth) { if (auth) {
if ( auth.key_path ) { if ( auth.key_path ) {
return runGitCommandWithSSHCommand(args,cwd,auth); return runGitCommandWithSSHCommand(args,cwd,auth,true);
} }
else { else {
return runGitCommandWithAuth(args,cwd,auth); return runGitCommandWithAuth(args,cwd,auth,true);
} }
} else { } else {
return runGitCommand(args,cwd); return runGitCommand(args,cwd,undefined,true);
} }
}, },
getStatus: getStatus, getStatus: getStatus,

View File

@ -30,19 +30,22 @@ module.exports = {
log.init(settings); log.init(settings);
i18n.init(); i18n.init();
}, },
/** /**
* Logging utilities * Logging utilities
* @see module:@node-red/util.module:log * @see module:@node-red/util.module:log
*/ */
log: log, log: log,
/** /**
* Internationalization utilities * Internationalization utilities
* @see module:@node-red/util.module:i18n * @see module:@node-red/util.module:i18n
*/ */
i18n: i18n, i18n: i18n,
/** /**
* General utilities * General utilities
* @see module:@node-red/util.module:util * @see module:@node-red/util.module:util
*/ */
util: util util: util,
} }

View File

@ -38,18 +38,6 @@ function checkVersion(userSettings) {
} }
} }
function checkBuild() {
console.log("Skipping red.js/checkBuild");
// var editorFile = path.resolve(path.join(__dirname,"..","..","..","..","public","red","red.min.js"));
// try {
// var stats = fs.statSync(editorFile);
// } catch(err) {
// var e = new Error("Node-RED not built");
// e.code = "not_built";
// throw e;
// }
}
module.exports = { module.exports = {
init: function(httpServer,userSettings) { init: function(httpServer,userSettings) {
if (!userSettings) { if (!userSettings) {
@ -59,7 +47,6 @@ module.exports = {
if (!userSettings.SKIP_BUILD_CHECK) { if (!userSettings.SKIP_BUILD_CHECK) {
checkVersion(userSettings); checkVersion(userSettings);
checkBuild();
} }
if (!userSettings.coreNodesDir) { if (!userSettings.coreNodesDir) {

View File

@ -21,9 +21,6 @@ var path = require("path");
var fs = require('fs'); var fs = require('fs');
var EventEmitter = require('events'); var EventEmitter = require('events');
var child_process = require('child_process');
var NR_TEST_UTILS = require("nr-test-utils"); var NR_TEST_UTILS = require("nr-test-utils");
var installer = NR_TEST_UTILS.require("@node-red/registry/lib/installer"); var installer = NR_TEST_UTILS.require("@node-red/registry/lib/installer");
@ -42,16 +39,21 @@ describe('nodes/registry/installer', function() {
_: function() { return "abc"} _: function() { return "abc"}
} }
before(function() { beforeEach(function() {
installer.init({log:mockLog, settings:{}, events: new EventEmitter()}); installer.init({log:mockLog, settings:{}, events: new EventEmitter(), exec: {
run: function() {
return Promise.resolve("");
}
}});
}); });
function initInstaller(execResult) {
installer.init({log:mockLog, settings:{}, events: new EventEmitter(), exec: {
run: function() {
return execResult;
}
}});
}
afterEach(function() { afterEach(function() {
if (child_process.spawn.restore) {
child_process.spawn.restore();
}
if (child_process.execFile.restore) {
child_process.execFile.restore();
}
if (registry.addModule.restore) { if (registry.addModule.restore) {
registry.addModule.restore(); registry.addModule.restore();
} }
@ -76,39 +78,33 @@ describe('nodes/registry/installer', function() {
describe("installs module", function() { describe("installs module", function() {
it("rejects when npm returns a 404", function(done) { it("rejects when npm returns a 404", function(done) {
sinon.stub(child_process,"spawn",function(cmd,args,opt) { var res = {
var ee = new EventEmitter(); code: 1,
ee.stdout = new EventEmitter(); stdout:"",
ee.stderr = new EventEmitter(); stderr:" 404 this_wont_exist"
setTimeout(function() { }
ee.stderr.emit('data'," 404 this_wont_exist"); var p = Promise.reject(res);
ee.emit('close',1); p.catch((err)=>{});
},10) initInstaller(p)
return ee;
});
installer.installModule("this_wont_exist").catch(function(err) { installer.installModule("this_wont_exist").catch(function(err) {
err.should.have.property("code",404); err.should.have.property("code",404);
done(); done();
}); });
}); });
it("rejects when npm does not find specified version", function(done) { it("rejects when npm does not find specified version", function(done) {
sinon.stub(child_process,"spawn",function(cmd,args,opt) { var res = {
var ee = new EventEmitter(); code: 1,
ee.stdout = new EventEmitter(); stdout:"",
ee.stderr = new EventEmitter(); stderr:" version not found: this_wont_exist@0.1.2"
setTimeout(function() { }
ee.stderr.emit('data'," version not found: this_wont_exist@0.1.2"); var p = Promise.reject(res);
ee.emit('close',1); p.catch((err)=>{});
},10) initInstaller(p)
return ee;
});
sinon.stub(typeRegistry,"getModuleInfo", function() { sinon.stub(typeRegistry,"getModuleInfo", function() {
return { return {
version: "0.1.1" version: "0.1.1"
} }
}); });
installer.installModule("this_wont_exist","0.1.2").catch(function(err) { installer.installModule("this_wont_exist","0.1.2").catch(function(err) {
err.code.should.be.eql(404); err.code.should.be.eql(404);
done(); done();
@ -126,17 +122,14 @@ describe('nodes/registry/installer', function() {
}); });
}); });
it("rejects with generic error", function(done) { it("rejects with generic error", function(done) {
sinon.stub(child_process,"spawn",function(cmd,args,opt,cb) { var res = {
var ee = new EventEmitter(); code: 1,
ee.stdout = new EventEmitter(); stdout:"",
ee.stderr = new EventEmitter(); stderr:" kaboom!"
setTimeout(function() { }
ee.stderr.emit('data'," kaboom!"); var p = Promise.reject(res);
ee.emit('close',1); p.catch((err)=>{});
},10) initInstaller(p)
return ee;
});
installer.installModule("this_wont_exist").then(function() { installer.installModule("this_wont_exist").then(function() {
done(new Error("Unexpected success")); done(new Error("Unexpected success"));
}).catch(function(err) { }).catch(function(err) {
@ -145,15 +138,16 @@ describe('nodes/registry/installer', function() {
}); });
it("succeeds when module is found", function(done) { it("succeeds when module is found", function(done) {
var nodeInfo = {nodes:{module:"foo",types:["a"]}}; var nodeInfo = {nodes:{module:"foo",types:["a"]}};
sinon.stub(child_process,"spawn",function(cmd,args,opt) {
var ee = new EventEmitter(); var res = {
ee.stdout = new EventEmitter(); code: 0,
ee.stderr = new EventEmitter(); stdout:"",
setTimeout(function() { stderr:""
ee.emit('close',0); }
},10) var p = Promise.resolve(res);
return ee; p.catch((err)=>{});
}); initInstaller(p)
var addModule = sinon.stub(registry,"addModule",function(md) { var addModule = sinon.stub(registry,"addModule",function(md) {
return when.resolve(nodeInfo); return when.resolve(nodeInfo);
}); });
@ -191,15 +185,15 @@ describe('nodes/registry/installer', function() {
return when.resolve(nodeInfo); return when.resolve(nodeInfo);
}); });
var resourcesDir = path.resolve(path.join(__dirname,"resources","local","TestNodeModule","node_modules","TestNodeModule")); var resourcesDir = path.resolve(path.join(__dirname,"resources","local","TestNodeModule","node_modules","TestNodeModule"));
sinon.stub(child_process,"spawn",function(cmd,args,opt) {
var ee = new EventEmitter(); var res = {
ee.stdout = new EventEmitter(); code: 0,
ee.stderr = new EventEmitter(); stdout:"",
setTimeout(function() { stderr:""
ee.emit('close',0); }
},10) var p = Promise.resolve(res);
return ee; p.catch((err)=>{});
}); initInstaller(p)
installer.installModule(resourcesDir).then(function(info) { installer.installModule(resourcesDir).then(function(info) {
info.should.eql(nodeInfo); info.should.eql(nodeInfo);
done(); done();
@ -226,9 +220,14 @@ describe('nodes/registry/installer', function() {
var removeModule = sinon.stub(registry,"removeModule",function(md) { var removeModule = sinon.stub(registry,"removeModule",function(md) {
return when.resolve(nodeInfo); return when.resolve(nodeInfo);
}); });
sinon.stub(child_process,"execFile",function(cmd,args,opt,cb) { var res = {
cb(new Error("test_error"),"",""); code: 1,
}); stdout:"",
stderr:"error"
}
var p = Promise.reject(res);
p.catch((err)=>{});
initInstaller(p)
installer.uninstallModule("this_wont_exist").then(function() { installer.uninstallModule("this_wont_exist").then(function() {
done(new Error("Unexpected success")); done(new Error("Unexpected success"));
@ -244,9 +243,14 @@ describe('nodes/registry/installer', function() {
var getModuleInfo = sinon.stub(registry,"getModuleInfo",function(md) { var getModuleInfo = sinon.stub(registry,"getModuleInfo",function(md) {
return {nodes:[]}; return {nodes:[]};
}); });
sinon.stub(child_process,"execFile",function(cmd,args,opt,cb) { var res = {
cb(null,"",""); code: 0,
}); stdout:"",
stderr:""
}
var p = Promise.resolve(res);
p.catch((err)=>{});
initInstaller(p)
sinon.stub(fs,"statSync", function(fn) { return {}; }); sinon.stub(fs,"statSync", function(fn) { return {}; });

View File

@ -0,0 +1,22 @@
/**
* 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 sinon = require("sinon");
var path = require("path");
var NR_TEST_UTILS = require("nr-test-utils");
var exec = NR_TEST_UTILS.require("@node-red/runtime/lib/exec");