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:
parent
2816b3edae
commit
b2516117f5
@ -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/editor.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/clipboard.js",
|
||||
"packages/node_modules/@node-red/editor-client/src/js/ui/library.js",
|
||||
|
@ -215,6 +215,10 @@
|
||||
"plusNMore": "+ __count__ more"
|
||||
}
|
||||
},
|
||||
"eventLog": {
|
||||
"title": "Event log",
|
||||
"view": "View log"
|
||||
},
|
||||
"diff": {
|
||||
"unresolvedCount": "__count__ unresolved conflict",
|
||||
"unresolvedCount_plural": "__count__ unresolved conflicts",
|
||||
|
@ -19,7 +19,8 @@
|
||||
"ctrl-alt-r": "core:show-remote-diff",
|
||||
"ctrl-alt-n": "core:new-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": {
|
||||
"backspace": "core:delete-selection",
|
||||
|
@ -398,6 +398,10 @@ var RED = (function() {
|
||||
// Refresh flow library to ensure any examples are updated
|
||||
RED.library.loadFlowLibrary();
|
||||
});
|
||||
RED.comms.subscribe("event-log/#", function(topic,payload) {
|
||||
var id = topic.substring(9);
|
||||
RED.eventLog.log(id,payload);
|
||||
});
|
||||
}
|
||||
|
||||
function showAbout() {
|
||||
@ -436,6 +440,7 @@ var RED = (function() {
|
||||
// null,
|
||||
{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-event-log",label:RED._("eventLog.title"),onselect:"core:show-event-log"},
|
||||
null
|
||||
]});
|
||||
menuOptions.push(null);
|
||||
@ -483,6 +488,7 @@ var RED = (function() {
|
||||
RED.library.init();
|
||||
RED.keyboard.init();
|
||||
RED.palette.init();
|
||||
RED.eventLog.init();
|
||||
if (RED.settings.theme('palette.editable') !== false) {
|
||||
RED.palette.editor.init();
|
||||
} else {
|
||||
|
121
packages/node_modules/@node-red/editor-client/src/js/ui/event-log.js
vendored
Normal file
121
packages/node_modules/@node-red/editor-client/src/js/ui/event-log.js
vendored
Normal 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("");
|
||||
}
|
||||
}
|
||||
})();
|
@ -863,11 +863,35 @@ RED.palette.editor = (function() {
|
||||
class: "primary palette-module-install-confirm-button-update",
|
||||
click: function() {
|
||||
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) {
|
||||
spinner.remove();
|
||||
if (xhr) {
|
||||
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);
|
||||
@ -898,12 +922,35 @@ RED.palette.editor = (function() {
|
||||
class: "primary palette-module-install-confirm-button-remove",
|
||||
click: function() {
|
||||
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) {
|
||||
spinner.remove();
|
||||
if (xhr) {
|
||||
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();
|
||||
@ -940,11 +987,36 @@ RED.palette.editor = (function() {
|
||||
class: "primary palette-module-install-confirm-button-install",
|
||||
click: function() {
|
||||
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) {
|
||||
spinner.remove();
|
||||
if (xhr) {
|
||||
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);
|
||||
|
@ -870,7 +870,13 @@ RED.sidebar.versionControl = (function() {
|
||||
.click(function(e) {
|
||||
e.preventDefault();
|
||||
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();
|
||||
RED.eventLog.startEvent("Push changes"+(activeProject.git.branches.remoteAlt?(" : "+activeProject.git.branches.remoteAlt):""));
|
||||
var url = "projects/"+activeProject.name+"/push";
|
||||
if (activeProject.git.branches.remoteAlt) {
|
||||
url+="/"+activeProject.git.branches.remoteAlt;
|
||||
@ -914,7 +920,13 @@ RED.sidebar.versionControl = (function() {
|
||||
var pullRemote = function(options) {
|
||||
options = options || {};
|
||||
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();
|
||||
RED.eventLog.startEvent("Pull changes"+(activeProject.git.branches.remoteAlt?(" : "+activeProject.git.branches.remoteAlt):""));
|
||||
var url = "projects/"+activeProject.name+"/pull";
|
||||
if (activeProject.git.branches.remoteAlt) {
|
||||
url+="/"+activeProject.git.branches.remoteAlt;
|
||||
|
@ -6,3 +6,16 @@
|
||||
border-top-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;
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ var fs = require("fs");
|
||||
var registry = require("./registry");
|
||||
var library = require("./library");
|
||||
var log;
|
||||
var exec;
|
||||
|
||||
var events;
|
||||
|
||||
@ -36,6 +37,7 @@ function init(runtime) {
|
||||
events = runtime.events;
|
||||
settings = runtime.settings;
|
||||
log = runtime.log;
|
||||
exec = runtime.exec;
|
||||
}
|
||||
|
||||
var activePromise = Promise.resolve();
|
||||
@ -104,19 +106,19 @@ function installModule(module,version) {
|
||||
var installDir = settings.userDir || process.env.NODE_RED_HOME || ".";
|
||||
var args = ['install','--save','--save-prefix="~"','--production',installName];
|
||||
log.trace(npmCommand + JSON.stringify(args));
|
||||
var child = child_process.spawn(npmCommand,args,{
|
||||
cwd: installDir,
|
||||
shell: true
|
||||
});
|
||||
var output = "";
|
||||
child.stdout.on('data', (data) => {
|
||||
output += data;
|
||||
});
|
||||
child.stderr.on('data', (data) => {
|
||||
output += data;
|
||||
});
|
||||
child.on('close', (code) => {
|
||||
if (code !== 0) {
|
||||
exec.run(npmCommand,args,{
|
||||
cwd: installDir
|
||||
}, true).then(result => {
|
||||
if (!isUpgrade) {
|
||||
log.info(log._("server.install.installed",{name:module}));
|
||||
resolve(require("./index").addModule(module).then(reportAddedModules));
|
||||
} 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");
|
||||
@ -137,17 +139,7 @@ function installModule(module,version) {
|
||||
log.warn("------------------------------------------");
|
||||
reject(new Error(log._("server.install.install-failed")));
|
||||
}
|
||||
} else {
|
||||
if (!isUpgrade) {
|
||||
log.info(log._("server.install.installed",{name:module}));
|
||||
resolve(require("./index").addModule(module).then(reportAddedModules));
|
||||
} 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(err => {
|
||||
// In case of error, reset activePromise to be resolvable
|
||||
@ -208,25 +200,21 @@ function uninstallModule(module) {
|
||||
var args = ['remove','--save',module];
|
||||
log.trace(npmCommand + JSON.stringify(args));
|
||||
|
||||
var child = child_process.execFile(npmCommand,args,
|
||||
{
|
||||
cwd: installDir
|
||||
},
|
||||
function(err, stdin, stdout) {
|
||||
if (err) {
|
||||
log.warn(log._("server.install.uninstall-failed-long",{name:module}));
|
||||
log.warn("------------------------------------------");
|
||||
log.warn(err.toString());
|
||||
log.warn("------------------------------------------");
|
||||
reject(new Error(log._("server.install.uninstall-failed",{name:module})));
|
||||
} else {
|
||||
exec.run(npmCommand,args,{
|
||||
cwd: installDir,
|
||||
},true).then(result => {
|
||||
log.info(log._("server.install.uninstalled",{name:module}));
|
||||
reportRemovedModules(list);
|
||||
library.removeExamplesDir(module);
|
||||
resolve(list);
|
||||
}
|
||||
}
|
||||
);
|
||||
}).catch(result => {
|
||||
var output = result.stderr;
|
||||
log.warn(log._("server.install.uninstall-failed-long",{name:module}));
|
||||
log.warn("------------------------------------------");
|
||||
log.warn(output.toString());
|
||||
log.warn("------------------------------------------");
|
||||
reject(new Error(log._("server.install.uninstall-failed",{name:module})));
|
||||
});
|
||||
});
|
||||
}).catch(err => {
|
||||
// In case of error, reset activePromise to be resolvable
|
||||
|
@ -42,6 +42,21 @@ function handleRuntimeEvent(event) {
|
||||
runtime.log.trace("runtime event: "+JSON.stringify(event));
|
||||
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) {
|
||||
if (retain) {
|
||||
@ -64,6 +79,8 @@ var api = module.exports = {
|
||||
runtime.events.on("runtime-event",handleRuntimeEvent);
|
||||
runtime.events.removeListener("comms",handleCommsEvent);
|
||||
runtime.events.on("comms",handleCommsEvent);
|
||||
runtime.events.removeListener("event-log",handleEventLog);
|
||||
runtime.events.on("event-log",handleEventLog);
|
||||
},
|
||||
|
||||
/**
|
||||
|
69
packages/node_modules/@node-red/runtime/lib/exec.js
vendored
Normal file
69
packages/node_modules/@node-red/runtime/lib/exec.js
vendored
Normal 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);
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ var storage = require("./storage");
|
||||
var library = require("./library");
|
||||
var events = require("./events");
|
||||
var settings = require("./settings");
|
||||
var exec = require("./exec");
|
||||
|
||||
var express = require("express");
|
||||
var path = require('path');
|
||||
@ -71,6 +72,7 @@ function init(userSettings,_redUtil,_adminApi) {
|
||||
redNodes.init(runtime);
|
||||
library.init(runtime);
|
||||
externalAPI.init(runtime);
|
||||
exec.init(runtime);
|
||||
}
|
||||
|
||||
var version;
|
||||
@ -248,6 +250,7 @@ var runtime = {
|
||||
events: events,
|
||||
nodes: redNodes,
|
||||
library: library,
|
||||
exec: exec,
|
||||
util: require("@node-red/util").util,
|
||||
get adminApi() { return adminApi },
|
||||
get nodeApp() { return nodeApp },
|
||||
|
@ -15,8 +15,9 @@
|
||||
**/
|
||||
|
||||
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 sshResponseServer = require('./authServer').ResponseSSHServer;
|
||||
var clone = require('clone');
|
||||
@ -26,26 +27,16 @@ var gitCommand = "git";
|
||||
var gitVersion;
|
||||
var log;
|
||||
|
||||
function runGitCommand(args,cwd,env) {
|
||||
function runGitCommand(args,cwd,env,emit) {
|
||||
log.trace(gitCommand + JSON.stringify(args));
|
||||
return when.promise(function(resolve,reject) {
|
||||
args.unshift("credential.helper=")
|
||||
args.unshift("-c");
|
||||
var child = spawn(gitCommand, args, {cwd:cwd, detached:true, env:env});
|
||||
var stdout = "";
|
||||
var stderr = "";
|
||||
child.stdout.on('data', function(data) {
|
||||
stdout += data;
|
||||
});
|
||||
child.stderr.on('data', function(data) {
|
||||
stderr += data;
|
||||
});
|
||||
child.on('error', function(err) {
|
||||
stderr = err.toString();
|
||||
})
|
||||
child.on('close', function(code) {
|
||||
if (code !== 0) {
|
||||
return exec.run(gitCommand, args, {cwd:cwd, env:env}, emit).then(result => {
|
||||
return result.stdout;
|
||||
}).catch(result => {
|
||||
var err = new Error(stderr);
|
||||
var stdout = result.stdout;
|
||||
var stderr = result.stderr;
|
||||
err.stdout = stdout;
|
||||
err.stderr = stderr;
|
||||
if (/Connection refused/i.test(stderr)) {
|
||||
@ -83,26 +74,23 @@ function runGitCommand(args,cwd,env) {
|
||||
} else if (/name consists only of disallowed characters/i.test(stderr)) {
|
||||
err.code = "git_missing_user";
|
||||
}
|
||||
return reject(err);
|
||||
}
|
||||
resolve(stdout);
|
||||
});
|
||||
});
|
||||
throw err;
|
||||
})
|
||||
}
|
||||
function runGitCommandWithAuth(args,cwd,auth) {
|
||||
function runGitCommandWithAuth(args,cwd,auth,emit) {
|
||||
return authResponseServer(auth).then(function(rs) {
|
||||
var commandEnv = clone(process.env);
|
||||
commandEnv.GIT_ASKPASS = path.join(__dirname,"node-red-ask-pass.sh");
|
||||
commandEnv.NODE_RED_GIT_NODE_PATH = process.execPath;
|
||||
commandEnv.NODE_RED_GIT_SOCK_PATH = rs.path;
|
||||
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();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function runGitCommandWithSSHCommand(args,cwd,auth) {
|
||||
function runGitCommandWithSSHCommand(args,cwd,auth,emit) {
|
||||
return sshResponseServer(auth).then(function(rs) {
|
||||
var commandEnv = clone(process.env);
|
||||
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
|
||||
commandEnv.GIT_SSH_COMMAND = "ssh -i " + auth.key_path + " -F /dev/null";
|
||||
// console.log('commandEnv:', commandEnv);
|
||||
return runGitCommand(args,cwd,commandEnv).finally(function() {
|
||||
return runGitCommand(args,cwd,commandEnv,emit).finally(function() {
|
||||
rs.close();
|
||||
});
|
||||
})
|
||||
@ -448,13 +436,13 @@ module.exports = {
|
||||
var promise;
|
||||
if (auth) {
|
||||
if ( auth.key_path ) {
|
||||
promise = runGitCommandWithSSHCommand(args,cwd,auth);
|
||||
promise = runGitCommandWithSSHCommand(args,cwd,auth,true);
|
||||
}
|
||||
else {
|
||||
promise = runGitCommandWithAuth(args,cwd,auth);
|
||||
promise = runGitCommandWithAuth(args,cwd,auth,true);
|
||||
}
|
||||
} else {
|
||||
promise = runGitCommand(args,cwd)
|
||||
promise = runGitCommand(args,cwd,undefined,true)
|
||||
}
|
||||
return promise;
|
||||
// .catch(function(err) {
|
||||
@ -485,13 +473,13 @@ module.exports = {
|
||||
var promise;
|
||||
if (auth) {
|
||||
if ( auth.key_path ) {
|
||||
promise = runGitCommandWithSSHCommand(args,cwd,auth);
|
||||
promise = runGitCommandWithSSHCommand(args,cwd,auth,true);
|
||||
}
|
||||
else {
|
||||
promise = runGitCommandWithAuth(args,cwd,auth);
|
||||
promise = runGitCommandWithAuth(args,cwd,auth,true);
|
||||
}
|
||||
} else {
|
||||
promise = runGitCommand(args,cwd)
|
||||
promise = runGitCommand(args,cwd,undefined,true)
|
||||
}
|
||||
return promise.catch(function(err) {
|
||||
if (err.code === 'git_error') {
|
||||
@ -517,13 +505,13 @@ module.exports = {
|
||||
args.push(".");
|
||||
if (auth) {
|
||||
if ( auth.key_path ) {
|
||||
return runGitCommandWithSSHCommand(args,cwd,auth);
|
||||
return runGitCommandWithSSHCommand(args,cwd,auth,true);
|
||||
}
|
||||
else {
|
||||
return runGitCommandWithAuth(args,cwd,auth);
|
||||
return runGitCommandWithAuth(args,cwd,auth,true);
|
||||
}
|
||||
} else {
|
||||
return runGitCommand(args,cwd);
|
||||
return runGitCommand(args,cwd,undefined,true);
|
||||
}
|
||||
},
|
||||
getStatus: getStatus,
|
||||
|
@ -30,19 +30,22 @@ module.exports = {
|
||||
log.init(settings);
|
||||
i18n.init();
|
||||
},
|
||||
|
||||
/**
|
||||
* Logging utilities
|
||||
* @see module:@node-red/util.module:log
|
||||
*/
|
||||
log: log,
|
||||
|
||||
/**
|
||||
* Internationalization utilities
|
||||
* @see module:@node-red/util.module:i18n
|
||||
*/
|
||||
i18n: i18n,
|
||||
|
||||
/**
|
||||
* General utilities
|
||||
* @see module:@node-red/util.module:util
|
||||
*/
|
||||
util: util
|
||||
util: util,
|
||||
}
|
||||
|
13
packages/node_modules/node-red/lib/red.js
vendored
13
packages/node_modules/node-red/lib/red.js
vendored
@ -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 = {
|
||||
init: function(httpServer,userSettings) {
|
||||
if (!userSettings) {
|
||||
@ -59,7 +47,6 @@ module.exports = {
|
||||
|
||||
if (!userSettings.SKIP_BUILD_CHECK) {
|
||||
checkVersion(userSettings);
|
||||
checkBuild();
|
||||
}
|
||||
|
||||
if (!userSettings.coreNodesDir) {
|
||||
|
@ -21,9 +21,6 @@ var path = require("path");
|
||||
var fs = require('fs');
|
||||
var EventEmitter = require('events');
|
||||
|
||||
var child_process = require('child_process');
|
||||
|
||||
|
||||
var NR_TEST_UTILS = require("nr-test-utils");
|
||||
|
||||
var installer = NR_TEST_UTILS.require("@node-red/registry/lib/installer");
|
||||
@ -42,16 +39,21 @@ describe('nodes/registry/installer', function() {
|
||||
_: function() { return "abc"}
|
||||
}
|
||||
|
||||
before(function() {
|
||||
installer.init({log:mockLog, settings:{}, events: new EventEmitter()});
|
||||
beforeEach(function() {
|
||||
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() {
|
||||
if (child_process.spawn.restore) {
|
||||
child_process.spawn.restore();
|
||||
}
|
||||
if (child_process.execFile.restore) {
|
||||
child_process.execFile.restore();
|
||||
}
|
||||
if (registry.addModule.restore) {
|
||||
registry.addModule.restore();
|
||||
}
|
||||
@ -76,39 +78,33 @@ describe('nodes/registry/installer', function() {
|
||||
|
||||
describe("installs module", function() {
|
||||
it("rejects when npm returns a 404", function(done) {
|
||||
sinon.stub(child_process,"spawn",function(cmd,args,opt) {
|
||||
var ee = new EventEmitter();
|
||||
ee.stdout = new EventEmitter();
|
||||
ee.stderr = new EventEmitter();
|
||||
setTimeout(function() {
|
||||
ee.stderr.emit('data'," 404 this_wont_exist");
|
||||
ee.emit('close',1);
|
||||
},10)
|
||||
return ee;
|
||||
});
|
||||
|
||||
var res = {
|
||||
code: 1,
|
||||
stdout:"",
|
||||
stderr:" 404 this_wont_exist"
|
||||
}
|
||||
var p = Promise.reject(res);
|
||||
p.catch((err)=>{});
|
||||
initInstaller(p)
|
||||
installer.installModule("this_wont_exist").catch(function(err) {
|
||||
err.should.have.property("code",404);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it("rejects when npm does not find specified version", function(done) {
|
||||
sinon.stub(child_process,"spawn",function(cmd,args,opt) {
|
||||
var ee = new EventEmitter();
|
||||
ee.stdout = new EventEmitter();
|
||||
ee.stderr = new EventEmitter();
|
||||
setTimeout(function() {
|
||||
ee.stderr.emit('data'," version not found: this_wont_exist@0.1.2");
|
||||
ee.emit('close',1);
|
||||
},10)
|
||||
return ee;
|
||||
});
|
||||
var res = {
|
||||
code: 1,
|
||||
stdout:"",
|
||||
stderr:" version not found: this_wont_exist@0.1.2"
|
||||
}
|
||||
var p = Promise.reject(res);
|
||||
p.catch((err)=>{});
|
||||
initInstaller(p)
|
||||
sinon.stub(typeRegistry,"getModuleInfo", function() {
|
||||
return {
|
||||
version: "0.1.1"
|
||||
}
|
||||
});
|
||||
|
||||
installer.installModule("this_wont_exist","0.1.2").catch(function(err) {
|
||||
err.code.should.be.eql(404);
|
||||
done();
|
||||
@ -126,17 +122,14 @@ describe('nodes/registry/installer', function() {
|
||||
});
|
||||
});
|
||||
it("rejects with generic error", function(done) {
|
||||
sinon.stub(child_process,"spawn",function(cmd,args,opt,cb) {
|
||||
var ee = new EventEmitter();
|
||||
ee.stdout = new EventEmitter();
|
||||
ee.stderr = new EventEmitter();
|
||||
setTimeout(function() {
|
||||
ee.stderr.emit('data'," kaboom!");
|
||||
ee.emit('close',1);
|
||||
},10)
|
||||
return ee;
|
||||
});
|
||||
|
||||
var res = {
|
||||
code: 1,
|
||||
stdout:"",
|
||||
stderr:" kaboom!"
|
||||
}
|
||||
var p = Promise.reject(res);
|
||||
p.catch((err)=>{});
|
||||
initInstaller(p)
|
||||
installer.installModule("this_wont_exist").then(function() {
|
||||
done(new Error("Unexpected success"));
|
||||
}).catch(function(err) {
|
||||
@ -145,15 +138,16 @@ describe('nodes/registry/installer', function() {
|
||||
});
|
||||
it("succeeds when module is found", function(done) {
|
||||
var nodeInfo = {nodes:{module:"foo",types:["a"]}};
|
||||
sinon.stub(child_process,"spawn",function(cmd,args,opt) {
|
||||
var ee = new EventEmitter();
|
||||
ee.stdout = new EventEmitter();
|
||||
ee.stderr = new EventEmitter();
|
||||
setTimeout(function() {
|
||||
ee.emit('close',0);
|
||||
},10)
|
||||
return ee;
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
@ -191,15 +185,15 @@ describe('nodes/registry/installer', function() {
|
||||
return when.resolve(nodeInfo);
|
||||
});
|
||||
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();
|
||||
ee.stdout = new EventEmitter();
|
||||
ee.stderr = new EventEmitter();
|
||||
setTimeout(function() {
|
||||
ee.emit('close',0);
|
||||
},10)
|
||||
return ee;
|
||||
});
|
||||
|
||||
var res = {
|
||||
code: 0,
|
||||
stdout:"",
|
||||
stderr:""
|
||||
}
|
||||
var p = Promise.resolve(res);
|
||||
p.catch((err)=>{});
|
||||
initInstaller(p)
|
||||
installer.installModule(resourcesDir).then(function(info) {
|
||||
info.should.eql(nodeInfo);
|
||||
done();
|
||||
@ -226,9 +220,14 @@ describe('nodes/registry/installer', function() {
|
||||
var removeModule = sinon.stub(registry,"removeModule",function(md) {
|
||||
return when.resolve(nodeInfo);
|
||||
});
|
||||
sinon.stub(child_process,"execFile",function(cmd,args,opt,cb) {
|
||||
cb(new Error("test_error"),"","");
|
||||
});
|
||||
var res = {
|
||||
code: 1,
|
||||
stdout:"",
|
||||
stderr:"error"
|
||||
}
|
||||
var p = Promise.reject(res);
|
||||
p.catch((err)=>{});
|
||||
initInstaller(p)
|
||||
|
||||
installer.uninstallModule("this_wont_exist").then(function() {
|
||||
done(new Error("Unexpected success"));
|
||||
@ -244,9 +243,14 @@ describe('nodes/registry/installer', function() {
|
||||
var getModuleInfo = sinon.stub(registry,"getModuleInfo",function(md) {
|
||||
return {nodes:[]};
|
||||
});
|
||||
sinon.stub(child_process,"execFile",function(cmd,args,opt,cb) {
|
||||
cb(null,"","");
|
||||
});
|
||||
var res = {
|
||||
code: 0,
|
||||
stdout:"",
|
||||
stderr:""
|
||||
}
|
||||
var p = Promise.resolve(res);
|
||||
p.catch((err)=>{});
|
||||
initInstaller(p)
|
||||
|
||||
sinon.stub(fs,"statSync", function(fn) { return {}; });
|
||||
|
||||
|
22
test/unit/@node-red/runtime/lib/exec_spec.js
Normal file
22
test/unit/@node-red/runtime/lib/exec_spec.js
Normal 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");
|
Loading…
Reference in New Issue
Block a user