mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
The exec and events components are common components that are used by both runtime and registry. It makes sense to move them into the util package. This also adds some docs to the registry module
1060 lines
38 KiB
JavaScript
1060 lines
38 KiB
JavaScript
/**
|
|
* 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 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 = require("@node-red/util").log;
|
|
const events = require("@node-red/util").events;
|
|
|
|
var projectsDir;
|
|
|
|
var authCache = require("./git/authCache");
|
|
|
|
// TODO: DRY - red/api/editor/sshkeys !
|
|
function getSSHKeyUsername(userObj) {
|
|
var username = '__default';
|
|
if ( userObj && userObj.username ) {
|
|
username = userObj.username;
|
|
}
|
|
return username;
|
|
}
|
|
|
|
function getUserGitSettings(user) {
|
|
var username;
|
|
if (!user) {
|
|
username = "_";
|
|
} else {
|
|
username = user.username;
|
|
}
|
|
var userSettings = settings.getUserSettings(username)||{};
|
|
return userSettings.git;
|
|
}
|
|
|
|
function getGitUser(user) {
|
|
var gitSettings = getUserGitSettings(user);
|
|
if (gitSettings) {
|
|
return gitSettings.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.paths.root = projectSettings.rootPath || "";
|
|
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) {
|
|
// We have a package.json in project.path+project.paths.root+"package.json"
|
|
project.paths['package.json'] = fspath.join(project.paths.root,"package.json");
|
|
promises.push(fs.readFile(fspath.join(project.path,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 = fspath.join(project.paths.root,project.package['node-red'].settings.flowFile);
|
|
project.paths.credentialsFile = fspath.join(project.paths.root,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 = {};
|
|
}
|
|
}).catch(err => {})); //
|
|
if (missingFiles.indexOf('README.md') === -1) {
|
|
project.paths['README.md'] = fspath.join(project.paths.root,"README.md");
|
|
promises.push(fs.readFile(fspath.join(project.path,project.paths['README.md']),"utf8").then(function(content) {
|
|
project.description = content;
|
|
}).catch(err => {}));
|
|
} else {
|
|
project.description = "";
|
|
}
|
|
} else {
|
|
project.package = {};
|
|
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().catch(err => {}));
|
|
|
|
return Promise.all(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)));
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return Promise.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 = async 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 reloadProject = 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";
|
|
throw e;
|
|
}
|
|
this.credentialSecret = secret;
|
|
|
|
globalProjectSettings.projects[this.name].credentialSecret = project.credentialSecret;
|
|
delete this.credentialSecretInvalid;
|
|
saveSettings = true;
|
|
credentialSecretChanged = true;
|
|
}
|
|
|
|
if (this.missingFiles.indexOf('package.json') !== -1) {
|
|
if (!data.files || !data.files.package) {
|
|
// Cannot update a project that doesn't have a known package.json
|
|
throw new Error("Cannot update project with missing package.json");
|
|
}
|
|
}
|
|
|
|
if (data.hasOwnProperty('files')) {
|
|
this.package['node-red'] = this.package['node-red'] || { settings: {}};
|
|
if (data.files.hasOwnProperty('package') && (data.files.package !== fspath.join(this.paths.root,"package.json") || !this.paths['package.json'])) {
|
|
// We have a package file. It could be one that doesn't exist yet,
|
|
// or it does exist and we need to load it.
|
|
if (!/package\.json$/.test(data.files.package)) {
|
|
return new Error("Invalid package file: "+data.files.package)
|
|
}
|
|
var root = data.files.package.substring(0,data.files.package.length-12);
|
|
this.paths.root = root;
|
|
this.paths['package.json'] = data.files.package;
|
|
globalProjectSettings.projects[this.name].rootPath = root;
|
|
saveSettings = true;
|
|
// 1. check if it exists
|
|
if (fs.existsSync(fspath.join(this.path,this.paths['package.json']))) {
|
|
// Load the existing one....
|
|
} else {
|
|
var newPackage = defaultFileSet["package.json"](this);
|
|
fs.writeFileSync(fspath.join(this.path,this.paths['package.json']),newPackage);
|
|
this.package = JSON.parse(newPackage);
|
|
}
|
|
reloadProject = true;
|
|
flowFilesChanged = true;
|
|
}
|
|
|
|
if (data.files.hasOwnProperty('flow') && this.package['node-red'].settings.flowFile !== data.files.flow.substring(this.paths.root.length)) {
|
|
this.paths.flowFile = data.files.flow;
|
|
this.package['node-red'].settings.flowFile = data.files.flow.substring(this.paths.root.length);
|
|
savePackage = true;
|
|
flowFilesChanged = true;
|
|
}
|
|
if (data.files.hasOwnProperty('credentials') && this.package['node-red'].settings.credentialsFile !== data.files.credentials.substring(this.paths.root.length)) {
|
|
this.paths.credentialsFile = data.files.credentials;
|
|
this.package['node-red'].settings.credentialsFile = data.files.credentials.substring(this.paths.root.length);
|
|
// Don't know if the credSecret is invalid or not so clear the flag
|
|
delete this.credentialSecretInvalid;
|
|
savePackage = true;
|
|
flowFilesChanged = 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('version')) {
|
|
savePackage = true;
|
|
this.package.version = data.version;
|
|
}
|
|
|
|
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.catch(err => {}));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (saveSettings) {
|
|
promises.push(settings.set("projects",globalProjectSettings).catch(err => {}));
|
|
}
|
|
|
|
var modifiedFiles = [];
|
|
|
|
if (saveREADME) {
|
|
promises.push(util.writeFile(fspath.join(this.path,this.paths['README.md']), this.description).catch(err => {}));
|
|
modifiedFiles.push('README.md');
|
|
}
|
|
if (savePackage) {
|
|
promises.push(fs.readFile(fspath.join(this.path,this.paths['package.json']),"utf8").then(content => {
|
|
var currentPackage = {};
|
|
try {
|
|
currentPackage = util.parseJSON(content);
|
|
} catch(err) {
|
|
}
|
|
this.package = Object.assign(currentPackage,this.package);
|
|
return util.writeFile(fspath.join(project.path,this.paths['package.json']), JSON.stringify(this.package,"",4));
|
|
}).catch(err => {}));
|
|
modifiedFiles.push('package.json');
|
|
}
|
|
return Promise.all(promises).then(function(res) {
|
|
var gitSettings = getUserGitSettings(user) || {};
|
|
var workflowMode = (gitSettings.workflow||{}).mode || "manual";
|
|
if (workflowMode === 'auto') {
|
|
return project.stageFile(modifiedFiles.map(f => project.paths[f])).then(() => {
|
|
return project.commit(user,{message:"Update "+modifiedFiles.join(", ")})
|
|
})
|
|
}
|
|
}).then(res => {
|
|
if (reloadProject) {
|
|
return this.load()
|
|
}
|
|
}).then(function() {
|
|
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 Promise.all(promises).then(function(results) {
|
|
var result = results[0];
|
|
if (results[1]) {
|
|
result.merging = true;
|
|
if (!self.merging) {
|
|
self.merging = true;
|
|
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) {
|
|
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) {
|
|
events.emit("runtime-event",{id:"runtime-state",retain:true});
|
|
} else {
|
|
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) {
|
|
if (!err.remote) {
|
|
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,
|
|
version: this.package.version,
|
|
description: this.description,
|
|
dependencies: this.package.dependencies||{},
|
|
empty: this.empty,
|
|
settings: {
|
|
credentialsEncrypted: (typeof this.credentialSecret === "string") && this.credentialSecret.length > 0,
|
|
credentialSecretInvalid: this.credentialSecretInvalid
|
|
},
|
|
files: {
|
|
package: this.paths['package.json'],
|
|
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)));
|
|
}
|
|
}
|
|
|
|
return Promise.all(promises).then(function() {
|
|
return gitTools.stageFile(projectPath,files);
|
|
}).then(function() {
|
|
return gitTools.commit(projectPath,"Create project",getGitUser(user));
|
|
})
|
|
});
|
|
}
|
|
function checkProjectFiles(project) {
|
|
var promises = [];
|
|
var missing = [];
|
|
for (var file in defaultFileSet) {
|
|
if (defaultFileSet.hasOwnProperty(file)) {
|
|
(function(f) {
|
|
promises.push(fs.stat(fspath.join(project.path,project.paths.root,f)).catch(err => {
|
|
missing.push(f);
|
|
}));
|
|
})(file);
|
|
}
|
|
}
|
|
return Promise.all(promises).then(() => 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')) {
|
|
if (metadata.credentialSecret === "") {
|
|
metadata.credentialSecret = false;
|
|
}
|
|
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;
|
|
projectsDir = fspath.join(settings.userDir,"projects");
|
|
authCache.init();
|
|
}
|
|
|
|
module.exports = {
|
|
init: init,
|
|
load: loadProject,
|
|
create: createProject,
|
|
delete: deleteProject
|
|
}
|