mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
pull out editor-client and editor-api
This commit is contained in:
240
packages/node_modules/@node-red/runtime/lib/storage/index.js
generated
vendored
Normal file
240
packages/node_modules/@node-red/runtime/lib/storage/index.js
generated
vendored
Normal file
@@ -0,0 +1,240 @@
|
||||
/**
|
||||
* 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 when = require('when');
|
||||
var Path = require('path');
|
||||
var crypto = require('crypto');
|
||||
|
||||
var log = require("@node-red/util").log; // TODO: separate module
|
||||
|
||||
var runtime;
|
||||
var storageModule;
|
||||
var settingsAvailable;
|
||||
var sessionsAvailable;
|
||||
|
||||
var libraryFlowsCachedResult = null;
|
||||
|
||||
function moduleSelector(aSettings) {
|
||||
var toReturn;
|
||||
if (aSettings.storageModule) {
|
||||
if (typeof aSettings.storageModule === "string") {
|
||||
// TODO: allow storage modules to be specified by absolute path
|
||||
toReturn = require("./"+aSettings.storageModule);
|
||||
} else {
|
||||
toReturn = aSettings.storageModule;
|
||||
}
|
||||
} else {
|
||||
toReturn = require("./localfilesystem");
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
function is_malicious(path) {
|
||||
return path.indexOf('../') != -1 || path.indexOf('..\\') != -1;
|
||||
}
|
||||
|
||||
var storageModuleInterface = {
|
||||
init: function(_runtime) {
|
||||
runtime = _runtime;
|
||||
try {
|
||||
storageModule = moduleSelector(runtime.settings);
|
||||
settingsAvailable = storageModule.hasOwnProperty("getSettings") && storageModule.hasOwnProperty("saveSettings");
|
||||
sessionsAvailable = storageModule.hasOwnProperty("getSessions") && storageModule.hasOwnProperty("saveSessions");
|
||||
} catch (e) {
|
||||
return when.reject(e);
|
||||
}
|
||||
if (!!storageModule.projects) {
|
||||
var projectsEnabled = false;
|
||||
if (runtime.settings.hasOwnProperty("editorTheme") && runtime.settings.editorTheme.hasOwnProperty("projects")) {
|
||||
projectsEnabled = runtime.settings.editorTheme.projects.enabled === true;
|
||||
}
|
||||
if (projectsEnabled) {
|
||||
storageModuleInterface.projects = storageModule.projects;
|
||||
}
|
||||
}
|
||||
if (storageModule.sshkeys) {
|
||||
storageModuleInterface.sshkeys = storageModule.sshkeys;
|
||||
}
|
||||
return storageModule.init(runtime.settings,runtime);
|
||||
},
|
||||
getFlows: function() {
|
||||
return storageModule.getFlows().then(function(flows) {
|
||||
return storageModule.getCredentials().then(function(creds) {
|
||||
var result = {
|
||||
flows: flows,
|
||||
credentials: creds
|
||||
};
|
||||
result.rev = crypto.createHash('md5').update(JSON.stringify(result.flows)).digest("hex");
|
||||
return result;
|
||||
})
|
||||
});
|
||||
},
|
||||
saveFlows: function(config) {
|
||||
var flows = config.flows;
|
||||
var credentials = config.credentials;
|
||||
var credentialSavePromise;
|
||||
if (config.credentialsDirty) {
|
||||
credentialSavePromise = storageModule.saveCredentials(credentials);
|
||||
} else {
|
||||
credentialSavePromise = when.resolve();
|
||||
}
|
||||
delete config.credentialsDirty;
|
||||
|
||||
return credentialSavePromise.then(function() {
|
||||
return storageModule.saveFlows(flows).then(function() {
|
||||
return crypto.createHash('md5').update(JSON.stringify(config.flows)).digest("hex");
|
||||
})
|
||||
});
|
||||
},
|
||||
// getCredentials: function() {
|
||||
// return storageModule.getCredentials();
|
||||
// },
|
||||
saveCredentials: function(credentials) {
|
||||
return storageModule.saveCredentials(credentials);
|
||||
},
|
||||
getSettings: function() {
|
||||
if (settingsAvailable) {
|
||||
return storageModule.getSettings();
|
||||
} else {
|
||||
return when.resolve(null);
|
||||
}
|
||||
},
|
||||
saveSettings: function(settings) {
|
||||
if (settingsAvailable) {
|
||||
return storageModule.saveSettings(settings);
|
||||
} else {
|
||||
return when.resolve();
|
||||
}
|
||||
},
|
||||
getSessions: function() {
|
||||
if (sessionsAvailable) {
|
||||
return storageModule.getSessions();
|
||||
} else {
|
||||
return when.resolve(null);
|
||||
}
|
||||
},
|
||||
saveSessions: function(sessions) {
|
||||
if (sessionsAvailable) {
|
||||
return storageModule.saveSessions(sessions);
|
||||
} else {
|
||||
return when.resolve();
|
||||
}
|
||||
},
|
||||
|
||||
/* Library Functions */
|
||||
|
||||
getLibraryEntry: function(type, path) {
|
||||
if (is_malicious(path)) {
|
||||
var err = new Error();
|
||||
err.code = "forbidden";
|
||||
return when.reject(err);
|
||||
}
|
||||
return storageModule.getLibraryEntry(type, path);
|
||||
},
|
||||
saveLibraryEntry: function(type, path, meta, body) {
|
||||
if (is_malicious(path)) {
|
||||
var err = new Error();
|
||||
err.code = "forbidden";
|
||||
return when.reject(err);
|
||||
}
|
||||
return storageModule.saveLibraryEntry(type, path, meta, body);
|
||||
},
|
||||
|
||||
/* Deprecated functions */
|
||||
getAllFlows: function() {
|
||||
if (storageModule.hasOwnProperty("getAllFlows")) {
|
||||
return storageModule.getAllFlows();
|
||||
} else {
|
||||
if (libraryFlowsCachedResult) {
|
||||
return Promise.resolve(libraryFlowsCachedResult);
|
||||
} else {
|
||||
return listFlows("/").then(function(result) {
|
||||
libraryFlowsCachedResult = result;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
getFlow: function(fn) {
|
||||
if (is_malicious(fn)) {
|
||||
var err = new Error();
|
||||
err.code = "forbidden";
|
||||
return when.reject(err);
|
||||
}
|
||||
if (storageModule.hasOwnProperty("getFlow")) {
|
||||
return storageModule.getFlow(fn);
|
||||
} else {
|
||||
return storageModule.getLibraryEntry("flows",fn);
|
||||
}
|
||||
|
||||
},
|
||||
saveFlow: function(fn, data) {
|
||||
if (is_malicious(fn)) {
|
||||
var err = new Error();
|
||||
err.code = "forbidden";
|
||||
return when.reject(err);
|
||||
}
|
||||
libraryFlowsCachedResult = null;
|
||||
if (storageModule.hasOwnProperty("saveFlow")) {
|
||||
return storageModule.saveFlow(fn, data);
|
||||
} else {
|
||||
return storageModule.saveLibraryEntry("flows",fn,{},data);
|
||||
}
|
||||
}
|
||||
/* End deprecated functions */
|
||||
|
||||
}
|
||||
|
||||
|
||||
function listFlows(path) {
|
||||
return storageModule.getLibraryEntry("flows",path).then(function(res) {
|
||||
return when.promise(function(resolve) {
|
||||
var promises = [];
|
||||
res.forEach(function(r) {
|
||||
if (typeof r === "string") {
|
||||
promises.push(listFlows(Path.join(path,r)));
|
||||
} else {
|
||||
promises.push(when.resolve(r));
|
||||
}
|
||||
});
|
||||
var i=0;
|
||||
when.settle(promises).then(function(res2) {
|
||||
var result = {};
|
||||
res2.forEach(function(r) {
|
||||
// TODO: name||fn
|
||||
if (r.value.fn) {
|
||||
var name = r.value.name;
|
||||
if (!name) {
|
||||
name = r.value.fn.replace(/\.json$/, "");
|
||||
}
|
||||
result.f = result.f || [];
|
||||
result.f.push(name);
|
||||
} else {
|
||||
result.d = result.d || {};
|
||||
result.d[res[i]] = r.value;
|
||||
//console.log(">",r.value);
|
||||
}
|
||||
i++;
|
||||
});
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
module.exports = storageModuleInterface;
|
102
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/index.js
generated
vendored
Normal file
102
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/index.js
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 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 fs = require('fs-extra');
|
||||
var when = require('when');
|
||||
var fspath = require("path");
|
||||
|
||||
var log = require("@node-red/util").log; // TODO: separate module
|
||||
|
||||
var util = require("./util");
|
||||
var library = require("./library");
|
||||
var sessions = require("./sessions");
|
||||
var runtimeSettings = require("./settings");
|
||||
var projects = require("./projects");
|
||||
|
||||
var initialFlowLoadComplete = false;
|
||||
var settings;
|
||||
|
||||
var localfilesystem = {
|
||||
init: function(_settings, runtime) {
|
||||
settings = _settings;
|
||||
|
||||
var promises = [];
|
||||
|
||||
if (!settings.userDir) {
|
||||
try {
|
||||
fs.statSync(fspath.join(process.env.NODE_RED_HOME,".config.json"));
|
||||
settings.userDir = process.env.NODE_RED_HOME;
|
||||
} catch(err) {
|
||||
try {
|
||||
// Consider compatibility for older versions
|
||||
if (process.env.HOMEPATH) {
|
||||
fs.statSync(fspath.join(process.env.HOMEPATH,".node-red",".config.json"));
|
||||
settings.userDir = fspath.join(process.env.HOMEPATH,".node-red");
|
||||
}
|
||||
} catch(err) {
|
||||
}
|
||||
if (!settings.userDir) {
|
||||
settings.userDir = fspath.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || process.env.NODE_RED_HOME,".node-red");
|
||||
if (!settings.readOnly) {
|
||||
promises.push(fs.ensureDir(fspath.join(settings.userDir,"node_modules")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sessions.init(settings);
|
||||
runtimeSettings.init(settings);
|
||||
promises.push(library.init(settings));
|
||||
promises.push(projects.init(settings, runtime));
|
||||
|
||||
var packageFile = fspath.join(settings.userDir,"package.json");
|
||||
var packagePromise = when.resolve();
|
||||
|
||||
if (!settings.readOnly) {
|
||||
packagePromise = function() {
|
||||
try {
|
||||
fs.statSync(packageFile);
|
||||
} catch(err) {
|
||||
var defaultPackage = {
|
||||
"name": "node-red-project",
|
||||
"description": "A Node-RED Project",
|
||||
"version": "0.0.1",
|
||||
"private": true
|
||||
};
|
||||
return util.writeFile(packageFile,JSON.stringify(defaultPackage,"",4));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return when.all(promises).then(packagePromise);
|
||||
},
|
||||
|
||||
|
||||
getFlows: projects.getFlows,
|
||||
saveFlows: projects.saveFlows,
|
||||
getCredentials: projects.getCredentials,
|
||||
saveCredentials: projects.saveCredentials,
|
||||
|
||||
getSettings: runtimeSettings.getSettings,
|
||||
saveSettings: runtimeSettings.saveSettings,
|
||||
getSessions: sessions.getSessions,
|
||||
saveSessions: sessions.saveSessions,
|
||||
getLibraryEntry: library.getLibraryEntry,
|
||||
saveLibraryEntry: library.saveLibraryEntry,
|
||||
projects: projects
|
||||
};
|
||||
|
||||
module.exports = localfilesystem;
|
183
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js
generated
vendored
Normal file
183
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js
generated
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* 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 fs = require('fs-extra');
|
||||
var when = require('when');
|
||||
var fspath = require("path");
|
||||
var nodeFn = require('when/node/function');
|
||||
|
||||
var util = require("./util");
|
||||
|
||||
var settings;
|
||||
var libDir;
|
||||
var libFlowsDir;
|
||||
|
||||
|
||||
function getFileMeta(root,path) {
|
||||
var fn = fspath.join(root,path);
|
||||
var fd = fs.openSync(fn,"r");
|
||||
var size = fs.fstatSync(fd).size;
|
||||
var meta = {};
|
||||
var read = 0;
|
||||
var length = 10;
|
||||
var remaining = "";
|
||||
var buffer = Buffer(length);
|
||||
while(read < size) {
|
||||
read+=fs.readSync(fd,buffer,0,length);
|
||||
var data = remaining+buffer.toString();
|
||||
var parts = data.split("\n");
|
||||
remaining = parts.splice(-1);
|
||||
for (var i=0;i<parts.length;i+=1) {
|
||||
var match = /^\/\/ (\w+): (.*)/.exec(parts[i]);
|
||||
if (match) {
|
||||
meta[match[1]] = match[2];
|
||||
} else {
|
||||
read = size;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fs.closeSync(fd);
|
||||
return meta;
|
||||
}
|
||||
|
||||
function getFileBody(root,path) {
|
||||
var body = "";
|
||||
var fn = fspath.join(root,path);
|
||||
var fd = fs.openSync(fn,"r");
|
||||
var size = fs.fstatSync(fd).size;
|
||||
var scanning = true;
|
||||
var read = 0;
|
||||
var length = 50;
|
||||
var remaining = "";
|
||||
var buffer = Buffer(length);
|
||||
while(read < size) {
|
||||
var thisRead = fs.readSync(fd,buffer,0,length);
|
||||
read += thisRead;
|
||||
if (scanning) {
|
||||
var data = remaining+buffer.slice(0,thisRead).toString();
|
||||
var parts = data.split("\n");
|
||||
remaining = parts.splice(-1)[0];
|
||||
for (var i=0;i<parts.length;i+=1) {
|
||||
if (! /^\/\/ \w+: /.test(parts[i])) {
|
||||
scanning = false;
|
||||
body += parts[i]+"\n";
|
||||
}
|
||||
}
|
||||
if (! /^\/\/ \w+: /.test(remaining)) {
|
||||
scanning = false;
|
||||
}
|
||||
if (!scanning) {
|
||||
body += remaining;
|
||||
}
|
||||
} else {
|
||||
body += buffer.slice(0,thisRead).toString();
|
||||
}
|
||||
}
|
||||
fs.closeSync(fd);
|
||||
return body;
|
||||
}
|
||||
function getLibraryEntry(type,path) {
|
||||
var root = fspath.join(libDir,type);
|
||||
var rootPath = fspath.join(libDir,type,path);
|
||||
|
||||
// don't create the folder if it does not exist - we are only reading....
|
||||
return nodeFn.call(fs.lstat, rootPath).then(function(stats) {
|
||||
if (stats.isFile()) {
|
||||
return getFileBody(root,path);
|
||||
}
|
||||
if (path.substr(-1) == '/') {
|
||||
path = path.substr(0,path.length-1);
|
||||
}
|
||||
return nodeFn.call(fs.readdir, rootPath).then(function(fns) {
|
||||
var dirs = [];
|
||||
var files = [];
|
||||
fns.sort().filter(function(fn) {
|
||||
var fullPath = fspath.join(path,fn);
|
||||
var absoluteFullPath = fspath.join(root,fullPath);
|
||||
if (fn[0] != ".") {
|
||||
var stats = fs.lstatSync(absoluteFullPath);
|
||||
if (stats.isDirectory()) {
|
||||
dirs.push(fn);
|
||||
} else {
|
||||
var meta = getFileMeta(root,fullPath);
|
||||
meta.fn = fn;
|
||||
files.push(meta);
|
||||
}
|
||||
}
|
||||
});
|
||||
return dirs.concat(files);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
// if path is empty, then assume it was a folder, return empty
|
||||
if (path === ""){
|
||||
return [];
|
||||
}
|
||||
|
||||
// if path ends with slash, it was a folder
|
||||
// so return empty
|
||||
if (path.substr(-1) == '/') {
|
||||
return [];
|
||||
}
|
||||
|
||||
// else path was specified, but did not exist,
|
||||
// check for path.json as an alternative if flows
|
||||
if (type === "flows" && !/\.json$/.test(path)) {
|
||||
return getLibraryEntry(type,path+".json")
|
||||
.catch(function(e) {
|
||||
throw err;
|
||||
});
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(_settings) {
|
||||
settings = _settings;
|
||||
libDir = fspath.join(settings.userDir,"lib");
|
||||
libFlowsDir = fspath.join(libDir,"flows");
|
||||
if (!settings.readOnly) {
|
||||
return fs.ensureDir(libFlowsDir);
|
||||
} else {
|
||||
return when.resolve();
|
||||
}
|
||||
},
|
||||
getLibraryEntry: getLibraryEntry,
|
||||
|
||||
saveLibraryEntry: function(type,path,meta,body) {
|
||||
if (settings.readOnly) {
|
||||
return when.resolve();
|
||||
}
|
||||
if (type === "flows" && !path.endsWith(".json")) {
|
||||
path += ".json";
|
||||
}
|
||||
var fn = fspath.join(libDir, type, path);
|
||||
var headers = "";
|
||||
for (var i in meta) {
|
||||
if (meta.hasOwnProperty(i)) {
|
||||
headers += "// "+i+": "+meta[i]+"\n";
|
||||
}
|
||||
}
|
||||
if (type === "flows" && settings.flowFilePretty) {
|
||||
body = JSON.stringify(JSON.parse(body),null,4);
|
||||
}
|
||||
return fs.ensureDir(fspath.dirname(fn)).then(function () {
|
||||
util.writeFile(fn,headers+body);
|
||||
});
|
||||
}
|
||||
}
|
997
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js
generated
vendored
Normal file
997
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js
generated
vendored
Normal file
@@ -0,0 +1,997 @@
|
||||
/**
|
||||
* 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 fs = require('fs-extra');
|
||||
var when = require('when');
|
||||
var fspath = require("path");
|
||||
var os = require('os');
|
||||
|
||||
var gitTools = require("./git");
|
||||
var util = require("../util");
|
||||
var defaultFileSet = require("./defaultFileSet");
|
||||
var sshKeys = require("./ssh");
|
||||
var settings;
|
||||
var runtime;
|
||||
var log;
|
||||
|
||||
var projectsDir;
|
||||
|
||||
var authCache = require("./git/authCache");
|
||||
|
||||
// TODO: DRY - red/api/editor/sshkeys !
|
||||
function getSSHKeyUsername(userObj) {
|
||||
var username = '__default';
|
||||
if ( userObj && userObj.name ) {
|
||||
username = userObj.name;
|
||||
}
|
||||
return username;
|
||||
}
|
||||
function getGitUser(user) {
|
||||
var username;
|
||||
if (!user) {
|
||||
username = "_";
|
||||
} else {
|
||||
username = user.username;
|
||||
}
|
||||
var userSettings = settings.getUserSettings(username);
|
||||
if (userSettings && userSettings.git) {
|
||||
return userSettings.git.user;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function Project(path) {
|
||||
this.path = path;
|
||||
this.name = fspath.basename(path);
|
||||
this.paths = {};
|
||||
this.files = {};
|
||||
this.auth = {origin:{}};
|
||||
this.missingFiles = [];
|
||||
this.credentialSecret = null;
|
||||
}
|
||||
Project.prototype.load = function () {
|
||||
var project = this;
|
||||
var globalProjectSettings = settings.get("projects");
|
||||
// console.log(globalProjectSettings)
|
||||
var projectSettings = {};
|
||||
if (globalProjectSettings) {
|
||||
if (globalProjectSettings.projects.hasOwnProperty(this.name)) {
|
||||
projectSettings = globalProjectSettings.projects[this.name] || {};
|
||||
}
|
||||
}
|
||||
|
||||
this.credentialSecret = projectSettings.credentialSecret;
|
||||
this.git = projectSettings.git || { user:{} };
|
||||
|
||||
// this.paths.flowFile = fspath.join(this.path,"flow.json");
|
||||
// this.paths.credentialsFile = fspath.join(this.path,"flow_cred.json");
|
||||
|
||||
var promises = [];
|
||||
return checkProjectFiles(project).then(function(missingFiles) {
|
||||
project.missingFiles = missingFiles;
|
||||
if (missingFiles.indexOf('package.json') === -1) {
|
||||
project.paths['package.json'] = fspath.join(project.path,"package.json");
|
||||
promises.push(fs.readFile(project.paths['package.json'],"utf8").then(function(content) {
|
||||
try {
|
||||
project.package = util.parseJSON(content);
|
||||
if (project.package.hasOwnProperty('node-red')) {
|
||||
if (project.package['node-red'].hasOwnProperty('settings')) {
|
||||
project.paths.flowFile = project.package['node-red'].settings.flowFile;
|
||||
project.paths.credentialsFile = project.package['node-red'].settings.credentialsFile;
|
||||
}
|
||||
} else {
|
||||
// TODO: package.json doesn't have a node-red section
|
||||
// is that a bad thing?
|
||||
}
|
||||
} catch(err) {
|
||||
// package.json isn't valid JSON... is a merge underway?
|
||||
project.package = {};
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
project.package = {};
|
||||
}
|
||||
if (missingFiles.indexOf('README.md') === -1) {
|
||||
project.paths['README.md'] = fspath.join(project.path,"README.md");
|
||||
promises.push(fs.readFile(project.paths['README.md'],"utf8").then(function(content) {
|
||||
project.description = content;
|
||||
}));
|
||||
} else {
|
||||
project.description = "";
|
||||
}
|
||||
// if (missingFiles.indexOf('flow.json') !== -1) {
|
||||
// console.log("MISSING FLOW FILE");
|
||||
// } else {
|
||||
// project.paths.flowFile = fspath.join(project.path,"flow.json");
|
||||
// }
|
||||
// if (missingFiles.indexOf('flow_cred.json') !== -1) {
|
||||
// console.log("MISSING CREDS FILE");
|
||||
// } else {
|
||||
// project.paths.credentialsFile = fspath.join(project.path,"flow_cred.json");
|
||||
// }
|
||||
|
||||
promises.push(project.loadRemotes());
|
||||
|
||||
return when.settle(promises).then(function(results) {
|
||||
return project;
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
Project.prototype.initialise = function(user,data) {
|
||||
var project = this;
|
||||
// if (!this.empty) {
|
||||
// throw new Error("Cannot initialise non-empty project");
|
||||
// }
|
||||
var files = Object.keys(defaultFileSet);
|
||||
var promises = [];
|
||||
|
||||
if (data.hasOwnProperty('credentialSecret')) {
|
||||
var projects = settings.get('projects');
|
||||
projects.projects[project.name] = projects.projects[project.name] || {};
|
||||
projects.projects[project.name].credentialSecret = data.credentialSecret;
|
||||
promises.push(settings.set('projects',projects));
|
||||
}
|
||||
|
||||
if (data.hasOwnProperty('files')) {
|
||||
if (data.files.hasOwnProperty('flow') && data.files.hasOwnProperty('credentials')) {
|
||||
project.files.flow = data.files.flow;
|
||||
project.files.credentials = data.files.credentials;
|
||||
var flowFilePath = fspath.join(project.path,project.files.flow);
|
||||
var credsFilePath = getCredentialsFilename(flowFilePath);
|
||||
promises.push(util.writeFile(flowFilePath,"[]"));
|
||||
promises.push(util.writeFile(credsFilePath,"{}"));
|
||||
files.push(project.files.flow);
|
||||
files.push(project.files.credentials);
|
||||
}
|
||||
}
|
||||
for (var file in defaultFileSet) {
|
||||
if (defaultFileSet.hasOwnProperty(file)) {
|
||||
var path = fspath.join(project.path,file);
|
||||
if (!fs.existsSync(path)) {
|
||||
promises.push(util.writeFile(path,defaultFileSet[file](project, runtime)));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return when.all(promises).then(function() {
|
||||
return gitTools.stageFile(project.path,files);
|
||||
}).then(function() {
|
||||
return gitTools.commit(project.path,"Create project files",getGitUser(user));
|
||||
}).then(function() {
|
||||
return project.load()
|
||||
})
|
||||
}
|
||||
|
||||
Project.prototype.loadRemotes = function() {
|
||||
var project = this;
|
||||
return gitTools.getRemotes(project.path).then(function(remotes) {
|
||||
project.remotes = remotes;
|
||||
}).then(function() {
|
||||
project.branches = {};
|
||||
return project.status();
|
||||
}).then(function() {
|
||||
if (project.remotes) {
|
||||
var allRemotes = Object.keys(project.remotes);
|
||||
var match = "";
|
||||
if (project.branches.remote) {
|
||||
allRemotes.forEach(function(remote) {
|
||||
if (project.branches.remote.indexOf(remote) === 0 && match.length < remote.length) {
|
||||
match = remote;
|
||||
}
|
||||
});
|
||||
project.currentRemote = project.parseRemoteBranch(project.branches.remote).remote;
|
||||
}
|
||||
} else {
|
||||
delete project.currentRemote;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Project.prototype.parseRemoteBranch = function (remoteBranch) {
|
||||
if (!remoteBranch) {
|
||||
return {}
|
||||
}
|
||||
var project = this;
|
||||
var allRemotes = Object.keys(project.remotes);
|
||||
var match = "";
|
||||
allRemotes.forEach(function(remote) {
|
||||
if (remoteBranch.indexOf(remote) === 0 && match.length < remote.length) {
|
||||
match = remote;
|
||||
}
|
||||
});
|
||||
return {
|
||||
remote: match,
|
||||
branch: remoteBranch.substring(match.length+1)
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Project.prototype.isEmpty = function () {
|
||||
return this.empty;
|
||||
};
|
||||
Project.prototype.isMerging = function() {
|
||||
return this.merging;
|
||||
}
|
||||
|
||||
Project.prototype.update = function (user, data) {
|
||||
var username;
|
||||
if (!user) {
|
||||
username = "_";
|
||||
} else {
|
||||
username = user.username;
|
||||
}
|
||||
|
||||
var promises = [];
|
||||
var project = this;
|
||||
var saveSettings = false;
|
||||
var saveREADME = false;
|
||||
var savePackage = false;
|
||||
var flowFilesChanged = false;
|
||||
var credentialSecretChanged = false;
|
||||
|
||||
var globalProjectSettings = settings.get("projects");
|
||||
if (!globalProjectSettings.projects.hasOwnProperty(this.name)) {
|
||||
globalProjectSettings.projects[this.name] = {};
|
||||
saveSettings = true;
|
||||
}
|
||||
|
||||
|
||||
if (data.credentialSecret && data.credentialSecret !== this.credentialSecret) {
|
||||
var existingSecret = data.currentCredentialSecret;
|
||||
var isReset = data.resetCredentialSecret;
|
||||
var secret = data.credentialSecret;
|
||||
|
||||
// console.log("updating credentialSecret");
|
||||
// console.log("request:");
|
||||
// console.log(JSON.stringify(data,"",4));
|
||||
// console.log(" this.credentialSecret",this.credentialSecret);
|
||||
// console.log(" this.info", this.info);
|
||||
|
||||
if (!isReset && // not a reset
|
||||
this.credentialSecret && // key already set
|
||||
!this.credentialSecretInvalid && // key not invalid
|
||||
this.credentialSecret !== existingSecret) { // key doesn't match provided existing key
|
||||
var e = new Error("Cannot change credentialSecret without current key");
|
||||
e.code = "missing_current_credential_key";
|
||||
return when.reject(e);
|
||||
}
|
||||
this.credentialSecret = secret;
|
||||
|
||||
globalProjectSettings.projects[this.name].credentialSecret = project.credentialSecret;
|
||||
delete this.credentialSecretInvalid;
|
||||
saveSettings = true;
|
||||
credentialSecretChanged = true;
|
||||
}
|
||||
|
||||
if (data.hasOwnProperty('description')) {
|
||||
saveREADME = true;
|
||||
this.description = data.description;
|
||||
}
|
||||
if (data.hasOwnProperty('dependencies')) {
|
||||
savePackage = true;
|
||||
this.package.dependencies = data.dependencies;
|
||||
}
|
||||
if (data.hasOwnProperty('summary')) {
|
||||
savePackage = true;
|
||||
this.package.description = data.summary;
|
||||
}
|
||||
|
||||
if (data.hasOwnProperty('git')) {
|
||||
if (data.git.hasOwnProperty('user')) {
|
||||
globalProjectSettings.projects[this.name].git = globalProjectSettings.projects[this.name].git || {};
|
||||
globalProjectSettings.projects[this.name].git.user = globalProjectSettings.projects[this.name].git.user || {};
|
||||
globalProjectSettings.projects[this.name].git.user[username] = {
|
||||
name: data.git.user.name,
|
||||
email: data.git.user.email
|
||||
}
|
||||
this.git.user[username] = {
|
||||
name: data.git.user.name,
|
||||
email: data.git.user.email
|
||||
}
|
||||
saveSettings = true;
|
||||
}
|
||||
if (data.git.hasOwnProperty('remotes')) {
|
||||
var remoteNames = Object.keys(data.git.remotes);
|
||||
var remotesChanged = false;
|
||||
var modifyRemotesPromise = Promise.resolve();
|
||||
remoteNames.forEach(function(name) {
|
||||
if (data.git.remotes[name].removed) {
|
||||
remotesChanged = true;
|
||||
modifyRemotesPromise = modifyRemotesPromise.then(function() { gitTools.removeRemote(project.path,name) });
|
||||
} else {
|
||||
if (data.git.remotes[name].url) {
|
||||
remotesChanged = true;
|
||||
modifyRemotesPromise = modifyRemotesPromise.then(function() { gitTools.addRemote(project.path,name,data.git.remotes[name])});
|
||||
}
|
||||
if (data.git.remotes[name].username && data.git.remotes[name].password) {
|
||||
var url = data.git.remotes[name].url || project.remotes[name].fetch;
|
||||
authCache.set(project.name,url,username,data.git.remotes[name]);
|
||||
}
|
||||
}
|
||||
})
|
||||
if (remotesChanged) {
|
||||
modifyRemotesPromise = modifyRemotesPromise.then(function() {
|
||||
return project.loadRemotes();
|
||||
});
|
||||
promises.push(modifyRemotesPromise);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.hasOwnProperty('files')) {
|
||||
this.package['node-red'] = this.package['node-red'] || { settings: {}};
|
||||
if (data.files.hasOwnProperty('flow') && this.package['node-red'].settings.flowFile !== data.files.flow) {
|
||||
this.paths.flowFile = data.files.flow;
|
||||
this.package['node-red'].settings.flowFile = data.files.flow;
|
||||
savePackage = true;
|
||||
flowFilesChanged = true;
|
||||
}
|
||||
if (data.files.hasOwnProperty('credentials') && this.package['node-red'].settings.credentialsFile !== data.files.credentials) {
|
||||
this.paths.credentialsFile = data.files.credentials;
|
||||
this.package['node-red'].settings.credentialsFile = data.files.credentials;
|
||||
// Don't know if the credSecret is invalid or not so clear the flag
|
||||
delete this.credentialSecretInvalid;
|
||||
|
||||
savePackage = true;
|
||||
flowFilesChanged = true;
|
||||
}
|
||||
}
|
||||
if (saveSettings) {
|
||||
promises.push(settings.set("projects",globalProjectSettings));
|
||||
}
|
||||
if (saveREADME) {
|
||||
promises.push(util.writeFile(this.paths['README.md'], this.description));
|
||||
}
|
||||
if (savePackage) {
|
||||
promises.push(util.writeFile(this.paths['package.json'], JSON.stringify(this.package,"",4)));
|
||||
}
|
||||
return when.settle(promises).then(function(res) {
|
||||
return {
|
||||
flowFilesChanged: flowFilesChanged,
|
||||
credentialSecretChanged: credentialSecretChanged
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
Project.prototype.getFiles = function () {
|
||||
return gitTools.getFiles(this.path).catch(function(err) {
|
||||
if (/ambiguous argument/.test(err.message)) {
|
||||
return {};
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
};
|
||||
Project.prototype.stageFile = function(file) {
|
||||
return gitTools.stageFile(this.path,file);
|
||||
};
|
||||
Project.prototype.unstageFile = function(file) {
|
||||
return gitTools.unstageFile(this.path,file);
|
||||
}
|
||||
Project.prototype.commit = function(user, options) {
|
||||
var self = this;
|
||||
return gitTools.commit(this.path,options.message,getGitUser(user)).then(function() {
|
||||
if (self.merging) {
|
||||
self.merging = false;
|
||||
return
|
||||
}
|
||||
});
|
||||
}
|
||||
Project.prototype.getFileDiff = function(file,type) {
|
||||
return gitTools.getFileDiff(this.path,file,type);
|
||||
}
|
||||
Project.prototype.getCommits = function(options) {
|
||||
return gitTools.getCommits(this.path,options).catch(function(err) {
|
||||
if (/bad default revision/i.test(err.message) || /ambiguous argument/i.test(err.message) || /does not have any commits yet/i.test(err.message)) {
|
||||
return {
|
||||
count:0,
|
||||
commits:[],
|
||||
total: 0
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
})
|
||||
}
|
||||
Project.prototype.getCommit = function(sha) {
|
||||
return gitTools.getCommit(this.path,sha);
|
||||
}
|
||||
Project.prototype.getFile = function (filePath,treeish) {
|
||||
if (treeish !== "_") {
|
||||
return gitTools.getFile(this.path, filePath, treeish);
|
||||
} else {
|
||||
return fs.readFile(fspath.join(this.path,filePath),"utf8");
|
||||
}
|
||||
};
|
||||
Project.prototype.revertFile = function (filePath) {
|
||||
var self = this;
|
||||
return gitTools.revertFile(this.path, filePath).then(function() {
|
||||
return self.load();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
Project.prototype.status = function(user, includeRemote) {
|
||||
var self = this;
|
||||
|
||||
var fetchPromise;
|
||||
if (this.remotes && includeRemote) {
|
||||
fetchPromise = gitTools.getRemoteBranch(self.path).then(function(remoteBranch) {
|
||||
if (remoteBranch) {
|
||||
var allRemotes = Object.keys(self.remotes);
|
||||
var match = "";
|
||||
allRemotes.forEach(function(remote) {
|
||||
if (remoteBranch.indexOf(remote) === 0 && match.length < remote.length) {
|
||||
match = remote;
|
||||
}
|
||||
})
|
||||
return self.fetch(user, match);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
fetchPromise = Promise.resolve();
|
||||
}
|
||||
|
||||
var completeStatus = function(fetchError) {
|
||||
var promises = [
|
||||
gitTools.getStatus(self.path),
|
||||
fs.exists(fspath.join(self.path,".git","MERGE_HEAD"))
|
||||
];
|
||||
return when.all(promises).then(function(results) {
|
||||
var result = results[0];
|
||||
if (results[1]) {
|
||||
result.merging = true;
|
||||
if (!self.merging) {
|
||||
self.merging = true;
|
||||
runtime.events.emit("runtime-event",{
|
||||
id:"runtime-state",
|
||||
payload:{
|
||||
type:"warning",
|
||||
error:"git_merge_conflict",
|
||||
project:self.name,
|
||||
text:"notification.warnings.git_merge_conflict"
|
||||
},
|
||||
retain:true}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.merging = false;
|
||||
}
|
||||
self.branches.local = result.branches.local;
|
||||
self.branches.remote = result.branches.remote;
|
||||
if (fetchError && !/ambiguous argument/.test(fetchError.message)) {
|
||||
result.branches.remoteError = {
|
||||
remote: fetchError.remote,
|
||||
code: fetchError.code
|
||||
}
|
||||
}
|
||||
if (result.commits.total === 0 && Object.keys(result.files).length === 0) {
|
||||
if (!self.empty) {
|
||||
runtime.events.emit("runtime-event",{
|
||||
id:"runtime-state",
|
||||
payload:{
|
||||
type:"warning",
|
||||
error:"project_empty",
|
||||
text:"notification.warnings.project_empty"},
|
||||
retain:true
|
||||
}
|
||||
);
|
||||
}
|
||||
self.empty = true;
|
||||
} else {
|
||||
if (self.empty) {
|
||||
if (self.paths.flowFile) {
|
||||
runtime.events.emit("runtime-event",{id:"runtime-state",retain:true});
|
||||
} else {
|
||||
runtime.events.emit("runtime-event",{
|
||||
id:"runtime-state",
|
||||
payload:{
|
||||
type:"warning",
|
||||
error:"missing_flow_file",
|
||||
text:"notification.warnings.missing_flow_file"},
|
||||
retain:true
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
delete self.empty;
|
||||
}
|
||||
return result;
|
||||
}).catch(function(err) {
|
||||
if (/ambiguous argument/.test(err.message)) {
|
||||
return {
|
||||
files:{},
|
||||
commits:{total:0},
|
||||
branches:{}
|
||||
};
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
return fetchPromise.then(completeStatus).catch(function(e) {
|
||||
// if (e.code !== 'git_auth_failed') {
|
||||
// console.log("Fetch failed");
|
||||
// console.log(e);
|
||||
// }
|
||||
return completeStatus(e);
|
||||
})
|
||||
};
|
||||
|
||||
Project.prototype.push = function (user,remoteBranchName,setRemote) {
|
||||
var username;
|
||||
if (!user) {
|
||||
username = "_";
|
||||
} else {
|
||||
username = user.username;
|
||||
}
|
||||
var remote = this.parseRemoteBranch(remoteBranchName||this.branches.remote);
|
||||
return gitTools.push(this.path, remote.remote || this.currentRemote,remote.branch, setRemote, authCache.get(this.name,this.remotes[remote.remote || this.currentRemote].fetch,username));
|
||||
};
|
||||
|
||||
Project.prototype.pull = function (user,remoteBranchName,setRemote,allowUnrelatedHistories) {
|
||||
var username;
|
||||
if (!user) {
|
||||
username = "_";
|
||||
} else {
|
||||
username = user.username;
|
||||
}
|
||||
var self = this;
|
||||
if (setRemote) {
|
||||
return gitTools.setUpstream(this.path, remoteBranchName).then(function() {
|
||||
self.currentRemote = self.parseRemoteBranch(remoteBranchName).remote;
|
||||
return gitTools.pull(self.path, null, null, allowUnrelatedHistories, authCache.get(self.name,self.remotes[self.currentRemote].fetch,username),getGitUser(user));
|
||||
})
|
||||
} else {
|
||||
var remote = this.parseRemoteBranch(remoteBranchName);
|
||||
return gitTools.pull(this.path, remote.remote, remote.branch, allowUnrelatedHistories, authCache.get(this.name,this.remotes[remote.remote||self.currentRemote].fetch,username),getGitUser(user));
|
||||
}
|
||||
};
|
||||
|
||||
Project.prototype.resolveMerge = function (file,resolutions) {
|
||||
var filePath = fspath.join(this.path,file);
|
||||
var self = this;
|
||||
if (typeof resolutions === 'string') {
|
||||
return util.writeFile(filePath, resolutions).then(function() {
|
||||
return self.stageFile(file);
|
||||
})
|
||||
}
|
||||
return fs.readFile(filePath,"utf8").then(function(content) {
|
||||
var lines = content.split("\n");
|
||||
var result = [];
|
||||
var ignoreBlock = false;
|
||||
var currentBlock;
|
||||
for (var i=1;i<=lines.length;i++) {
|
||||
if (resolutions.hasOwnProperty(i)) {
|
||||
currentBlock = resolutions[i];
|
||||
if (currentBlock.selection === "A") {
|
||||
ignoreBlock = false;
|
||||
} else {
|
||||
ignoreBlock = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (currentBlock) {
|
||||
if (currentBlock.separator === i) {
|
||||
if (currentBlock.selection === "A") {
|
||||
ignoreBlock = true;
|
||||
} else {
|
||||
ignoreBlock = false;
|
||||
}
|
||||
continue;
|
||||
} else if (currentBlock.changeEnd === i) {
|
||||
currentBlock = null;
|
||||
continue;
|
||||
} else if (ignoreBlock) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result.push(lines[i-1]);
|
||||
}
|
||||
var finalResult = result.join("\n");
|
||||
return util.writeFile(filePath,finalResult).then(function() {
|
||||
return self.stageFile(file);
|
||||
})
|
||||
});
|
||||
};
|
||||
Project.prototype.abortMerge = function () {
|
||||
var self = this;
|
||||
return gitTools.abortMerge(this.path).then(function() {
|
||||
self.merging = false;
|
||||
})
|
||||
};
|
||||
|
||||
Project.prototype.getBranches = function (user, isRemote) {
|
||||
var self = this;
|
||||
var fetchPromise;
|
||||
if (isRemote) {
|
||||
fetchPromise = self.fetch(user);
|
||||
} else {
|
||||
fetchPromise = Promise.resolve();
|
||||
}
|
||||
return fetchPromise.then(function() {
|
||||
return gitTools.getBranches(self.path,isRemote);
|
||||
});
|
||||
};
|
||||
|
||||
Project.prototype.deleteBranch = function (user, branch, isRemote, force) {
|
||||
// TODO: isRemote==true support
|
||||
// TODO: make sure we don't try to delete active branch
|
||||
return gitTools.deleteBranch(this.path,branch,isRemote, force);
|
||||
};
|
||||
|
||||
Project.prototype.fetch = function(user,remoteName) {
|
||||
var username;
|
||||
if (!user) {
|
||||
username = "_";
|
||||
} else {
|
||||
username = user.username;
|
||||
}
|
||||
var project = this;
|
||||
if (remoteName) {
|
||||
return gitTools.fetch(project.path,remoteName,authCache.get(project.name,project.remotes[remoteName].fetch,username)).catch(function(err) {
|
||||
err.remote = remoteName;
|
||||
throw err;
|
||||
})
|
||||
} else {
|
||||
var remotes = Object.keys(this.remotes);
|
||||
var promise = Promise.resolve();
|
||||
remotes.forEach(function(remote) {
|
||||
promise = promise.then(function() {
|
||||
return gitTools.fetch(project.path,remote,authCache.get(project.name,project.remotes[remote].fetch,username))
|
||||
}).catch(function(err) {
|
||||
err.remote = remote;
|
||||
throw err;
|
||||
})
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
|
||||
Project.prototype.setBranch = function (branchName, isCreate) {
|
||||
var self = this;
|
||||
return gitTools.checkoutBranch(this.path, branchName, isCreate).then(function() {
|
||||
return self.load();
|
||||
})
|
||||
};
|
||||
Project.prototype.getBranchStatus = function (branchName) {
|
||||
return gitTools.getBranchStatus(this.path,branchName);
|
||||
};
|
||||
|
||||
|
||||
|
||||
Project.prototype.getRemotes = function (user) {
|
||||
return gitTools.getRemotes(this.path).then(function(remotes) {
|
||||
var result = [];
|
||||
for (var name in remotes) {
|
||||
if (remotes.hasOwnProperty(name)) {
|
||||
remotes[name].name = name;
|
||||
result.push(remotes[name]);
|
||||
}
|
||||
}
|
||||
return {remotes:result};
|
||||
})
|
||||
};
|
||||
Project.prototype.addRemote = function(user,remote,options) {
|
||||
var project = this;
|
||||
return gitTools.addRemote(this.path,remote,options).then(function() {
|
||||
return project.loadRemotes()
|
||||
});
|
||||
}
|
||||
Project.prototype.updateRemote = function(user,remote,options) {
|
||||
var username;
|
||||
if (!user) {
|
||||
username = "_";
|
||||
} else {
|
||||
username = user.username;
|
||||
}
|
||||
|
||||
if (options.auth) {
|
||||
var url = this.remotes[remote].fetch;
|
||||
if (options.auth.keyFile) {
|
||||
options.auth.key_path = sshKeys.getPrivateKeyPath(getSSHKeyUsername(user), options.auth.keyFile);
|
||||
}
|
||||
authCache.set(this.name,url,username,options.auth);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
Project.prototype.removeRemote = function(user, remote) {
|
||||
// TODO: if this was the last remote using this url, then remove the authCache
|
||||
// details.
|
||||
var project = this;
|
||||
return gitTools.removeRemote(this.path,remote).then(function() {
|
||||
return project.loadRemotes()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Project.prototype.getFlowFile = function() {
|
||||
// console.log("Project.getFlowFile = ",this.paths.flowFile);
|
||||
if (this.paths.flowFile) {
|
||||
return fspath.join(this.path,this.paths.flowFile);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Project.prototype.getFlowFileBackup = function() {
|
||||
var flowFile = this.getFlowFile();
|
||||
if (flowFile) {
|
||||
return getBackupFilename(flowFile);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Project.prototype.getCredentialsFile = function() {
|
||||
// console.log("Project.getCredentialsFile = ",this.paths.credentialsFile);
|
||||
if (this.paths.credentialsFile) {
|
||||
return fspath.join(this.path,this.paths.credentialsFile);
|
||||
} else {
|
||||
return this.paths.credentialsFile;
|
||||
}
|
||||
}
|
||||
Project.prototype.getCredentialsFileBackup = function() {
|
||||
return getBackupFilename(this.getCredentialsFile());
|
||||
}
|
||||
|
||||
Project.prototype.export = function () {
|
||||
|
||||
return {
|
||||
name: this.name,
|
||||
summary: this.package.description,
|
||||
description: this.description,
|
||||
dependencies: this.package.dependencies||{},
|
||||
empty: this.empty,
|
||||
settings: {
|
||||
credentialsEncrypted: (typeof this.credentialSecret === "string"),
|
||||
credentialSecretInvalid: this.credentialSecretInvalid
|
||||
},
|
||||
files: {
|
||||
flow: this.paths.flowFile,
|
||||
credentials: this.paths.credentialsFile
|
||||
},
|
||||
git: {
|
||||
remotes: this.remotes,
|
||||
branches: this.branches
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function getCredentialsFilename(filename) {
|
||||
filename = filename || "undefined";
|
||||
// TODO: DRY - ./index.js
|
||||
var ffDir = fspath.dirname(filename);
|
||||
var ffExt = fspath.extname(filename);
|
||||
var ffBase = fspath.basename(filename,ffExt);
|
||||
return fspath.join(ffDir,ffBase+"_cred"+ffExt);
|
||||
}
|
||||
function getBackupFilename(filename) {
|
||||
// TODO: DRY - ./index.js
|
||||
filename = filename || "undefined";
|
||||
var ffName = fspath.basename(filename);
|
||||
var ffDir = fspath.dirname(filename);
|
||||
return fspath.join(ffDir,"."+ffName+".backup");
|
||||
}
|
||||
|
||||
function checkProjectExists(projectPath) {
|
||||
return fs.pathExists(projectPath).then(function(exists) {
|
||||
if (!exists) {
|
||||
var e = new Error("Project not found");
|
||||
e.code = "project_not_found";
|
||||
var name = fspath.basename(projectPath);
|
||||
e.project = name;
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createDefaultProject(user, project) {
|
||||
var projectPath = fspath.join(projectsDir,project.name);
|
||||
// Create a basic skeleton of a project
|
||||
return gitTools.initRepo(projectPath).then(function() {
|
||||
var promises = [];
|
||||
var files = Object.keys(defaultFileSet);
|
||||
if (project.files) {
|
||||
if (project.files.flow && !/\.\./.test(project.files.flow)) {
|
||||
var flowFilePath;
|
||||
var credsFilePath;
|
||||
|
||||
if (project.migrateFiles) {
|
||||
var baseFlowFileName = project.files.flow || fspath.basename(project.files.oldFlow);
|
||||
var baseCredentialFileName = project.files.credentials || fspath.basename(project.files.oldCredentials);
|
||||
files.push(baseFlowFileName);
|
||||
files.push(baseCredentialFileName);
|
||||
flowFilePath = fspath.join(projectPath,baseFlowFileName);
|
||||
credsFilePath = fspath.join(projectPath,baseCredentialFileName);
|
||||
if (fs.existsSync(project.files.oldFlow)) {
|
||||
log.trace("Migrating "+project.files.oldFlow+" to "+flowFilePath);
|
||||
promises.push(fs.copy(project.files.oldFlow,flowFilePath));
|
||||
} else {
|
||||
log.trace(project.files.oldFlow+" does not exist - creating blank file");
|
||||
promises.push(util.writeFile(flowFilePath,"[]"));
|
||||
}
|
||||
log.trace("Migrating "+project.files.oldCredentials+" to "+credsFilePath);
|
||||
runtime.nodes.setCredentialSecret(project.credentialSecret);
|
||||
promises.push(runtime.nodes.exportCredentials().then(function(creds) {
|
||||
var credentialData;
|
||||
if (settings.flowFilePretty) {
|
||||
credentialData = JSON.stringify(creds,null,4);
|
||||
} else {
|
||||
credentialData = JSON.stringify(creds);
|
||||
}
|
||||
return util.writeFile(credsFilePath,credentialData);
|
||||
}));
|
||||
delete project.migrateFiles;
|
||||
project.files.flow = baseFlowFileName;
|
||||
project.files.credentials = baseCredentialFileName;
|
||||
} else {
|
||||
project.files.credentials = project.files.credentials || getCredentialsFilename(project.files.flow);
|
||||
files.push(project.files.flow);
|
||||
files.push(project.files.credentials);
|
||||
flowFilePath = fspath.join(projectPath,project.files.flow);
|
||||
credsFilePath = getCredentialsFilename(flowFilePath);
|
||||
promises.push(util.writeFile(flowFilePath,"[]"));
|
||||
promises.push(util.writeFile(credsFilePath,"{}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var file in defaultFileSet) {
|
||||
if (defaultFileSet.hasOwnProperty(file)) {
|
||||
promises.push(util.writeFile(fspath.join(projectPath,file),defaultFileSet[file](project, runtime)));
|
||||
}
|
||||
}
|
||||
|
||||
return when.all(promises).then(function() {
|
||||
return gitTools.stageFile(projectPath,files);
|
||||
}).then(function() {
|
||||
return gitTools.commit(projectPath,"Create project",getGitUser(user));
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function checkProjectFiles(project) {
|
||||
var projectPath = project.path;
|
||||
var promises = [];
|
||||
var paths = [];
|
||||
for (var file in defaultFileSet) {
|
||||
if (defaultFileSet.hasOwnProperty(file)) {
|
||||
paths.push(file);
|
||||
promises.push(fs.stat(fspath.join(projectPath,file)));
|
||||
}
|
||||
}
|
||||
return when.settle(promises).then(function(results) {
|
||||
var missing = [];
|
||||
results.forEach(function(result,i) {
|
||||
if (result.state === 'rejected') {
|
||||
missing.push(paths[i]);
|
||||
}
|
||||
});
|
||||
return missing;
|
||||
}).then(function(missing) {
|
||||
// if (createMissing) {
|
||||
// var promises = [];
|
||||
// missing.forEach(function(file) {
|
||||
// promises.push(util.writeFile(fspath.join(projectPath,file),defaultFileSet[file](project)));
|
||||
// });
|
||||
// return promises;
|
||||
// } else {
|
||||
return missing;
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
function createProject(user, metadata) {
|
||||
var username;
|
||||
if (!user) {
|
||||
username = "_";
|
||||
} else {
|
||||
username = user.username;
|
||||
}
|
||||
if (!metadata.path) {
|
||||
throw new Error("Project missing path property");
|
||||
}
|
||||
if (!metadata.name) {
|
||||
throw new Error("Project missing name property");
|
||||
}
|
||||
|
||||
var project = metadata.name;
|
||||
var projectPath = metadata.path;
|
||||
return new Promise(function(resolve,reject) {
|
||||
fs.stat(projectPath, function(err,stat) {
|
||||
if (!err) {
|
||||
var e = new Error("NLS: Project already exists");
|
||||
e.code = "project_exists";
|
||||
return reject(e);
|
||||
}
|
||||
fs.ensureDir(projectPath).then(function() {
|
||||
var projects = settings.get('projects');
|
||||
if (!projects) {
|
||||
projects = {
|
||||
projects:{}
|
||||
}
|
||||
}
|
||||
projects.projects[project] = {};
|
||||
if (metadata.hasOwnProperty('credentialSecret')) {
|
||||
projects.projects[project].credentialSecret = metadata.credentialSecret;
|
||||
}
|
||||
return settings.set('projects',projects);
|
||||
}).then(function() {
|
||||
if (metadata.git && metadata.git.remotes && metadata.git.remotes.origin) {
|
||||
var originRemote = metadata.git.remotes.origin;
|
||||
var auth;
|
||||
if (originRemote.hasOwnProperty("username") && originRemote.hasOwnProperty("password")) {
|
||||
authCache.set(project,originRemote.url,username,{ // TODO: hardcoded remote name
|
||||
username: originRemote.username,
|
||||
password: originRemote.password
|
||||
}
|
||||
);
|
||||
auth = authCache.get(project,originRemote.url,username);
|
||||
}
|
||||
else if (originRemote.hasOwnProperty("keyFile") && originRemote.hasOwnProperty("passphrase")) {
|
||||
authCache.set(project,originRemote.url,username,{ // TODO: hardcoded remote name
|
||||
key_path: sshKeys.getPrivateKeyPath(getSSHKeyUsername(user), originRemote.keyFile),
|
||||
passphrase: originRemote.passphrase
|
||||
}
|
||||
);
|
||||
auth = authCache.get(project,originRemote.url,username);
|
||||
}
|
||||
return gitTools.clone(originRemote,auth,projectPath);
|
||||
} else {
|
||||
return createDefaultProject(user, metadata);
|
||||
}
|
||||
}).then(function() {
|
||||
resolve(loadProject(projectPath))
|
||||
}).catch(function(err) {
|
||||
fs.remove(projectPath,function() {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function deleteProject(user, projectPath) {
|
||||
return checkProjectExists(projectPath).then(function() {
|
||||
return fs.remove(projectPath).then(function() {
|
||||
var name = fspath.basename(projectPath);
|
||||
var projects = settings.get('projects');
|
||||
delete projects.projects[name];
|
||||
return settings.set('projects', projects);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadProject(projectPath) {
|
||||
return checkProjectExists(projectPath).then(function() {
|
||||
var project = new Project(projectPath);
|
||||
return project.load();
|
||||
});
|
||||
}
|
||||
|
||||
function init(_settings, _runtime) {
|
||||
settings = _settings;
|
||||
runtime = _runtime;
|
||||
log = runtime.log;
|
||||
projectsDir = fspath.join(settings.userDir,"projects");
|
||||
authCache.init();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
load: loadProject,
|
||||
create: createProject,
|
||||
delete: deleteProject
|
||||
}
|
48
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/defaultFileSet.js
generated
vendored
Normal file
48
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/defaultFileSet.js
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
|
||||
module.exports = {
|
||||
"package.json": function(project, runtime) {
|
||||
var i18n = runtime.i18n;
|
||||
var package = {
|
||||
"name": project.name,
|
||||
"description": project.summary||i18n._("storage.localfilesystem.projects.summary"),
|
||||
"version": "0.0.1",
|
||||
"dependencies": {},
|
||||
"node-red": {
|
||||
"settings": {
|
||||
}
|
||||
}
|
||||
};
|
||||
if (project.files) {
|
||||
if (project.files.flow) {
|
||||
package['node-red'].settings.flowFile = project.files.flow;
|
||||
package['node-red'].settings.credentialsFile = project.files.credentials;
|
||||
}
|
||||
}
|
||||
return JSON.stringify(package,"",4);
|
||||
},
|
||||
"README.md": function(project, runtime) {
|
||||
var i18n = runtime.i18n;
|
||||
var content = project.name+"\n"+("=".repeat(project.name.length))+"\n\n";
|
||||
if (project.summary) {
|
||||
content += project.summary+"\n\n";
|
||||
}
|
||||
content += i18n._("storage.localfilesystem.projects.readme");
|
||||
return content;
|
||||
},
|
||||
".gitignore": function() { return "*.backup" ;}
|
||||
}
|
46
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/authCache.js
generated
vendored
Normal file
46
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/authCache.js
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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 authCache = {}
|
||||
|
||||
module.exports = {
|
||||
init: function() {
|
||||
authCache = {};
|
||||
},
|
||||
clear: function(project,remote, user) {
|
||||
if (user && remote && authCache[project] && authCache[project][remote]) {
|
||||
delete authCache[project][remote][user];
|
||||
} else if (remote && authCache.hasOwnProperty(project)) {
|
||||
delete authCache[project][remote];
|
||||
} else {
|
||||
delete authCache[project];
|
||||
}
|
||||
},
|
||||
set: function(project,remote,user,auth) {
|
||||
// console.log("AuthCache.set",remote,user,auth);
|
||||
authCache[project] = authCache[project]||{};
|
||||
authCache[project][remote] = authCache[project][remote]||{};
|
||||
authCache[project][remote][user] = auth;
|
||||
// console.log(JSON.stringify(authCache,'',4));
|
||||
},
|
||||
get: function(project,remote,user) {
|
||||
// console.log("AuthCache.get",remote,user,authCache[project]&&authCache[project][remote]&&authCache[project][remote][user]);
|
||||
if (authCache[project] && authCache[project][remote]) {
|
||||
return authCache[project][remote][user];
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
132
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/authServer.js
generated
vendored
Normal file
132
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/authServer.js
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* 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 net = require("net");
|
||||
var fs = require("fs-extra");
|
||||
var path = require("path");
|
||||
var os = require("os");
|
||||
|
||||
function getListenPath() {
|
||||
var seed = (0x100000+Math.random()*0x999999).toString(16);
|
||||
var fn = 'node-red-git-askpass-'+seed+'-sock';
|
||||
var listenPath;
|
||||
if (process.platform === 'win32') {
|
||||
listenPath = '\\\\.\\pipe\\'+fn;
|
||||
} else {
|
||||
listenPath = path.join(process.env['XDG_RUNTIME_DIR'] || os.tmpdir(), fn);
|
||||
}
|
||||
// console.log(listenPath);
|
||||
return listenPath;
|
||||
}
|
||||
|
||||
|
||||
|
||||
var ResponseServer = function(auth) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var server = net.createServer(function(connection) {
|
||||
connection.setEncoding('utf8');
|
||||
var parts = [];
|
||||
connection.on('data', function(data) {
|
||||
var m = data.indexOf("\n");
|
||||
if (m !== -1) {
|
||||
parts.push(data.substring(0, m));
|
||||
data = data.substring(m);
|
||||
var line = parts.join("");
|
||||
// console.log("LINE:",line);
|
||||
parts = [];
|
||||
if (line==='Username') {
|
||||
connection.end(auth.username);
|
||||
} else if (line === 'Password') {
|
||||
connection.end(auth.password);
|
||||
server.close();
|
||||
} else {
|
||||
}
|
||||
}
|
||||
if (data.length > 0) {
|
||||
parts.push(data);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
var listenPath = getListenPath();
|
||||
|
||||
server.listen(listenPath, function(ready) {
|
||||
resolve({path:listenPath,close:function() { server.close(); }});
|
||||
});
|
||||
server.on('close', function() {
|
||||
// console.log("Closing response server");
|
||||
fs.removeSync(listenPath);
|
||||
});
|
||||
server.on('error',function(err) {
|
||||
console.log("ResponseServer unexpectedError:",err.toString());
|
||||
server.close();
|
||||
reject(err);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
var ResponseSSHServer = function(auth) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var server = net.createServer(function(connection) {
|
||||
connection.setEncoding('utf8');
|
||||
var parts = [];
|
||||
connection.on('data', function(data) {
|
||||
var m = data.indexOf("\n");
|
||||
if (m !== -1) {
|
||||
parts.push(data.substring(0, m));
|
||||
data = data.substring(m);
|
||||
var line = parts.join("");
|
||||
parts = [];
|
||||
if (line==='The') {
|
||||
// TODO: document these exchanges!
|
||||
connection.end('yes');
|
||||
// server.close();
|
||||
} else if (line === 'Enter') {
|
||||
connection.end(auth.passphrase);
|
||||
// server.close();
|
||||
} else {
|
||||
}
|
||||
}
|
||||
if (data.length > 0) {
|
||||
parts.push(data);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
var listenPath = getListenPath();
|
||||
|
||||
server.listen(listenPath, function(ready) {
|
||||
resolve({path:listenPath,close:function() { server.close(); }});
|
||||
});
|
||||
server.on('close', function() {
|
||||
// console.log("Closing response server");
|
||||
fs.removeSync(listenPath);
|
||||
});
|
||||
server.on('error',function(err) {
|
||||
console.log("ResponseServer unexpectedError:",err.toString());
|
||||
server.close();
|
||||
reject(err);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
ResponseServer: ResponseServer,
|
||||
ResponseSSHServer: ResponseSSHServer
|
||||
}
|
24
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/authWriter.js
generated
vendored
Normal file
24
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/authWriter.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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 net = require("net");
|
||||
var socket = net.connect(process.argv[2], function() {
|
||||
socket.on('data', function(data) { console.log(data);});
|
||||
socket.on('end', function() {
|
||||
});
|
||||
socket.write((process.argv[3]||"")+"\n", 'utf8');
|
||||
});
|
||||
socket.setEncoding('utf8');
|
660
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js
generated
vendored
Normal file
660
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js
generated
vendored
Normal file
@@ -0,0 +1,660 @@
|
||||
/**
|
||||
* 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 when = require('when');
|
||||
var exec = require('child_process').exec;
|
||||
var spawn = require('child_process').spawn;
|
||||
var authResponseServer = require('./authServer').ResponseServer;
|
||||
var sshResponseServer = require('./authServer').ResponseSSHServer;
|
||||
var clone = require('clone');
|
||||
var path = require("path");
|
||||
|
||||
var gitCommand = "git";
|
||||
var gitVersion;
|
||||
var log;
|
||||
|
||||
function runGitCommand(args,cwd,env) {
|
||||
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) {
|
||||
var err = new Error(stderr);
|
||||
err.stdout = stdout;
|
||||
err.stderr = stderr;
|
||||
if (/Connection refused/i.test(stderr)) {
|
||||
err.code = "git_connection_failed";
|
||||
} else if (/Connection timed out/i.test(stderr)) {
|
||||
err.code = "git_connection_failed";
|
||||
} else if (/fatal: could not read/i.test(stderr)) {
|
||||
// Username/Password
|
||||
err.code = "git_auth_failed";
|
||||
} else if(/HTTP Basic: Access denied/i.test(stderr)) {
|
||||
err.code = "git_auth_failed";
|
||||
} else if(/Permission denied \(publickey\)/i.test(stderr)) {
|
||||
err.code = "git_auth_failed";
|
||||
} else if(/Host key verification failed/i.test(stderr)) {
|
||||
// TODO: handle host key verification errors separately
|
||||
err.code = "git_auth_failed";
|
||||
} else if (/commit your changes or stash/i.test(stderr)) {
|
||||
err.code = "git_local_overwrite";
|
||||
} else if (/CONFLICT/.test(err.stdout)) {
|
||||
err.code = "git_pull_merge_conflict";
|
||||
} else if (/not fully merged/i.test(stderr)) {
|
||||
err.code = "git_delete_branch_unmerged";
|
||||
} else if (/remote .* already exists/i.test(stderr)) {
|
||||
err.code = "git_remote_already_exists";
|
||||
} else if (/does not appear to be a git repository/i.test(stderr)) {
|
||||
err.code = "git_not_a_repository";
|
||||
} else if (/Repository not found/i.test(stderr)) {
|
||||
err.code = "git_repository_not_found";
|
||||
} 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) {
|
||||
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() {
|
||||
rs.close();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function runGitCommandWithSSHCommand(args,cwd,auth) {
|
||||
return sshResponseServer(auth).then(function(rs) {
|
||||
var commandEnv = clone(process.env);
|
||||
commandEnv.SSH_ASKPASS = path.join(__dirname,"node-red-ask-pass.sh");
|
||||
commandEnv.DISPLAY = "dummy:0";
|
||||
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");
|
||||
// For git < 2.3.0
|
||||
commandEnv.GIT_SSH = path.join(__dirname,"node-red-ssh.sh");
|
||||
commandEnv.NODE_RED_KEY_FILE=auth.key_path;
|
||||
// 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() {
|
||||
rs.close();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function cleanFilename(name) {
|
||||
if (name[0] !== '"') {
|
||||
return name;
|
||||
}
|
||||
return name.substring(1,name.length-1);
|
||||
}
|
||||
function parseFilenames(name) {
|
||||
var re = /([^ "]+|(".*?"))($| -> ([^ ]+|(".*"))$)/;
|
||||
var m = re.exec(name);
|
||||
var result = [];
|
||||
if (m) {
|
||||
result.push(cleanFilename(m[1]));
|
||||
if (m[4]) {
|
||||
result.push(cleanFilename(m[4]));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// function getBranchInfo(localRepo) {
|
||||
// return runGitCommand(["status","--porcelain","-b"],localRepo).then(function(output) {
|
||||
// var lines = output.split("\n");
|
||||
// var unknownDirs = [];
|
||||
// var branchLineRE = /^## (No commits yet on )?(.+?)($|\.\.\.(.+?)($| \[(ahead (\d+))?.*?(behind (\d+))?\]))/m;
|
||||
// console.log(output);
|
||||
// console.log(lines);
|
||||
// var m = branchLineRE.exec(output);
|
||||
// console.log(m);
|
||||
// var result = {}; //commits:{}};
|
||||
// if (m) {
|
||||
// if (m[1]) {
|
||||
// result.empty = true;
|
||||
// }
|
||||
// result.local = m[2];
|
||||
// if (m[4]) {
|
||||
// result.remote = m[4];
|
||||
// }
|
||||
// }
|
||||
// return result;
|
||||
// });
|
||||
// }
|
||||
function getStatus(localRepo) {
|
||||
// parseFilename('"test with space"');
|
||||
// parseFilename('"test with space" -> knownFile.txt');
|
||||
// parseFilename('"test with space" -> "un -> knownFile.txt"');
|
||||
var result = {
|
||||
files: {},
|
||||
commits: {},
|
||||
branches: {}
|
||||
}
|
||||
return runGitCommand(['rev-list', 'HEAD', '--count'],localRepo).then(function(count) {
|
||||
result.commits.total = parseInt(count);
|
||||
}).catch(function(err) {
|
||||
if (/ambiguous argument/i.test(err.message)) {
|
||||
result.commits.total = 0;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}).then(function() {
|
||||
return runGitCommand(["ls-files","--cached","--others","--exclude-standard"],localRepo).then(function(output) {
|
||||
var lines = output.split("\n");
|
||||
lines.forEach(function(l) {
|
||||
if (l==="") {
|
||||
return;
|
||||
}
|
||||
var fullName = cleanFilename(l);
|
||||
// parseFilename(l);
|
||||
var parts = fullName.split("/");
|
||||
var p = result.files;
|
||||
var name;
|
||||
for (var i = 0;i<parts.length-1;i++) {
|
||||
var name = parts.slice(0,i+1).join("/")+"/";
|
||||
if (!p.hasOwnProperty(name)) {
|
||||
p[name] = {
|
||||
type:"d"
|
||||
}
|
||||
}
|
||||
}
|
||||
result.files[fullName] = {
|
||||
type: /\/$/.test(fullName)?"d":"f"
|
||||
}
|
||||
})
|
||||
return runGitCommand(["status","--porcelain","-b"],localRepo).then(function(output) {
|
||||
var lines = output.split("\n");
|
||||
var unknownDirs = [];
|
||||
var branchLineRE = /^## (?:(?:No commits yet on )|(?:Initial commit on))?(.+?)(?:$|\.\.\.(.+?)(?:$| \[(?:(?:ahead (\d+)(?:,\s*)?)?(?:behind (\d+))?|(gone))\]))/;
|
||||
lines.forEach(function(line) {
|
||||
if (line==="") {
|
||||
return;
|
||||
}
|
||||
if (line[0] === "#") {
|
||||
var m = branchLineRE.exec(line);
|
||||
if (m) {
|
||||
result.branches.local = m[1];
|
||||
if (m[2]) {
|
||||
result.branches.remote = m[2];
|
||||
result.commits.ahead = 0;
|
||||
result.commits.behind = 0;
|
||||
}
|
||||
if (m[3] !== undefined) {
|
||||
result.commits.ahead = parseInt(m[3]);
|
||||
}
|
||||
if (m[4] !== undefined) {
|
||||
result.commits.behind = parseInt(m[4]);
|
||||
}
|
||||
if (m[5] !== undefined) {
|
||||
result.commits.ahead = result.commits.total;
|
||||
result.branches.remoteError = {
|
||||
code: "git_remote_gone"
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
var status = line.substring(0,2);
|
||||
var fileName;
|
||||
var names;
|
||||
if (status !== '??') {
|
||||
names = parseFilenames(line.substring(3));
|
||||
} else {
|
||||
names = [cleanFilename(line.substring(3))];
|
||||
}
|
||||
fileName = names[0];
|
||||
if (names.length > 1) {
|
||||
fileName = names[1];
|
||||
}
|
||||
|
||||
// parseFilename(fileName);
|
||||
if (fileName.charCodeAt(0) === 34) {
|
||||
fileName = fileName.substring(1,fileName.length-1);
|
||||
}
|
||||
if (result.files.hasOwnProperty(fileName)) {
|
||||
result.files[fileName].status = status;
|
||||
} else {
|
||||
result.files[fileName] = {
|
||||
type: "f",
|
||||
status: status
|
||||
};
|
||||
}
|
||||
if (names.length > 1) {
|
||||
result.files[fileName].oldName = names[0];
|
||||
}
|
||||
if (status === "??" && fileName[fileName.length-1] === '/') {
|
||||
unknownDirs.push(fileName);
|
||||
}
|
||||
})
|
||||
var allFilenames = Object.keys(result.files);
|
||||
allFilenames.forEach(function(f) {
|
||||
var entry = result.files[f];
|
||||
if (!entry.hasOwnProperty('status')) {
|
||||
unknownDirs.forEach(function(uf) {
|
||||
if (f.startsWith(uf)) {
|
||||
entry.status = "??"
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
// console.log(files);
|
||||
return result;
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function parseLog(log) {
|
||||
var lines = log.split("\n");
|
||||
var currentCommit = {};
|
||||
var commits = [];
|
||||
lines.forEach(function(l) {
|
||||
if (l === "-----") {
|
||||
commits.push(currentCommit);
|
||||
currentCommit = {}
|
||||
return;
|
||||
}
|
||||
var m = /^(.*): (.*)$/.exec(l);
|
||||
if (m) {
|
||||
// git 2.1.4 (Debian Stable) doesn't support %D for refs - so filter out
|
||||
if (m[1] === 'refs' && m[2]) {
|
||||
if (m[2] !== '%D') {
|
||||
currentCommit[m[1]] = m[2].split(",").map(function(v) { return v.trim() });
|
||||
} else {
|
||||
currentCommit[m[1]] = [];
|
||||
}
|
||||
} else {
|
||||
if (m[1] === 'parents') {
|
||||
currentCommit[m[1]] = m[2].split(" ");
|
||||
} else {
|
||||
currentCommit[m[1]] = m[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return commits;
|
||||
}
|
||||
|
||||
function getRemotes(cwd) {
|
||||
return runGitCommand(['remote','-v'],cwd).then(function(output) {
|
||||
var result;
|
||||
if (output.length > 0) {
|
||||
result = {};
|
||||
var remoteRE = /^(.+)\t(.+) \((.+)\)$/gm;
|
||||
var m;
|
||||
while ((m = remoteRE.exec(output)) !== null) {
|
||||
result[m[1]] = result[m[1]]||{};
|
||||
result[m[1]][m[3]] = m[2];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})
|
||||
}
|
||||
|
||||
function getBranches(cwd, remote) {
|
||||
var args = ['branch','-vv','--no-color'];
|
||||
if (remote) {
|
||||
args.push('-r');
|
||||
}
|
||||
var branchRE = /^([ \*] )(\S+) +(\S+)(?: \[(\S+?)(?:: (?:ahead (\d+)(?:, )?)?(?:behind (\d+))?)?\])? (.*)$/;
|
||||
return runGitCommand(args,cwd).then(function(output) {
|
||||
var branches = [];
|
||||
var lines = output.split("\n");
|
||||
branches = lines.map(function(l) {
|
||||
var m = branchRE.exec(l);
|
||||
var branch = null;
|
||||
if (m) {
|
||||
branch = {
|
||||
name: m[2],
|
||||
remote: m[4],
|
||||
status: {
|
||||
ahead: m[5]||0,
|
||||
behind: m[6]||0,
|
||||
},
|
||||
commit: {
|
||||
sha: m[3],
|
||||
subject: m[7]
|
||||
}
|
||||
}
|
||||
if (m[1] === '* ') {
|
||||
branch.current = true;
|
||||
}
|
||||
}
|
||||
return branch;
|
||||
}).filter(function(v) { return !!v && v.commit.sha !== '->' });
|
||||
|
||||
return {branches:branches};
|
||||
})
|
||||
}
|
||||
function getBranchStatus(cwd,remoteBranch) {
|
||||
var commands = [
|
||||
// #commits master ahead
|
||||
runGitCommand(['rev-list', 'HEAD','^'+remoteBranch, '--count'],cwd),
|
||||
// #commits master behind
|
||||
runGitCommand(['rev-list', '^HEAD',remoteBranch, '--count'],cwd)
|
||||
];
|
||||
return when.all(commands).then(function(results) {
|
||||
return {
|
||||
commits: {
|
||||
ahead: parseInt(results[0]),
|
||||
behind: parseInt(results[1])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function addRemote(cwd,name,options) {
|
||||
var args = ["remote","add",name,options.url]
|
||||
return runGitCommand(args,cwd);
|
||||
}
|
||||
function removeRemote(cwd,name) {
|
||||
var args = ["remote","remove",name];
|
||||
return runGitCommand(args,cwd);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function(_settings,_runtime) {
|
||||
log = _runtime.log
|
||||
return new Promise(function(resolve,reject) {
|
||||
Promise.all([
|
||||
runGitCommand(["--version"]),
|
||||
runGitCommand(["config","--global","user.name"]).catch(err=>""),
|
||||
runGitCommand(["config","--global","user.email"]).catch(err=>"")
|
||||
]).then(function(output) {
|
||||
var m = / (\d\S+)/.exec(output[0]);
|
||||
gitVersion = m[1];
|
||||
var globalUserName = output[1].trim();
|
||||
var globalUserEmail = output[2].trim();
|
||||
var result = {
|
||||
version: gitVersion
|
||||
};
|
||||
if (globalUserName && globalUserEmail) {
|
||||
result.user = {
|
||||
name: globalUserName,
|
||||
email: globalUserEmail
|
||||
}
|
||||
}
|
||||
log.trace("git init: "+JSON.stringify(result));
|
||||
resolve(result);
|
||||
}).catch(function(err) {
|
||||
log.trace("git init: git not found");
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
},
|
||||
initRepo: function(cwd) {
|
||||
return runGitCommand(["init"],cwd);
|
||||
},
|
||||
setUpstream: function(cwd,remoteBranch) {
|
||||
var args = ["branch","--set-upstream-to",remoteBranch];
|
||||
return runGitCommand(args,cwd);
|
||||
},
|
||||
pull: function(cwd,remote,branch,allowUnrelatedHistories,auth,gitUser) {
|
||||
var args = ["pull"];
|
||||
if (remote && branch) {
|
||||
args.push(remote);
|
||||
args.push(branch);
|
||||
}
|
||||
if (gitUser && gitUser['name'] && gitUser['email']) {
|
||||
args.unshift('user.name="'+gitUser['name']+'"');
|
||||
args.unshift('-c');
|
||||
args.unshift('user.email="'+gitUser['email']+'"');
|
||||
args.unshift('-c');
|
||||
}
|
||||
if (allowUnrelatedHistories) {
|
||||
args.push("--allow-unrelated-histories");
|
||||
}
|
||||
var promise;
|
||||
if (auth) {
|
||||
if ( auth.key_path ) {
|
||||
promise = runGitCommandWithSSHCommand(args,cwd,auth);
|
||||
}
|
||||
else {
|
||||
promise = runGitCommandWithAuth(args,cwd,auth);
|
||||
}
|
||||
} else {
|
||||
promise = runGitCommand(args,cwd)
|
||||
}
|
||||
return promise;
|
||||
// .catch(function(err) {
|
||||
// if (/CONFLICT/.test(err.stdout)) {
|
||||
// var e = new Error("pull failed - merge conflict");
|
||||
// e.code = "git_pull_merge_conflict";
|
||||
// throw e;
|
||||
// } else if (/Please commit your changes or stash/i.test(err.message)) {
|
||||
// var e = new Error("Pull failed - local changes would be overwritten");
|
||||
// e.code = "git_pull_overwrite";
|
||||
// throw e;
|
||||
// }
|
||||
// throw err;
|
||||
// });
|
||||
},
|
||||
push: function(cwd,remote,branch,setUpstream, auth) {
|
||||
var args = ["push"];
|
||||
if (branch) {
|
||||
if (setUpstream) {
|
||||
args.push("-u");
|
||||
}
|
||||
args.push(remote);
|
||||
args.push("HEAD:"+branch);
|
||||
} else {
|
||||
args.push(remote);
|
||||
}
|
||||
args.push("--porcelain");
|
||||
var promise;
|
||||
if (auth) {
|
||||
if ( auth.key_path ) {
|
||||
promise = runGitCommandWithSSHCommand(args,cwd,auth);
|
||||
}
|
||||
else {
|
||||
promise = runGitCommandWithAuth(args,cwd,auth);
|
||||
}
|
||||
} else {
|
||||
promise = runGitCommand(args,cwd)
|
||||
}
|
||||
return promise.catch(function(err) {
|
||||
if (err.code === 'git_error') {
|
||||
if (/^!.*non-fast-forward/m.test(err.stdout)) {
|
||||
err.code = 'git_push_failed';
|
||||
}
|
||||
throw err;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
},
|
||||
clone: function(remote, auth, cwd) {
|
||||
var args = ["clone",remote.url];
|
||||
if (remote.name) {
|
||||
args.push("-o");
|
||||
args.push(remote.name);
|
||||
}
|
||||
if (remote.branch) {
|
||||
args.push("-b");
|
||||
args.push(remote.branch);
|
||||
}
|
||||
args.push(".");
|
||||
if (auth) {
|
||||
if ( auth.key_path ) {
|
||||
return runGitCommandWithSSHCommand(args,cwd,auth);
|
||||
}
|
||||
else {
|
||||
return runGitCommandWithAuth(args,cwd,auth);
|
||||
}
|
||||
} else {
|
||||
return runGitCommand(args,cwd);
|
||||
}
|
||||
},
|
||||
getStatus: getStatus,
|
||||
getFile: function(cwd, filePath, treeish) {
|
||||
var args = ["show",treeish+":"+filePath];
|
||||
return runGitCommand(args,cwd);
|
||||
},
|
||||
getFiles: function(cwd) {
|
||||
return getStatus(cwd).then(function(status) {
|
||||
return status.files;
|
||||
})
|
||||
},
|
||||
revertFile: function(cwd, filePath) {
|
||||
var args = ["checkout",filePath];
|
||||
return runGitCommand(args,cwd);
|
||||
},
|
||||
stageFile: function(cwd,file) {
|
||||
var args = ["add"];
|
||||
if (Array.isArray(file)) {
|
||||
args = args.concat(file);
|
||||
} else {
|
||||
args.push(file);
|
||||
}
|
||||
return runGitCommand(args,cwd);
|
||||
},
|
||||
unstageFile: function(cwd, file) {
|
||||
var args = ["reset","--"];
|
||||
if (file) {
|
||||
args.push(file);
|
||||
}
|
||||
return runGitCommand(args,cwd);
|
||||
},
|
||||
commit: function(cwd, message, gitUser) {
|
||||
var args = ["commit","-m",message];
|
||||
var env;
|
||||
if (gitUser && gitUser['name'] && gitUser['email']) {
|
||||
args.unshift('user.name="'+gitUser['name']+'"');
|
||||
args.unshift('-c');
|
||||
args.unshift('user.email="'+gitUser['email']+'"');
|
||||
args.unshift('-c');
|
||||
}
|
||||
return runGitCommand(args,cwd,env);
|
||||
},
|
||||
getFileDiff(cwd,file,type) {
|
||||
var args = ["diff","-w"];
|
||||
if (type === "tree") {
|
||||
// nothing else to do
|
||||
} else if (type === "index") {
|
||||
args.push("--cached");
|
||||
}
|
||||
args.push(file);
|
||||
return runGitCommand(args,cwd);
|
||||
},
|
||||
fetch: function(cwd,remote,auth) {
|
||||
var args = ["fetch",remote];
|
||||
if (auth) {
|
||||
if ( auth.key_path ) {
|
||||
return runGitCommandWithSSHCommand(args,cwd,auth);
|
||||
}
|
||||
else {
|
||||
return runGitCommandWithAuth(args,cwd,auth);
|
||||
}
|
||||
} else {
|
||||
return runGitCommand(args,cwd);
|
||||
}
|
||||
},
|
||||
getCommits: function(cwd,options) {
|
||||
var args = ["log", "--format=sha: %H%nparents: %p%nrefs: %D%nauthor: %an%ndate: %ct%nsubject: %s%n-----"];
|
||||
var limit = parseInt(options.limit) || 20;
|
||||
args.push("-n "+limit);
|
||||
var before = options.before;
|
||||
if (before) {
|
||||
args.push(before);
|
||||
}
|
||||
var commands = [
|
||||
runGitCommand(['rev-list', 'HEAD', '--count'],cwd),
|
||||
runGitCommand(args,cwd).then(parseLog)
|
||||
];
|
||||
return when.all(commands).then(function(results) {
|
||||
var result = results[0];
|
||||
result.count = results[1].length;
|
||||
result.before = before;
|
||||
result.commits = results[1];
|
||||
return {
|
||||
count: results[1].length,
|
||||
commits: results[1],
|
||||
before: before,
|
||||
total: parseInt(results[0])
|
||||
};
|
||||
})
|
||||
},
|
||||
getCommit: function(cwd,sha) {
|
||||
var args = ["show",sha];
|
||||
return runGitCommand(args,cwd);
|
||||
},
|
||||
abortMerge: function(cwd) {
|
||||
return runGitCommand(['merge','--abort'],cwd);
|
||||
},
|
||||
getRemotes: getRemotes,
|
||||
getRemoteBranch: function(cwd) {
|
||||
return runGitCommand(['rev-parse','--abbrev-ref','--symbolic-full-name','@{u}'],cwd).catch(function(err) {
|
||||
if (/no upstream configured for branch/i.test(err.message)) {
|
||||
return null;
|
||||
}
|
||||
throw err;
|
||||
})
|
||||
},
|
||||
getBranches: getBranches,
|
||||
// getBranchInfo: getBranchInfo,
|
||||
checkoutBranch: function(cwd, branchName, isCreate) {
|
||||
var args = ['checkout'];
|
||||
if (isCreate) {
|
||||
args.push('-b');
|
||||
}
|
||||
args.push(branchName);
|
||||
return runGitCommand(args,cwd);
|
||||
},
|
||||
deleteBranch: function(cwd, branchName, isRemote, force) {
|
||||
if (isRemote) {
|
||||
throw new Error("Deleting remote branches not supported");
|
||||
}
|
||||
var args = ['branch'];
|
||||
if (force) {
|
||||
args.push('-D');
|
||||
} else {
|
||||
args.push('-d');
|
||||
}
|
||||
args.push(branchName);
|
||||
return runGitCommand(args, cwd);
|
||||
},
|
||||
getBranchStatus: getBranchStatus,
|
||||
addRemote: addRemote,
|
||||
removeRemote: removeRemote
|
||||
}
|
1
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ask-pass.sh
generated
vendored
Executable file
1
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ask-pass.sh
generated
vendored
Executable file
@@ -0,0 +1 @@
|
||||
"$NODE_RED_GIT_NODE_PATH" "$NODE_RED_GIT_ASKPASS_PATH" "$NODE_RED_GIT_SOCK_PATH" $@
|
1
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ssh.sh
generated
vendored
Executable file
1
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ssh.sh
generated
vendored
Executable file
@@ -0,0 +1 @@
|
||||
ssh -i "$NODE_RED_KEY_FILE" -F /dev/null $@
|
633
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js
generated
vendored
Normal file
633
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js
generated
vendored
Normal file
@@ -0,0 +1,633 @@
|
||||
/**
|
||||
* 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 fs = require('fs-extra');
|
||||
var when = require('when');
|
||||
var fspath = require("path");
|
||||
var nodeFn = require('when/node/function');
|
||||
var crypto = require('crypto');
|
||||
|
||||
var storageSettings = require("../settings");
|
||||
var util = require("../util");
|
||||
var gitTools = require("./git");
|
||||
var sshTools = require("./ssh");
|
||||
|
||||
var Projects = require("./Project");
|
||||
|
||||
var settings;
|
||||
var runtime;
|
||||
var log;
|
||||
|
||||
var projectsEnabled = false;
|
||||
var projectLogMessages = [];
|
||||
|
||||
var projectsDir;
|
||||
var activeProject
|
||||
|
||||
var globalGitUser = false;
|
||||
|
||||
function init(_settings, _runtime) {
|
||||
settings = _settings;
|
||||
runtime = _runtime;
|
||||
log = runtime.log;
|
||||
|
||||
try {
|
||||
if (settings.editorTheme.projects.enabled === true) {
|
||||
projectsEnabled = true;
|
||||
} else if (settings.editorTheme.projects.enabled === false) {
|
||||
projectLogMessages.push(log._("storage.localfilesystem.projects.disabled"))
|
||||
}
|
||||
} catch(err) {
|
||||
projectLogMessages.push(log._("storage.localfilesystem.projects.disabledNoFlag"))
|
||||
projectsEnabled = false;
|
||||
}
|
||||
|
||||
if (settings.flowFile) {
|
||||
flowsFile = settings.flowFile;
|
||||
// handle Unix and Windows "C:\"
|
||||
if ((flowsFile[0] == "/") || (flowsFile[1] == ":")) {
|
||||
// Absolute path
|
||||
flowsFullPath = flowsFile;
|
||||
} else if (flowsFile.substring(0,2) === "./") {
|
||||
// Relative to cwd
|
||||
flowsFullPath = fspath.join(process.cwd(),flowsFile);
|
||||
} else {
|
||||
try {
|
||||
fs.statSync(fspath.join(process.cwd(),flowsFile));
|
||||
// Found in cwd
|
||||
flowsFullPath = fspath.join(process.cwd(),flowsFile);
|
||||
} catch(err) {
|
||||
// Use userDir
|
||||
flowsFullPath = fspath.join(settings.userDir,flowsFile);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
flowsFile = 'flows_'+require('os').hostname()+'.json';
|
||||
flowsFullPath = fspath.join(settings.userDir,flowsFile);
|
||||
}
|
||||
var ffExt = fspath.extname(flowsFullPath);
|
||||
var ffBase = fspath.basename(flowsFullPath,ffExt);
|
||||
|
||||
flowsFileBackup = getBackupFilename(flowsFullPath);
|
||||
credentialsFile = fspath.join(settings.userDir,ffBase+"_cred"+ffExt);
|
||||
credentialsFileBackup = getBackupFilename(credentialsFile)
|
||||
|
||||
var setupProjectsPromise;
|
||||
|
||||
if (projectsEnabled) {
|
||||
return sshTools.init(settings,runtime).then(function() {
|
||||
gitTools.init(_settings, _runtime).then(function(gitConfig) {
|
||||
if (!gitConfig || /^1\./.test(gitConfig.version)) {
|
||||
if (!gitConfig) {
|
||||
projectLogMessages.push(log._("storage.localfilesystem.projects.git-not-found"))
|
||||
} else {
|
||||
projectLogMessages.push(log._("storage.localfilesystem.projects.git-version-old",{version:gitConfig.version}))
|
||||
}
|
||||
projectsEnabled = false;
|
||||
try {
|
||||
// As projects have to be turned on, we know this property
|
||||
// must exist at this point, so turn it off.
|
||||
// TODO: when on-by-default, this will need to do more
|
||||
// work to disable.
|
||||
settings.editorTheme.projects.enabled = false;
|
||||
} catch(err) {
|
||||
}
|
||||
} else {
|
||||
globalGitUser = gitConfig.user;
|
||||
Projects.init(settings,runtime);
|
||||
sshTools.init(settings,runtime);
|
||||
projectsDir = fspath.join(settings.userDir,"projects");
|
||||
if (!settings.readOnly) {
|
||||
return fs.ensureDir(projectsDir)
|
||||
//TODO: this is accessing settings from storage directly as settings
|
||||
// has not yet been initialised. That isn't ideal - can this be deferred?
|
||||
.then(storageSettings.getSettings)
|
||||
.then(function(globalSettings) {
|
||||
var saveSettings = false;
|
||||
if (!globalSettings.projects) {
|
||||
globalSettings.projects = {
|
||||
projects: {}
|
||||
}
|
||||
saveSettings = true;
|
||||
} else {
|
||||
activeProject = globalSettings.projects.activeProject;
|
||||
}
|
||||
if (settings.flowFile) {
|
||||
// if flowFile is a known project name - use it
|
||||
if (globalSettings.projects.projects.hasOwnProperty(settings.flowFile)) {
|
||||
activeProject = settings.flowFile;
|
||||
globalSettings.projects.activeProject = settings.flowFile;
|
||||
saveSettings = true;
|
||||
} else {
|
||||
// if it resolves to a dir - use it... but:
|
||||
// - where to get credsecret from?
|
||||
// - what if the name clashes with a known project?
|
||||
|
||||
// var stat = fs.statSync(settings.flowFile);
|
||||
// if (stat && stat.isDirectory()) {
|
||||
// activeProject = settings.flowFile;
|
||||
// }
|
||||
}
|
||||
}
|
||||
if (!activeProject) {
|
||||
projectLogMessages.push(log._("storage.localfilesystem.no-active-project"))
|
||||
}
|
||||
if (saveSettings) {
|
||||
return storageSettings.saveSettings(globalSettings);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
function listProjects() {
|
||||
return fs.readdir(projectsDir).then(function(fns) {
|
||||
var dirs = [];
|
||||
fns.sort(function(A,B) {
|
||||
return A.toLowerCase().localeCompare(B.toLowerCase());
|
||||
}).filter(function(fn) {
|
||||
var fullPath = fspath.join(projectsDir,fn);
|
||||
if (fn[0] != ".") {
|
||||
var stats = fs.lstatSync(fullPath);
|
||||
if (stats.isDirectory()) {
|
||||
dirs.push(fn);
|
||||
}
|
||||
}
|
||||
});
|
||||
return dirs;
|
||||
});
|
||||
}
|
||||
|
||||
function getUserGitSettings(user) {
|
||||
var userSettings = settings.getUserSettings(user)||{};
|
||||
return userSettings.git;
|
||||
}
|
||||
|
||||
function getBackupFilename(filename) {
|
||||
var ffName = fspath.basename(filename);
|
||||
var ffDir = fspath.dirname(filename);
|
||||
return fspath.join(ffDir,"."+ffName+".backup");
|
||||
}
|
||||
|
||||
function loadProject(name) {
|
||||
var projectPath = name;
|
||||
if (projectPath.indexOf(fspath.sep) === -1) {
|
||||
projectPath = fspath.join(projectsDir,name);
|
||||
}
|
||||
return Projects.load(projectPath).then(function(project) {
|
||||
activeProject = project;
|
||||
flowsFullPath = project.getFlowFile();
|
||||
flowsFileBackup = project.getFlowFileBackup();
|
||||
credentialsFile = project.getCredentialsFile();
|
||||
credentialsFileBackup = project.getCredentialsFileBackup();
|
||||
return project;
|
||||
})
|
||||
}
|
||||
|
||||
function getProject(user, name) {
|
||||
checkActiveProject(name);
|
||||
//return when.resolve(activeProject.info);
|
||||
return Promise.resolve(activeProject.export());
|
||||
}
|
||||
|
||||
function deleteProject(user, name) {
|
||||
if (activeProject && activeProject.name === name) {
|
||||
var e = new Error("NLS: Can't delete the active project");
|
||||
e.code = "cannot_delete_active_project";
|
||||
throw e;
|
||||
}
|
||||
var projectPath = fspath.join(projectsDir,name);
|
||||
return Projects.delete(user, projectPath);
|
||||
}
|
||||
|
||||
function checkActiveProject(project) {
|
||||
if (!activeProject || activeProject.name !== project) {
|
||||
//TODO: throw better err
|
||||
throw new Error("Cannot operate on inactive project wanted:"+project+" current:"+(activeProject&&activeProject.name));
|
||||
}
|
||||
}
|
||||
function getFiles(user, project) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.getFiles();
|
||||
}
|
||||
function stageFile(user, project,file) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.stageFile(file);
|
||||
}
|
||||
function unstageFile(user, project,file) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.unstageFile(file);
|
||||
}
|
||||
function commit(user, project,options) {
|
||||
checkActiveProject(project);
|
||||
var isMerging = activeProject.isMerging();
|
||||
return activeProject.commit(user, options).then(function() {
|
||||
// The project was merging, now it isn't. Lets reload.
|
||||
if (isMerging && !activeProject.isMerging()) {
|
||||
return reloadActiveProject("merge-complete");
|
||||
}
|
||||
})
|
||||
}
|
||||
function getFileDiff(user, project,file,type) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.getFileDiff(file,type);
|
||||
}
|
||||
function getCommits(user, project,options) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.getCommits(options);
|
||||
}
|
||||
function getCommit(user, project,sha) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.getCommit(sha);
|
||||
}
|
||||
|
||||
function getFile(user, project,filePath,sha) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.getFile(filePath,sha);
|
||||
}
|
||||
function revertFile(user, project,filePath) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.revertFile(filePath).then(function() {
|
||||
return reloadActiveProject("revert");
|
||||
})
|
||||
}
|
||||
function push(user, project,remoteBranchName,setRemote) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.push(user,remoteBranchName,setRemote);
|
||||
}
|
||||
function pull(user, project,remoteBranchName,setRemote,allowUnrelatedHistories) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.pull(user,remoteBranchName,setRemote,allowUnrelatedHistories).then(function() {
|
||||
return reloadActiveProject("pull");
|
||||
});
|
||||
}
|
||||
function getStatus(user, project, includeRemote) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.status(user, includeRemote);
|
||||
}
|
||||
function resolveMerge(user, project,file,resolution) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.resolveMerge(file,resolution);
|
||||
}
|
||||
function abortMerge(user, project) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.abortMerge().then(function() {
|
||||
return reloadActiveProject("merge-abort")
|
||||
});
|
||||
}
|
||||
function getBranches(user, project,isRemote) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.getBranches(user, isRemote);
|
||||
}
|
||||
|
||||
function deleteBranch(user, project, branch, isRemote, force) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.deleteBranch(user, branch, isRemote, force);
|
||||
}
|
||||
|
||||
function setBranch(user, project,branchName,isCreate) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.setBranch(branchName,isCreate).then(function() {
|
||||
return reloadActiveProject("change-branch");
|
||||
});
|
||||
}
|
||||
function getBranchStatus(user, project,branchName) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.getBranchStatus(branchName);
|
||||
}
|
||||
|
||||
|
||||
function getRemotes(user, project) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.getRemotes(user);
|
||||
}
|
||||
function addRemote(user, project, options) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.addRemote(user, options.name, options);
|
||||
}
|
||||
function removeRemote(user, project, remote) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.removeRemote(user, remote);
|
||||
}
|
||||
function updateRemote(user, project, remote, body) {
|
||||
checkActiveProject(project);
|
||||
return activeProject.updateRemote(user, remote, body);
|
||||
}
|
||||
|
||||
function getActiveProject(user) {
|
||||
return activeProject;
|
||||
}
|
||||
|
||||
function reloadActiveProject(action) {
|
||||
return runtime.nodes.stopFlows().then(function() {
|
||||
return runtime.nodes.loadFlows(true).then(function() {
|
||||
runtime.events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}});
|
||||
}).catch(function(err) {
|
||||
// We're committed to the project change now, so notify editors
|
||||
// that it has changed.
|
||||
runtime.events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}});
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
}
|
||||
function createProject(user, metadata) {
|
||||
// var userSettings = getUserGitSettings(user);
|
||||
if (metadata.files && metadata.migrateFiles) {
|
||||
// We expect there to be no active project in this scenario
|
||||
if (activeProject) {
|
||||
throw new Error("Cannot migrate as there is an active project");
|
||||
}
|
||||
var currentEncryptionKey = settings.get('credentialSecret');
|
||||
if (currentEncryptionKey === undefined) {
|
||||
currentEncryptionKey = settings.get('_credentialSecret');
|
||||
}
|
||||
if (!metadata.hasOwnProperty('credentialSecret')) {
|
||||
metadata.credentialSecret = currentEncryptionKey;
|
||||
}
|
||||
if (!metadata.files.flow) {
|
||||
metadata.files.flow = fspath.basename(flowsFullPath);
|
||||
}
|
||||
if (!metadata.files.credentials) {
|
||||
metadata.files.credentials = fspath.basename(credentialsFile);
|
||||
}
|
||||
|
||||
metadata.files.oldFlow = flowsFullPath;
|
||||
metadata.files.oldCredentials = credentialsFile;
|
||||
metadata.files.credentialSecret = currentEncryptionKey;
|
||||
}
|
||||
metadata.path = fspath.join(projectsDir,metadata.name);
|
||||
return Projects.create(user, metadata).then(function(p) {
|
||||
return setActiveProject(user, p.name);
|
||||
}).then(function() {
|
||||
return getProject(user, metadata.name);
|
||||
})
|
||||
}
|
||||
function setActiveProject(user, projectName) {
|
||||
return loadProject(projectName).then(function(project) {
|
||||
var globalProjectSettings = settings.get("projects");
|
||||
globalProjectSettings.activeProject = project.name;
|
||||
return settings.set("projects",globalProjectSettings).then(function() {
|
||||
log.info(log._("storage.localfilesystem.projects.changing-project",{project:(activeProject&&activeProject.name)||"none"}));
|
||||
log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath}));
|
||||
// console.log("Updated file targets to");
|
||||
// console.log(flowsFullPath)
|
||||
// console.log(credentialsFile)
|
||||
return reloadActiveProject("loaded");
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function initialiseProject(user, project, data) {
|
||||
if (!activeProject || activeProject.name !== project) {
|
||||
// TODO standardise
|
||||
throw new Error("Cannot initialise inactive project");
|
||||
}
|
||||
return activeProject.initialise(user,data).then(function(result) {
|
||||
flowsFullPath = activeProject.getFlowFile();
|
||||
flowsFileBackup = activeProject.getFlowFileBackup();
|
||||
credentialsFile = activeProject.getCredentialsFile();
|
||||
credentialsFileBackup = activeProject.getCredentialsFileBackup();
|
||||
runtime.nodes.setCredentialSecret(activeProject.credentialSecret);
|
||||
return reloadActiveProject("updated");
|
||||
});
|
||||
}
|
||||
function updateProject(user, project, data) {
|
||||
if (!activeProject || activeProject.name !== project) {
|
||||
// TODO standardise
|
||||
throw new Error("Cannot update inactive project");
|
||||
}
|
||||
// In case this triggers a credential secret change
|
||||
var isReset = data.resetCredentialSecret;
|
||||
var wasInvalid = activeProject.credentialSecretInvalid;
|
||||
|
||||
return activeProject.update(user,data).then(function(result) {
|
||||
|
||||
if (result.flowFilesChanged) {
|
||||
flowsFullPath = activeProject.getFlowFile();
|
||||
flowsFileBackup = activeProject.getFlowFileBackup();
|
||||
credentialsFile = activeProject.getCredentialsFile();
|
||||
credentialsFileBackup = activeProject.getCredentialsFileBackup();
|
||||
return reloadActiveProject("updated");
|
||||
} else if (result.credentialSecretChanged) {
|
||||
if (isReset || !wasInvalid) {
|
||||
if (isReset) {
|
||||
runtime.nodes.clearCredentials();
|
||||
}
|
||||
runtime.nodes.setCredentialSecret(activeProject.credentialSecret);
|
||||
return runtime.nodes.exportCredentials()
|
||||
.then(runtime.storage.saveCredentials)
|
||||
.then(function() {
|
||||
if (wasInvalid) {
|
||||
return reloadActiveProject("updated");
|
||||
}
|
||||
});
|
||||
} else if (wasInvalid) {
|
||||
return reloadActiveProject("updated");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function setCredentialSecret(data) { //existingSecret,secret) {
|
||||
var isReset = data.resetCredentialSecret;
|
||||
var wasInvalid = activeProject.credentialSecretInvalid;
|
||||
return activeProject.update(data).then(function() {
|
||||
if (isReset || !wasInvalid) {
|
||||
if (isReset) {
|
||||
runtime.nodes.clearCredentials();
|
||||
}
|
||||
runtime.nodes.setCredentialSecret(activeProject.credentialSecret);
|
||||
return runtime.nodes.exportCredentials()
|
||||
.then(runtime.storage.saveCredentials)
|
||||
.then(function() {
|
||||
if (wasInvalid) {
|
||||
return reloadActiveProject("updated");
|
||||
}
|
||||
});
|
||||
} else if (wasInvalid) {
|
||||
return reloadActiveProject("updated");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
var initialFlowLoadComplete = false;
|
||||
|
||||
var flowsFile;
|
||||
var flowsFullPath;
|
||||
var flowsFileExists = false;
|
||||
var flowsFileBackup;
|
||||
var credentialsFile;
|
||||
var credentialsFileBackup;
|
||||
|
||||
function getFlows() {
|
||||
if (!initialFlowLoadComplete) {
|
||||
initialFlowLoadComplete = true;
|
||||
log.info(log._("storage.localfilesystem.user-dir",{path:settings.userDir}));
|
||||
if (activeProject) {
|
||||
// At this point activeProject will be a string, so go load it and
|
||||
// swap in an instance of Project
|
||||
return loadProject(activeProject).then(function() {
|
||||
log.info(log._("storage.localfilesystem.projects.active-project",{project:activeProject.name||"none"}));
|
||||
log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath}));
|
||||
return getFlows();
|
||||
});
|
||||
} else {
|
||||
if (projectsEnabled) {
|
||||
log.warn(log._("storage.localfilesystem.projects.no-active-project"))
|
||||
} else {
|
||||
projectLogMessages.forEach(log.warn);
|
||||
}
|
||||
log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath}));
|
||||
}
|
||||
}
|
||||
if (activeProject) {
|
||||
var error;
|
||||
if (activeProject.isEmpty()) {
|
||||
log.warn("Project repository is empty");
|
||||
error = new Error("Project repository is empty");
|
||||
error.code = "project_empty";
|
||||
return when.reject(error);
|
||||
}
|
||||
if (activeProject.missingFiles && activeProject.missingFiles.indexOf('package.json') !== -1) {
|
||||
log.warn("Project missing package.json");
|
||||
error = new Error("Project missing package.json");
|
||||
error.code = "missing_package_file";
|
||||
return when.reject(error);
|
||||
}
|
||||
if (!activeProject.getFlowFile()) {
|
||||
log.warn("Project has no flow file");
|
||||
error = new Error("Project has no flow file");
|
||||
error.code = "missing_flow_file";
|
||||
return when.reject(error);
|
||||
}
|
||||
if (activeProject.isMerging()) {
|
||||
log.warn("Project has unmerged changes");
|
||||
error = new Error("Project has unmerged changes. Cannot load flows");
|
||||
error.code = "git_merge_conflict";
|
||||
return when.reject(error);
|
||||
}
|
||||
|
||||
}
|
||||
return util.readFile(flowsFullPath,flowsFileBackup,null,'flow').then(function(result) {
|
||||
if (result === null) {
|
||||
flowsFileExists = false;
|
||||
return [];
|
||||
}
|
||||
flowsFileExists = true;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
function saveFlows(flows) {
|
||||
if (settings.readOnly) {
|
||||
return when.resolve();
|
||||
}
|
||||
if (activeProject && activeProject.isMerging()) {
|
||||
var error = new Error("Project has unmerged changes. Cannot deploy new flows");
|
||||
error.code = "git_merge_conflict";
|
||||
return when.reject(error);
|
||||
}
|
||||
|
||||
flowsFileExists = true;
|
||||
|
||||
var flowData;
|
||||
|
||||
if (settings.flowFilePretty) {
|
||||
flowData = JSON.stringify(flows,null,4);
|
||||
} else {
|
||||
flowData = JSON.stringify(flows);
|
||||
}
|
||||
return util.writeFile(flowsFullPath, flowData, flowsFileBackup);
|
||||
}
|
||||
|
||||
function getCredentials() {
|
||||
return util.readFile(credentialsFile,credentialsFileBackup,{},'credentials');
|
||||
}
|
||||
|
||||
function saveCredentials(credentials) {
|
||||
if (settings.readOnly) {
|
||||
return when.resolve();
|
||||
}
|
||||
|
||||
var credentialData;
|
||||
if (settings.flowFilePretty) {
|
||||
credentialData = JSON.stringify(credentials,null,4);
|
||||
} else {
|
||||
credentialData = JSON.stringify(credentials);
|
||||
}
|
||||
return util.writeFile(credentialsFile, credentialData, credentialsFileBackup);
|
||||
}
|
||||
|
||||
function getFlowFilename() {
|
||||
if (flowsFullPath) {
|
||||
return fspath.basename(flowsFullPath);
|
||||
}
|
||||
}
|
||||
function getCredentialsFilename() {
|
||||
if (flowsFullPath) {
|
||||
return fspath.basename(credentialsFile);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
listProjects: listProjects,
|
||||
getActiveProject: getActiveProject,
|
||||
setActiveProject: setActiveProject,
|
||||
getProject: getProject,
|
||||
deleteProject: deleteProject,
|
||||
createProject: createProject,
|
||||
initialiseProject: initialiseProject,
|
||||
updateProject: updateProject,
|
||||
getFiles: getFiles,
|
||||
getFile: getFile,
|
||||
revertFile: revertFile,
|
||||
stageFile: stageFile,
|
||||
unstageFile: unstageFile,
|
||||
commit: commit,
|
||||
getFileDiff: getFileDiff,
|
||||
getCommits: getCommits,
|
||||
getCommit: getCommit,
|
||||
push: push,
|
||||
pull: pull,
|
||||
getStatus:getStatus,
|
||||
resolveMerge: resolveMerge,
|
||||
abortMerge: abortMerge,
|
||||
getBranches: getBranches,
|
||||
deleteBranch: deleteBranch,
|
||||
setBranch: setBranch,
|
||||
getBranchStatus:getBranchStatus,
|
||||
getRemotes: getRemotes,
|
||||
addRemote: addRemote,
|
||||
removeRemote: removeRemote,
|
||||
updateRemote: updateRemote,
|
||||
getFlowFilename: getFlowFilename,
|
||||
flowFileExists: function() { return flowsFileExists },
|
||||
getCredentialsFilename: getCredentialsFilename,
|
||||
getGlobalGitUser: function() { return globalGitUser },
|
||||
getFlows: getFlows,
|
||||
saveFlows: saveFlows,
|
||||
getCredentials: getCredentials,
|
||||
saveCredentials: saveCredentials,
|
||||
|
||||
ssh: sshTools
|
||||
|
||||
};
|
216
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/ssh/index.js
generated
vendored
Normal file
216
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/ssh/index.js
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
/**
|
||||
* 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 fs = require('fs-extra');
|
||||
var when = require('when');
|
||||
var fspath = require("path");
|
||||
var keygen = require("./keygen");
|
||||
|
||||
var settings;
|
||||
var runtime;
|
||||
var log;
|
||||
var sshkeyDir;
|
||||
var userSSHKeyDir;
|
||||
|
||||
function init(_settings, _runtime) {
|
||||
settings = _settings;
|
||||
runtime = _runtime;
|
||||
log = runtime.log;
|
||||
sshkeyDir = fspath.resolve(fspath.join(settings.userDir, "projects", ".sshkeys"));
|
||||
userSSHKeyDir = fspath.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH, ".ssh");
|
||||
// console.log('sshkeys.init()');
|
||||
return fs.ensureDir(sshkeyDir);
|
||||
}
|
||||
|
||||
function listSSHKeys(username) {
|
||||
return listSSHKeysInDir(sshkeyDir,username + '_').then(function(customKeys) {
|
||||
return listSSHKeysInDir(userSSHKeyDir).then(function(existingKeys) {
|
||||
existingKeys.forEach(function(k){
|
||||
k.system = true;
|
||||
customKeys.push(k);
|
||||
})
|
||||
return customKeys;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function listSSHKeysInDir(dir,startStr) {
|
||||
startStr = startStr || "";
|
||||
return fs.readdir(dir).then(function(fns) {
|
||||
var ret = fns.sort()
|
||||
.filter(function(fn) {
|
||||
var fullPath = fspath.join(dir,fn);
|
||||
if (fn.length > 2 || fn[0] != ".") {
|
||||
var stats = fs.lstatSync(fullPath);
|
||||
if (stats.isFile()) {
|
||||
return fn.startsWith(startStr);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.map(function(filename) {
|
||||
return filename.substr(startStr.length);
|
||||
})
|
||||
.reduce(function(prev, current) {
|
||||
var parsePath = fspath.parse(current);
|
||||
if ( parsePath ) {
|
||||
if ( parsePath.ext !== '.pub' ) {
|
||||
// Private Keys
|
||||
prev.keyFiles.push(parsePath.base);
|
||||
}
|
||||
else if ( parsePath.ext === '.pub' && (prev.keyFiles.some(function(elem){ return elem === parsePath.name; }))) {
|
||||
prev.privateKeyFiles.push(parsePath.name);
|
||||
}
|
||||
}
|
||||
return prev;
|
||||
}, { keyFiles: [], privateKeyFiles: [] });
|
||||
return ret.privateKeyFiles.map(function(filename) {
|
||||
return {
|
||||
name: filename
|
||||
};
|
||||
});
|
||||
}).then(function(result) {
|
||||
return result;
|
||||
}).catch(function() {
|
||||
return []
|
||||
});
|
||||
}
|
||||
|
||||
function getSSHKey(username, name) {
|
||||
return checkSSHKeyFileAndGetPublicKeyFileName(username, name)
|
||||
.then(function(publicSSHKeyPath) {
|
||||
return fs.readFile(publicSSHKeyPath, 'utf-8');
|
||||
}).catch(function() {
|
||||
var privateKeyPath = fspath.join(userSSHKeyDir,name);
|
||||
var publicKeyPath = privateKeyPath+".pub";
|
||||
return checkFilePairExist(privateKeyPath,publicKeyPath).then(function() {
|
||||
return fs.readFile(publicKeyPath, 'utf-8');
|
||||
}).catch(function() {
|
||||
return null
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function generateSSHKey(username, options) {
|
||||
options = options || {};
|
||||
var name = options.name || "";
|
||||
if (!/^[a-zA-Z0-9\-_]+$/.test(options.name)) {
|
||||
var err = new Error("Invalid SSH Key name");
|
||||
e.code = "invalid_key_name";
|
||||
return Promise.reject(err);
|
||||
}
|
||||
return checkExistSSHKeyFiles(username, name)
|
||||
.then(function(result) {
|
||||
if ( result ) {
|
||||
var e = new Error("SSH Key name exists");
|
||||
e.code = "key_exists";
|
||||
throw e;
|
||||
} else {
|
||||
var comment = options.comment || "";
|
||||
var password = options.password || "";
|
||||
var size = options.size || 2048;
|
||||
var sshKeyFileBasename = username + '_' + name;
|
||||
var privateKeyFilePath = fspath.normalize(fspath.join(sshkeyDir, sshKeyFileBasename));
|
||||
return generateSSHKeyPair(name, privateKeyFilePath, comment, password, size)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function deleteSSHKey(username, name) {
|
||||
return checkSSHKeyFileAndGetPublicKeyFileName(username, name)
|
||||
.then(function() {
|
||||
return deleteSSHKeyFiles(username, name);
|
||||
});
|
||||
}
|
||||
|
||||
function checkExistSSHKeyFiles(username, name) {
|
||||
var sshKeyFileBasename = username + '_' + name;
|
||||
var privateKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename);
|
||||
var publicKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename + '.pub');
|
||||
return checkFilePairExist(privateKeyFilePath,publicKeyFilePath)
|
||||
.then(function() {
|
||||
return true;
|
||||
})
|
||||
.catch(function() {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function checkSSHKeyFileAndGetPublicKeyFileName(username, name) {
|
||||
var sshKeyFileBasename = username + '_' + name;
|
||||
var privateKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename);
|
||||
var publicKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename + '.pub');
|
||||
return checkFilePairExist(privateKeyFilePath,publicKeyFilePath).then(function() {
|
||||
return publicKeyFilePath;
|
||||
});
|
||||
}
|
||||
|
||||
function checkFilePairExist(privateKeyFilePath,publicKeyFilePath) {
|
||||
return Promise.all([
|
||||
fs.access(privateKeyFilePath, (fs.constants || fs).R_OK),
|
||||
fs.access(publicKeyFilePath , (fs.constants || fs).R_OK)
|
||||
])
|
||||
}
|
||||
|
||||
function deleteSSHKeyFiles(username, name) {
|
||||
var sshKeyFileBasename = username + '_' + name;
|
||||
var privateKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename);
|
||||
var publicKeyFilePath = fspath.join(sshkeyDir, sshKeyFileBasename + '.pub');
|
||||
return Promise.all([
|
||||
fs.remove(privateKeyFilePath),
|
||||
fs.remove(publicKeyFilePath)
|
||||
])
|
||||
}
|
||||
|
||||
function generateSSHKeyPair(name, privateKeyPath, comment, password, size) {
|
||||
log.trace("ssh-keygen["+[name,privateKeyPath,comment,size,"hasPassword?"+!!password].join(",")+"]");
|
||||
return keygen.generateKey({location: privateKeyPath, comment: comment, password: password, size: size})
|
||||
.then(function(stdout) {
|
||||
return name;
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.log('[SSHKey generation] error:', err);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
function getPrivateKeyPath(username, name) {
|
||||
var sshKeyFileBasename = username + '_' + name;
|
||||
var privateKeyFilePath = fspath.normalize(fspath.join(sshkeyDir, sshKeyFileBasename));
|
||||
try {
|
||||
fs.accessSync(privateKeyFilePath, (fs.constants || fs).R_OK);
|
||||
} catch(err) {
|
||||
privateKeyFilePath = fspath.join(userSSHKeyDir,name);
|
||||
try {
|
||||
fs.accessSync(privateKeyFilePath, (fs.constants || fs).R_OK);
|
||||
} catch(err2) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (fspath.sep === '\\') {
|
||||
privateKeyFilePath = privateKeyFilePath.replace(/\\/g,'\\\\');
|
||||
}
|
||||
return privateKeyFilePath;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
listSSHKeys: listSSHKeys,
|
||||
getSSHKey: getSSHKey,
|
||||
getPrivateKeyPath: getPrivateKeyPath,
|
||||
generateSSHKey: generateSSHKey,
|
||||
deleteSSHKey: deleteSSHKey
|
||||
};
|
95
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/ssh/keygen.js
generated
vendored
Normal file
95
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/ssh/keygen.js
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 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 child_process = require('child_process');
|
||||
|
||||
var sshkeygenCommand = "ssh-keygen";
|
||||
|
||||
var log;
|
||||
|
||||
function runSshKeygenCommand(args,cwd,env) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var child = child_process.spawn(sshkeygenCommand, 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('close', function(code, signal) {
|
||||
// console.log(code);
|
||||
// console.log(stdout);
|
||||
// console.log(stderr);
|
||||
if (code !== 0) {
|
||||
var err = new Error(stderr);
|
||||
err.stdout = stdout;
|
||||
err.stderr = stderr;
|
||||
if (/short/.test(stderr)) {
|
||||
err.code = "key_passphrase_too_short";
|
||||
} else if(/Key must at least be 1024 bits/.test(stderr)) {
|
||||
err.code = "key_length_too_short";
|
||||
}
|
||||
reject(err);
|
||||
}
|
||||
else {
|
||||
resolve(stdout);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function init(_settings, _runtime) {
|
||||
log = _runtime.log;
|
||||
}
|
||||
|
||||
function generateKey(options) {
|
||||
var args = ['-q', '-t', 'rsa'];
|
||||
var err;
|
||||
if (options.size) {
|
||||
if (options.size < 1024) {
|
||||
err = new Error("key_length_too_short");
|
||||
err.code = "key_length_too_short";
|
||||
throw err;
|
||||
}
|
||||
args.push('-b', options.size);
|
||||
}
|
||||
if (options.location) {
|
||||
args.push('-f', options.location);
|
||||
}
|
||||
if (options.comment) {
|
||||
args.push('-C', options.comment);
|
||||
}
|
||||
if (options.password) {
|
||||
if (options.password.length < 5) {
|
||||
err = new Error("key_passphrase_too_short");
|
||||
err.code = "key_passphrase_too_short";
|
||||
throw err;
|
||||
}
|
||||
args.push('-N', options.password||'');
|
||||
} else {
|
||||
args.push('-N', '');
|
||||
}
|
||||
|
||||
return runSshKeygenCommand(args,__dirname);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: init,
|
||||
generateKey: generateKey,
|
||||
};
|
53
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/sessions.js
generated
vendored
Normal file
53
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/sessions.js
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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 when = require('when');
|
||||
var fs = require('fs-extra');
|
||||
var fspath = require("path");
|
||||
|
||||
var log = require("@node-red/util").log; // TODO: separate module
|
||||
|
||||
var util = require("./util");
|
||||
|
||||
var sessionsFile;
|
||||
var settings;
|
||||
|
||||
module.exports = {
|
||||
init: function(_settings) {
|
||||
settings = _settings;
|
||||
sessionsFile = fspath.join(settings.userDir,".sessions.json");
|
||||
},
|
||||
getSessions: function() {
|
||||
return when.promise(function(resolve,reject) {
|
||||
fs.readFile(sessionsFile,'utf8',function(err,data){
|
||||
if (!err) {
|
||||
try {
|
||||
return resolve(util.parseJSON(data));
|
||||
} catch(err2) {
|
||||
log.trace("Corrupted sessions file - resetting");
|
||||
}
|
||||
}
|
||||
resolve({});
|
||||
})
|
||||
});
|
||||
},
|
||||
saveSessions: function(sessions) {
|
||||
if (settings.readOnly) {
|
||||
return when.resolve();
|
||||
}
|
||||
return util.writeFile(sessionsFile,JSON.stringify(sessions));
|
||||
}
|
||||
}
|
54
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/settings.js
generated
vendored
Normal file
54
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/settings.js
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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 when = require('when');
|
||||
var fs = require('fs-extra');
|
||||
var fspath = require("path");
|
||||
|
||||
var log = require("@node-red/util").log; // TODO: separate module
|
||||
var util = require("./util");
|
||||
|
||||
var globalSettingsFile;
|
||||
var globalSettingsBackup;
|
||||
var settings;
|
||||
|
||||
module.exports = {
|
||||
init: function(_settings) {
|
||||
settings = _settings;
|
||||
globalSettingsFile = fspath.join(settings.userDir,".config.json");
|
||||
globalSettingsBackup = fspath.join(settings.userDir,".config.json.backup");
|
||||
},
|
||||
getSettings: function() {
|
||||
return when.promise(function(resolve,reject) {
|
||||
fs.readFile(globalSettingsFile,'utf8',function(err,data) {
|
||||
if (!err) {
|
||||
try {
|
||||
return resolve(util.parseJSON(data));
|
||||
} catch(err2) {
|
||||
log.trace("Corrupted config detected - resetting");
|
||||
}
|
||||
}
|
||||
return resolve({});
|
||||
})
|
||||
})
|
||||
},
|
||||
saveSettings: function(newSettings) {
|
||||
if (settings.readOnly) {
|
||||
return when.resolve();
|
||||
}
|
||||
return util.writeFile(globalSettingsFile,JSON.stringify(newSettings,null,1),globalSettingsBackup);
|
||||
}
|
||||
}
|
107
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/util.js
generated
vendored
Normal file
107
packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/util.js
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* 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 fs = require('fs-extra');
|
||||
var when = require('when');
|
||||
var nodeFn = require('when/node/function');
|
||||
|
||||
var log = require("@node-red/util").log; // TODO: separate module
|
||||
|
||||
function parseJSON(data) {
|
||||
if (data.charCodeAt(0) === 0xFEFF) {
|
||||
data = data.slice(1)
|
||||
}
|
||||
return JSON.parse(data);
|
||||
}
|
||||
function readFile(path,backupPath,emptyResponse,type) {
|
||||
return when.promise(function(resolve) {
|
||||
fs.readFile(path,'utf8',function(err,data) {
|
||||
if (!err) {
|
||||
if (data.length === 0) {
|
||||
log.warn(log._("storage.localfilesystem.empty",{type:type}));
|
||||
try {
|
||||
var backupStat = fs.statSync(backupPath);
|
||||
if (backupStat.size === 0) {
|
||||
// Empty flows, empty backup - return empty flow
|
||||
return resolve(emptyResponse);
|
||||
}
|
||||
// Empty flows, restore backup
|
||||
log.warn(log._("storage.localfilesystem.restore",{path:backupPath,type:type}));
|
||||
fs.copy(backupPath,path,function(backupCopyErr) {
|
||||
if (backupCopyErr) {
|
||||
// Restore backup failed
|
||||
log.warn(log._("storage.localfilesystem.restore-fail",{message:backupCopyErr.toString(),type:type}));
|
||||
resolve([]);
|
||||
} else {
|
||||
// Loop back in to load the restored backup
|
||||
resolve(readFile(path,backupPath,emptyResponse,type));
|
||||
}
|
||||
});
|
||||
return;
|
||||
} catch(backupStatErr) {
|
||||
// Empty flow file, no back-up file
|
||||
return resolve(emptyResponse);
|
||||
}
|
||||
}
|
||||
try {
|
||||
return resolve(parseJSON(data));
|
||||
} catch(parseErr) {
|
||||
log.warn(log._("storage.localfilesystem.invalid",{type:type}));
|
||||
return resolve(emptyResponse);
|
||||
}
|
||||
} else {
|
||||
if (type === 'flow') {
|
||||
log.info(log._("storage.localfilesystem.create",{type:type}));
|
||||
}
|
||||
resolve(emptyResponse);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Write content to a file using UTF8 encoding.
|
||||
* This forces a fsync before completing to ensure
|
||||
* the write hits disk.
|
||||
*/
|
||||
writeFile: function(path,content,backupPath) {
|
||||
if (backupPath) {
|
||||
if (fs.existsSync(path)) {
|
||||
fs.renameSync(path,backupPath);
|
||||
}
|
||||
}
|
||||
return when.promise(function(resolve,reject) {
|
||||
var stream = fs.createWriteStream(path);
|
||||
stream.on('open',function(fd) {
|
||||
stream.write(content,'utf8',function() {
|
||||
fs.fsync(fd,function(err) {
|
||||
if (err) {
|
||||
log.warn(log._("storage.localfilesystem.fsync-fail",{path: path, message: err.toString()}));
|
||||
}
|
||||
stream.end(resolve);
|
||||
});
|
||||
});
|
||||
});
|
||||
stream.on('error',function(err) {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
},
|
||||
readFile: readFile,
|
||||
|
||||
parseJSON: parseJSON
|
||||
}
|
Reference in New Issue
Block a user