Change folder structure and starting to refactor files

This commit is contained in:
Daniel Treviño
2018-10-30 08:30:59 +01:00
parent c3b026b1d6
commit 0ce0f1fb52
16 changed files with 1756 additions and 1595 deletions
-748
View File
@@ -1,748 +0,0 @@
#!/usr/bin/env node
const program = require('commander');
const fs = require('fs-extra');
const path = require('path');
const chalk = require('chalk');
const inquirer = require('inquirer');
const configstore = require('configstore');
const request = require('request');
const commandExistsSync = require('command-exists').sync;
const ProgressBar = require('progress');
const ora = require('ora');
const {exec} = require('child_process');
const packagejson = require('./package.json');
const configs = new configstore(packagejson.name, {
captainMachines: [],
apps: []
});
const BRANCH_TO_PUSH = 'branchToPush';
const APP_NAME = 'appName';
const MACHINE_TO_DEPLOY = 'machineToDeploy';
const EMPTY_STRING = '';
console.log(' ');
console.log(' ');
program
.description('Deploy current directory to a Captain machine.')
.option('-t, --tarFile <value>', 'Specify file to be uploaded (rather than using git archive)')
.option('-d, --default', 'Run with default options')
.option('-s, --stateless', 'Run deploy stateless')
.option('-h, --host <value>', 'Only for stateless mode: Host of the captain machine')
.option('-a, --appName <value>', 'Only for stateless mode: Name of the app')
.option('-p, --pass <value>', 'Only for stateless mode: Password for Captain')
.option('-b, --branch <value>', 'Only for stateless mode: Branch name (default master)')
.parse(process.argv);
if (program.args.length) {
console.error(chalk.red('Unrecognized commands:'));
program.args.forEach(function (arg) {
console.log(chalk.red(arg));
});
console.error(chalk.red('Deploy does not require any options. '));
process.exit(1);
}
function printErrorAndExit(error) {
console.log(chalk.bold.red(error));
console.log(' ');
console.log(' ');
process.exit(0);
}
function getSuppliedTarFile() {
return program.tarFile;
}
if (!getSuppliedTarFile()) {
if (!fs.pathExistsSync('./.git')) {
printErrorAndExit('**** ERROR: You are not in a git root directory. This command will only deploys the current directory ****');
}
if (!fs.pathExistsSync('./captain-definition')) {
printErrorAndExit('**** ERROR: captain-definition file cannot be found. Please see docs! ****');
}
const contents = fs.readFileSync('./captain-definition', 'utf8');
let contentsJson = null;
try {
contentsJson = JSON.parse(contents);
} catch (e) {
console.log(e);
console.log('');
printErrorAndExit('**** ERROR: captain-definition file is not a valid JSON! ****');
}
if (!contentsJson.schemaVersion) {
printErrorAndExit('**** ERROR: captain-definition needs schemaVersion. Please see docs! ****');
}
if (!contentsJson.templateId && !contentsJson.dockerfileLines) {
printErrorAndExit('**** ERROR: captain-definition needs templateId or dockerfileLines. Please see docs! ****');
}
if (contentsJson.templateId && contentsJson.dockerfileLines) {
printErrorAndExit('**** ERROR: captain-definition needs templateId or dockerfileLines, NOT BOTH! Please see docs! ****');
}
}
let listOfMachines = [{
name: '-- CANCEL --',
value: EMPTY_STRING,
short: EMPTY_STRING
}];
let machines = configs.get('captainMachines');
for (let i = 0; i < machines.length; i++) {
let m = machines[i];
listOfMachines.push({
name: m.name + ' at ' + m.baseUrl,
value: m.name,
short: m.name + ' at ' + m.baseUrl
})
}
// Gets default value for propType that is stored in a directory.
// Replaces getAppForDirectory
function getPropForDirectory(propType) {
let apps = configs.get('apps');
for (let i = 0; i < apps.length; i++) {
let app = apps[i];
if (app.cwd === process.cwd()) {
return app[propType];
}
}
return undefined;
}
// Sets default value for propType that is stored in a directory to propValue.
// Replaces saveAppForDirectory
function savePropForDirectory(propType, propValue) {
let apps = configs.get('apps');
for (let i = 0; i < apps.length; i++) {
let app = apps[i];
if (app.cwd === process.cwd()) {
app[propType] = propValue;
configs.set('apps', apps);
return;
}
}
apps.push({
cwd: process.cwd(),
[propType]: propValue
});
configs.set('apps', apps);
}
function getDefaultMachine() {
let machine = getPropForDirectory(MACHINE_TO_DEPLOY);
if (machine) {
return machine.name;
}
if (listOfMachines.length == 2) {
return 1;
}
return EMPTY_STRING;
}
console.log('Preparing deployment to Captain...');
console.log(' ');
const questions = [
{
type: 'list',
name: 'captainNameToDeploy',
default: getDefaultMachine(),
message: 'Select the Captain Machine you want to deploy to:',
choices: listOfMachines
},
{
type: 'input',
default: getPropForDirectory(BRANCH_TO_PUSH) || 'master',
name: BRANCH_TO_PUSH,
message: 'Enter the "git" branch you would like to deploy:',
when: function (answers) {
return !!answers.captainNameToDeploy;
}
},
{
type: 'input',
default: getPropForDirectory(APP_NAME),
name: APP_NAME,
message: 'Enter the Captain app name this directory will be deployed to:',
when: function (answers) {
return !!answers.captainNameToDeploy;
}
},
{
type: 'confirm',
name: 'confirmedToDeploy',
message: 'Note that uncommitted files and files in gitignore (if any) will not be pushed to server. Please confirm so that deployment process can start.',
default: true,
when: function (answers) {
return !!answers.captainNameToDeploy;
}
}
];
let defaultInvalid = false;
if (program.default) {
if (!getDefaultMachine() || !getPropForDirectory(BRANCH_TO_PUSH) || !getPropForDirectory(APP_NAME)) {
console.log('Default deploy failed. Please select deploy options.');
defaultInvalid = true;
}
else {
console.log('Deploying to ' + getPropForDirectory(MACHINE_TO_DEPLOY).name);
deployTo(getPropForDirectory(MACHINE_TO_DEPLOY), getPropForDirectory(BRANCH_TO_PUSH), getPropForDirectory(APP_NAME));
}
}
const isStateless = program.stateless && program.host && program.appName && program.pass;
if (isStateless) {
// login first
console.log('Trying to login to', program.host)
requestLoginAuth(program.host, program.pass, function (authToken) {
// deploy
console.log('Starting stateless deploy to', program.host, program.branch, program.appName);
deployTo({
baseUrl: program.host,
authToken,
}, program.branch || 'master', program.appName);
});
}
else if (!program.default || defaultInvalid) {
inquirer.prompt(questions).then(function (answers) {
console.log(' ');
console.log(' ');
if (!answers.confirmedToDeploy) {
console.log('Operation cancelled by the user...');
console.log(' ');
}
else {
let machines = configs.get('captainMachines');
let machineToDeploy = null;
for (let i = 0; i < machines.length; i++) {
if (machines[i].name === answers.captainNameToDeploy) {
console.log('Deploying to ' + answers.captainNameToDeploy);
machineToDeploy = machines[i];
break;
}
}
console.log(' ');
deployTo(machineToDeploy, answers.branchToPush, answers.appName);
}
});
}
function deployTo(machineToDeploy, branchToPush, appName) {
function checkAuthAndSendFile(zipFileFullPath, gitHash) {
function isAuthTokenValidCallback(isValid) {
if (isValid) {
sendFileToCaptain(machineToDeploy, zipFileFullPath, appName, gitHash, branchToPush);
}
else {
requestLogin(machineToDeploy.name, machineToDeploy.baseUrl, function callback(machineToDeployNew) {
deployTo(machineToDeployNew, branchToPush, appName);
});
}
}
isAuthTokenValid(machineToDeploy, appName, isAuthTokenValidCallback);
}
if (getSuppliedTarFile()) {
checkAuthAndSendFile(path.join(process.cwd(), getSuppliedTarFile()), 'sendviatarfile');
return;
}
if (!commandExistsSync('git')) {
console.log(chalk.red('"git" command not found...'));
console.log(chalk.red('Captain needs "git" to create tar file of your source files...'));
console.log(' ');
process.exit(1);
}
let zipFileNameToDeploy = 'temporary-captain-to-deploy.tar';
let zipFileFullPath = path.join(process.cwd(), zipFileNameToDeploy);
console.log('Saving tar file to:');
console.log(zipFileFullPath);
console.log(' ');
try {
fs.removeSync(zipFileFullPath);
} catch (ignoreError) {
}
exec('git archive --format tar --output "' + zipFileFullPath + '" ' + branchToPush, function (err, stdout, stderr) {
if (err) {
console.log(chalk.red('TAR file failed'));
console.log(chalk.red(err + ' '));
console.log(' ');
fs.removeSync(zipFileFullPath);
return;
}
exec('git rev-parse ' + branchToPush, function (err, stdout, stderr) {
const gitHash = (stdout || '').trim();
if (err || !(/^[a-f0-9]{40}$/.test(gitHash))) {
console.log(chalk.red('Cannot find hash of last commit on this branch: ' + branchToPush));
console.log(chalk.red(gitHash + ' '));
console.log(chalk.red(err + ' '));
console.log(' ');
return;
}
console.log('Pushing last commit on ' + branchToPush + ': ' + gitHash);
checkAuthAndSendFile(zipFileFullPath, gitHash);
});
});
}
function sendFileToCaptain(machineToDeploy, zipFileFullPath, appName, gitHash, branchToPush) {
console.log('Uploading file to ' + machineToDeploy.baseUrl);
const fileSize = fs.statSync(zipFileFullPath).size;
const fileStream = fs.createReadStream(zipFileFullPath);
const barOpts = {
width: 20,
total: fileSize,
clear: true
};
const bar = new ProgressBar(' uploading [:bar] :percent (ETA :etas)', barOpts);
fileStream.on('data', function (chunk) {
bar.tick(chunk.length);
});
let spinner;
fileStream.on('end', function () {
console.log(' ');
console.log('This might take several minutes. PLEASE BE PATIENT...');
spinner = ora('Building your source code...').start();
spinner.color = 'yellow';
});
let options = {
url: machineToDeploy.baseUrl + '/api/v1/user/appData/' + appName + '/?detached=1',
headers: {
'x-namespace': 'captain',
'x-captain-auth': machineToDeploy.authToken
},
method: 'POST',
formData: {
sourceFile: fileStream,
gitHash: gitHash
}
};
function callback(error, response, body) {
if (spinner) {
spinner.stop();
}
if (fs.pathExistsSync(zipFileFullPath)) {
if (!getSuppliedTarFile()) {
fs.removeSync(zipFileFullPath);
}
}
try {
if (!error && response.statusCode === 200) {
let data = JSON.parse(body);
if (data.status === 1106) {
// expired token
requestLogin(machineToDeploy.name, machineToDeploy.baseUrl, function callback(machineToDeployNew) {
deployTo(machineToDeployNew, branchToPush, appName);
});
return;
}
if (data.status !== 100 && data.status !== 101) {
throw new Error(JSON.stringify(data, null, 2));
}
savePropForDirectory(APP_NAME, appName);
savePropForDirectory(BRANCH_TO_PUSH, branchToPush);
savePropForDirectory(MACHINE_TO_DEPLOY, machineToDeploy);
if (data.status === 100) {
console.log(chalk.green('Deployed successfully: ') + appName);
console.log(' ');
} else if (data.status === 101) {
console.log(chalk.green('Building started: ') + appName);
console.log(' ');
startFetchingBuildLogs(machineToDeploy, appName);
}
return;
}
if (error) {
throw new Error(error)
}
throw new Error(response ? JSON.stringify(response, null, 2) : 'Response NULL');
} catch (error) {
console.error(chalk.red('\nSomething bad happened. Cannot deploy "' + appName + '"\n'));
if (error.message) {
try {
var errorObj = JSON.parse(error.message);
if (errorObj.status) {
console.error(chalk.red('\nError code: ' + errorObj.status));
console.error(chalk.red('\nError message:\n\n ' + errorObj.description));
} else {
throw new Error("NOT API ERROR");
}
} catch (ignoreError) {
console.error(chalk.red(error.message));
}
} else {
console.error(chalk.red(error));
}
console.log(' ');
}
}
request(options, callback);
}
var lastLineNumberPrinted = -10000; // we want to show all lines to begin with!
function startFetchingBuildLogs(machineToDeploy, appName) {
let options = {
url: machineToDeploy.baseUrl + '/api/v1/user/appData/' + appName,
headers: {
'x-namespace': 'captain',
'x-captain-auth': machineToDeploy.authToken
},
method: 'GET'
};
function onLogRetrieved(data) {
if (data) {
var lines = data.logs.lines;
var firstLineNumberOfLogs = data.logs.firstLineNumber;
var firstLinesToPrint = 0;
if (firstLineNumberOfLogs > lastLineNumberPrinted) {
if (firstLineNumberOfLogs < 0) {
// This is the very first fetch, probably firstLineNumberOfLogs is around -50
firstLinesToPrint = -firstLineNumberOfLogs;
} else {
console.log('[[ TRUNCATED ]]');
}
} else {
firstLinesToPrint = lastLineNumberPrinted - firstLineNumberOfLogs;
}
lastLineNumberPrinted = firstLineNumberOfLogs + lines.length;
for (var i = firstLinesToPrint; i < lines.length; i++) {
console.log((lines[i] || '').trim());
}
}
if (data && !data.isAppBuilding) {
console.log(' ');
if (!data.isBuildFailed) {
console.log(chalk.green('Deployed successfully: ') + appName);
console.log(chalk.magenta('App is available at ')
+ (machineToDeploy.baseUrl.replace('//captain.', '//' + appName + '.').replace('https://', 'http://'))
);
} else {
console.error(chalk.red('\nSomething bad happened. Cannot deploy "' + appName + '"\n'));
}
console.log(' ');
return;
}
setTimeout(function () {
startFetchingBuildLogs(machineToDeploy, appName);
}, 2000);
}
function callback(error, response, body) {
try {
if (!error && response.statusCode === 200) {
let data = JSON.parse(body);
if (data.status !== 100) {
throw new Error(JSON.stringify(data, null, 2));
}
onLogRetrieved(data.data);
return;
}
if (error) {
throw new Error(error)
}
throw new Error(response ? JSON.stringify(response, null, 2) : 'Response NULL');
} catch (error) {
console.error(chalk.red('\nSomething while retrieving app build logs.. "' + error + '"\n'));
onLogRetrieved(null);
}
}
request(options, callback);
}
function isAuthTokenValid(machineToDeploy, appName, isAuthTokenValidCallback) {
let options = {
url: machineToDeploy.baseUrl + '/api/v1/user/appDefinitions/',
headers: {
'x-namespace': 'captain',
'x-captain-auth': machineToDeploy.authToken
},
method: 'GET'
};
function callback(error, response, body) {
try {
if (!error && response.statusCode === 200) {
let data = JSON.parse(body);
if (data.status === 1106 || data.status === 1105) {
isAuthTokenValidCallback(false);
}
else {
isAuthTokenValidCallback(true);
}
}
} catch (error) {
// This is just a sanity check. We only fire FALSE (i.e. expired) if we know it's expired or password is wrong
isAuthTokenValidCallback(true);
}
}
request(options, callback);
}
function requestLoginAuth(serverAddress, password, authCallback) {
let options = {
url: serverAddress + '/api/v1/login',
headers: {
'x-namespace': 'captain'
},
method: 'POST',
form: {
password: password
}
};
function callback(error, response, body) {
try {
if (!error && response.statusCode === 200) {
let data = JSON.parse(body);
if (data.status !== 100) {
throw new Error(JSON.stringify(data, null, 2));
}
authCallback(data.token);
return;
}
if (error) {
throw new Error(error)
}
throw new Error(response ? JSON.stringify(response, null, 2) : 'Response NULL');
} catch (error) {
if (error.message) {
try {
var errorObj = JSON.parse(error.message);
if (errorObj.status) {
console.error(chalk.red('\nError code: ' + errorObj.status));
console.error(chalk.red('\nError message:\n\n ' + errorObj.description));
} else {
throw new Error("NOT API ERROR");
}
} catch (ignoreError) {
console.error(chalk.red(error.message));
}
} else {
console.error(chalk.red(error));
}
console.log(' ');
}
process.exit(0);
}
request(options, callback);
}
function requestLogin(serverName, serverAddress, loginCallback) {
console.log('Your auth token is not valid anymore. Try to login again.');
const questions = [
{
type: 'password',
name: 'captainPassword',
message: 'Please enter your password for ' + serverAddress,
validate: function (value) {
if (value && value.trim()) {
return true;
}
return ('Please enter your password for ' + serverAddress);
}
}
];
function updateAuthTokenInConfigStoreAndReturn(authToken) {
let machines = configs.get('captainMachines');
for (let i = 0; i < machines.length; i++) {
if (machines[i].name === serverName) {
var baseUrl = machines[i].authToken = authToken;
configs.set('captainMachines', machines);
console.log('You are now logged back in to ' + serverAddress);
return machines[i];
}
}
}
inquirer.prompt(questions).then(function (passwordAnswers) {
var password = passwordAnswers.captainPassword;
let options = {
url: serverAddress + '/api/v1/login',
headers: {
'x-namespace': 'captain'
},
method: 'POST',
form: {
password: password
}
};
function callback(error, response, body) {
try {
if (!error && response.statusCode === 200) {
let data = JSON.parse(body);
if (data.status !== 100) {
throw new Error(JSON.stringify(data, null, 2));
}
var newMachineToDeploy = updateAuthTokenInConfigStoreAndReturn(data.token);
loginCallback(newMachineToDeploy);
return;
}
if (error) {
throw new Error(error)
}
throw new Error(response ? JSON.stringify(response, null, 2) : 'Response NULL');
} catch (error) {
if (error.message) {
try {
var errorObj = JSON.parse(error.message);
if (errorObj.status) {
console.error(chalk.red('\nError code: ' + errorObj.status));
console.error(chalk.red('\nError message:\n\n ' + errorObj.description));
} else {
throw new Error("NOT API ERROR");
}
} catch (ignoreError) {
console.error(chalk.red(error.message));
}
} else {
console.error(chalk.red(error));
}
console.log(' ');
}
process.exit(0);
}
request(options, callback);
});
}
-40
View File
@@ -1,40 +0,0 @@
const program = require('commander');
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const inquirer = require('inquirer');
const configstore = require('configstore');
const request = require('request');
const packagejson = require('./package.json');
const configs = new configstore(packagejson.name, {
captainMachines: []
});
program
.description('List all Captain machines currently logged in.')
.parse(process.argv);
if (program.args.length) {
console.error(chalk.red('Unrecognized commands:'));
program.args.forEach(function (arg) {
console.log(chalk.red(arg));
});
console.error(chalk.red('List does not require any options. '));
process.exit(1);
}
console.log();
console.log('Logged in Captain Machines:');
console.log();
let machines = configs.get('captainMachines');
for (let i = 0; i < machines.length; i++) {
console.log('>> ' + chalk.greenBright(machines[i].name) + ' at ' + chalk.cyan(machines[i].baseUrl));
}
console.log();
-225
View File
@@ -1,225 +0,0 @@
const program = require('commander');
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const inquirer = require('inquirer');
const configstore = require('configstore');
const request = require('request');
const packagejson = require('./package.json');
const configs = new configstore(packagejson.name, {
captainMachines: []
});
program
.description('Login to a CaptainDuckDuck machine. You can be logged in to multiple machines simultaneously.')
.parse(process.argv);
function cleanUpUrl(url) {
if (!url) {
return url;
}
url = url.trim();
url = url.replace('http://', '').replace('https://', '');
if (!url || !url.length) {
return url;
}
if (url.substr(url.length - 1, 1) === '/') {
url = url.substr(0, url.length - 1);
}
return url;
}
if (program.args.length) {
console.error(chalk.red('Unrecognized commands:'));
program.args.forEach(function (arg) {
console.log(chalk.red(arg));
});
console.error(chalk.red('Login does not require any options. '));
process.exit(1);
}
function findDefaultCaptainName() {
let machines = configs.get('captainMachines');
let currentSuffix = machines.length + 1;
function getCaptainFullName(suffix) {
if (suffix < 10) {
suffix = '0' + suffix;
}
return 'captain-' + suffix;
}
function isSuffixValid(suffixNumber) {
for (let i = 0; i < machines.length; i++) {
let m = machines[i];
if (m.name === getCaptainFullName(suffixNumber)) {
return false;
}
}
return true;
}
while (!isSuffixValid(currentSuffix)) {
currentSuffix++;
}
return getCaptainFullName(currentSuffix);
}
console.log('Login to a Captain Machine');
const SAMPLE_DOMAIN = 'captain.captainroot.yourdomain.com';
const questions = [
{
type: 'input',
default: SAMPLE_DOMAIN,
name: 'captainAddress',
message: 'Enter address of the Captain machine. \nIt is captain.[your-captain-root-domain] :',
validate: function (value) {
if (value===SAMPLE_DOMAIN){
return 'Enter a valid URL';
}
if (!cleanUpUrl(value)) {
return 'This is an invalid URL: ' + value;
}
let machines = configs.get('captainMachines');
for (let i = 0; i < machines.length; i++) {
if (cleanUpUrl(machines[i].baseUrl) === cleanUpUrl(value)) {
return value + ' already exist as ' + machines[i].name + '. If you want to replace the existing entry, you have to first use <logout> command, and then re-login.'
}
}
if (value && value.trim()) {
return true;
}
return 'Please enter a valid address.';
}
},
{
type: 'confirm',
name: 'captainHasRootSsl',
message: 'Is HTTPS activated for this Captain machine?',
default: true
},
{
type: 'password',
name: 'captainPassword',
message: 'Enter your password:',
validate: function (value) {
if (value && value.trim()) {
return true;
}
return 'Please enter your password.';
}
},
{
type: 'input',
name: 'captainName',
message: 'Enter a name for this Captain machine:',
default: findDefaultCaptainName(),
validate: function (value) {
let machines = configs.get('captainMachines');
for (let i = 0; i < machines.length; i++) {
if (machines[i].name === value) {
return value + ' already exist. If you want to replace the existing entry, you have to first use <logout> command, and then re-login.'
}
}
if (value.match(/^[-\d\w]+$/i)) {
return true;
}
return 'Please enter a Captain Name.';
}
}
];
inquirer.prompt(questions).then(function (answers) {
console.log(' ');
console.log(' ');
let baseUrl = (answers.captainHasRootSsl ? 'https://' : 'http://') + cleanUpUrl(answers.captainAddress);
let options = {
url: baseUrl + '/api/v1/login',
headers: {
'x-namespace': 'captain'
},
method: 'POST',
form: {
password: answers.captainPassword
}
};
function setCaptainMachine(data) {
let machines = configs.get('captainMachines');
machines.push(data);
configs.set('captainMachines', machines);
}
function callback(error, response, body) {
let captainNameEnterByUser = answers.captainName;
try {
if (!error && response.statusCode === 200) {
let data = JSON.parse(body);
if (data.status !== 100) {
throw new Error(JSON.stringify(data, null, 2));
}
console.log(chalk.green('Logged in successfully to ') + baseUrl);
console.log(chalk.green('Authorization token is now saved as ' + captainNameEnterByUser));
console.log(' ');
setCaptainMachine({
authToken: data.token,
baseUrl: baseUrl,
name: captainNameEnterByUser
});
return;
}
if (error) {
throw new Error(error)
}
throw new Error(response ? JSON.stringify(response, null, 2) : 'Response NULL');
} catch (error) {
console.error(chalk.red('Something bad happened. Cannot save "' + captainNameEnterByUser + '"'));
let errorMessage = error.message ? error.message : error;
console.error(chalk.red(errorMessage));
console.log(' ');
}
}
request(options, callback);
});
-91
View File
@@ -1,91 +0,0 @@
const program = require('commander');
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const inquirer = require('inquirer');
const configstore = require('configstore');
const request = require('request');
const packagejson = require('./package.json');
const configs = new configstore(packagejson.name, {
captainMachines: []
});
program
.description('Logout from a specific Captain machine.')
.parse(process.argv);
if (program.args.length) {
console.error(chalk.red('Unrecognized commands:'));
program.args.forEach(function (arg) {
console.log(chalk.red(arg));
});
console.error(chalk.red('Logout does not require any options. '));
process.exit(1);
}
let listOfMachines = [{
name: '-- CANCEL --',
value: '',
short: ''
}];
let machines = configs.get('captainMachines');
for (let i = 0; i < machines.length; i++) {
let m = machines[i];
listOfMachines.push({
name: m.name + ' at ' + m.baseUrl,
value: m.name,
short: m.name + ' at ' + m.baseUrl
})
}
console.log('Logout from a Captain Machine and clear auth info');
const questions = [
{
type: 'list',
name: 'captainNameToLogout',
message: 'Select the Captain Machine you want to logout from:',
choices: listOfMachines
},
{
type: 'confirm',
name: 'confirmedToLogout',
message: 'Are you sure you want to logout from this Captain machine?',
default: false,
when: function (answers) {
return !!answers.captainNameToLogout;
}
}
];
inquirer.prompt(questions).then(function (answers) {
console.log(' ');
console.log(' ');
if (!answers.captainNameToLogout) {
console.log('Operation cancelled by the user...');
}
else {
let machines = configs.get('captainMachines');
for (let i = 0; i < machines.length; i++) {
if (machines[i].name === answers.captainNameToLogout) {
var baseUrl = machines[i].baseUrl;
machines.splice(i,1);
configs.set('captainMachines', machines);
console.log('You are now logged out from ' + answers.captainNameToLogout + ' at ' + baseUrl + '...');
break;
}
}
}
console.log(' ');
});
-466
View File
@@ -1,466 +0,0 @@
const program = require('commander');
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const inquirer = require('inquirer');
const configstore = require('configstore');
const request = require('request');
const spinnerUtil = require('./utils/spinner');
const packagejson = require('./package.json');
const configs = new configstore(packagejson.name, {
captainMachines: []
});
program
.description('Easy setup for your Captain server.')
.parse(process.argv);
if (program.args.length) {
console.error(chalk.red('Unrecognized commands:'));
program.args.forEach(function (arg) {
console.log(chalk.red(arg));
});
console.error(chalk.red('This command does not require any options. '));
process.exit(1);
}
function findDefaultCaptainName() {
let machines = configs.get('captainMachines');
let currentSuffix = machines.length + 1;
function getCaptainFullName(suffix) {
if (suffix < 10) {
suffix = '0' + suffix;
}
return 'captain-' + suffix;
}
function isSuffixValid(suffixNumber) {
for (let i = 0; i < machines.length; i++) {
let m = machines[i];
if (m.name === getCaptainFullName(suffixNumber)) {
return false;
}
}
return true;
}
while (!isSuffixValid(currentSuffix)) {
currentSuffix++;
}
return getCaptainFullName(currentSuffix);
}
function isIpAddress(ipaddress) {
if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ipaddress)) {
return (true)
}
return (false)
}
console.log(' ');
console.log('Setup your Captain server');
console.log(' ');
const SAMPLE_IP = '123.123.123.123';
var authTokenFromLogin = null;
var ipAddressOfServer = null;
var customDomainFromUser = null;
var newPasswordFirstTry = null;
var oldPassword = 'captain42';
function setCustomDomain(baseApiUrl, customDomain) {
return apiEndpointWithLoading('Changing Domain...')('POST', baseApiUrl, '/api/v1/user/system/changerootdomain/', {
rootDomain: customDomain
});
}
function enableHttps(baseApiUrl, emailAddress) {
return apiEndpointWithLoading('Enabling SSL...')('POST', baseApiUrl, '/api/v1/user/system/enablessl/', {
emailAddress: emailAddress
});
}
function changePass(baseApiUrl, newPass) {
return apiEndpointWithLoading('Changing Password...')('POST', baseApiUrl, '/api/v1/user/changepassword/', {
oldPassword: oldPassword,
newPassword: newPass
});
}
function forceHttps(baseApiUrl) {
return apiEndpointWithLoading('Forcing SSL...')('POST', baseApiUrl, '/api/v1/user/system/forcessl/', {
isEnabled: true
});
}
// returns promise that resolves to auth token
// rejects with ErrorCode or null
function login(baseApiUrl, password) {
return apiEndpointWithLoading('Login...')('POST', baseApiUrl, '/api/v1/login', {
password: password
}).then(function (data) {
return data.token;
})
}
function apiEndpointWithLoading(message) {
return (method, baseApiUrl, endpoint, dataToSend) => {
return apiEndpoint(method, baseApiUrl, endpoint, dataToSend, message)
}
}
function apiEndpoint(method, baseApiUrl, endpoint, dataToSend, message) {
let options = {
url: baseApiUrl + endpoint,
headers: {
'x-namespace': 'captain',
'x-captain-auth': authTokenFromLogin
},
method: method,
form: dataToSend
};
return new Promise(function (res, rej) {
let spinner;
if (message) {
spinner = spinnerUtil.start(message);
}
var callback = function (error, response, body) {
try {
if (!error && response.statusCode === 200) {
let data = JSON.parse(body);
if (data.status !== 100) {
if (spinner) {
spinnerUtil.fail(spinner);
}
rej(data);
return;
}
if (spinner) {
spinnerUtil.succeed(spinner);
}
res(data);
return;
}
if (error) {
throw new Error(error)
}
throw new Error(response ? JSON.stringify(response, null, 2) : 'Response NULL');
} catch (error) {
if (spinner) {
spinnerUtil.fail(spinner);
}
console.error(chalk.red('Something bad happened. Cannot connect to "' + baseApiUrl + endpoint + '"'));
let errorMessage = error.message ? error.message : error;
console.error(chalk.red(errorMessage));
console.log(' ');
process.exit(0);
}
}
request(options, callback);
});
}
const questions = [
{
type: 'list',
name: 'hasInstalledCaptain',
message: 'Have you already installed Captain on your server by running the following line:'
+ '\nmkdir /captain && docker run -v /var/run/docker.sock:/var/run/docker.sock dockersaturn/captainduckduck ?',
default: 'Yes',
choices: ['Yes', 'No'],
filter: function (value) {
return new Promise(function (res, rej) {
if (value === 'Yes') {
res(true);
return;
}
console.log('');
console.log('');
console.log(' Cannot start the setup process if Captain is not installed.');
console.log(' Please read tutorial on CaptainDuckDuck.com'
+ ' to learn how to install CaptainDuckDuck on a server.');
console.log('');
process.exit(0);
});
}
},
{
type: 'input',
default: SAMPLE_IP,
name: 'captainAddress',
message: 'Enter IP address of your captain server:',
filter: function (value) {
return new Promise(function (res, rej) {
if (value === SAMPLE_IP) {
rej('Enter a valid IP Address');
return;
}
if (!isIpAddress(value.trim())) {
rej('This is an invalid IP Address: ' + value);
return;
}
ipAddressOfServer = value.trim();
//login using captain42.
return login('http://' + ipAddressOfServer + ':3000', oldPassword)
.then(function (authTokenFetched) {
authTokenFromLogin = authTokenFetched;
res(ipAddressOfServer);
})
.catch(function (error) {
// if error is anything but password wrong, exit here...
if (error.status == 1105) {
authTokenFromLogin = null;
res(ipAddressOfServer);
return;
}
console.log('');
if (error.status) {
console.log(chalk.red('Error: ' + error.status));
console.log(chalk.red('Error: ' + error.description));
} else {
console.log(chalk.red('Error: ' + error));
}
process.exit(0);
});
})
}
},
{
type: 'password',
name: 'captainOriginalPassword',
message: 'Enter your current password:',
when: function () {
return !authTokenFromLogin;
},
filter: function (value) {
return new Promise(function (res, rej) {
console.log('');
return login('http://' + ipAddressOfServer + ':3000', value)
.then(function (authTokenFetched) {
authTokenFromLogin = authTokenFetched;
oldPassword = value;
res(value);
})
.catch(function (error) {
console.log('');
if (error.status) {
console.log(chalk.red('Error: ' + error.status));
console.log(chalk.red('Error: ' + error.description));
} else {
console.log(chalk.red('Error: ' + error));
}
process.exit(0);
});
})
}
},
{
type: 'input',
name: 'captainRootDomain',
message: 'Enter a root domain for this Captain server. For example, enter test.yourdomain.com if you' +
' setup your DNS to point *.test.yourdomain.com to ip address of your server' + ': ',
filter: function (value) {
return new Promise(function (res, rej) {
value = value.trim();
customDomainFromUser = 'captain.' + value;
return setCustomDomain('http://' + ipAddressOfServer + ':3000', value)
.then(function () {
res(customDomainFromUser);
})
.catch(function (error) {
console.log('');
if (error.status) {
console.log(chalk.red('Error: ' + error.status));
console.log(chalk.red('Error: ' + error.description));
} else {
console.log(chalk.red('Error: ' + error));
}
process.exit(0);
});
})
}
},
{
type: 'input',
name: 'emailAddress',
message: 'Enter your "valid" email address to enable HTTPS: ',
filter: function (value) {
return new Promise(function (res, rej) {
console.log('');
value = value.trim();
return enableHttps('http://' + customDomainFromUser, value)
.then(function () {
return forceHttps('https://' + customDomainFromUser);
})
.then(function () {
res(value);
})
.catch(function (error) {
console.log('');
if (error.status) {
console.log(chalk.red('Error: ' + error.status));
console.log(chalk.red('Error: ' + error.description));
} else {
console.log(chalk.red('Error: ' + error));
}
process.exit(0);
});
})
}
},
{
type: 'password',
name: 'newPasswordFirstTry',
message: 'Enter a new password:',
filter: function (value) {
return new Promise(function (res, rej) {
newPasswordFirstTry = value;
res(value);
})
}
},
{
type: 'password',
name: 'newPassword',
message: 'Enter a new password:',
filter: function (value) {
return new Promise(function (res, rej) {
if (newPasswordFirstTry !== value) {
rej('Passwords do not match');
//process.exit(0);
return;
}
return changePass('https://' + customDomainFromUser, value)
.then(function () {
return login('https://' + customDomainFromUser, value)
})
.then(function (token) {
authTokenFromLogin = token;
res(value);
})
.catch(function (error) {
console.log('');
if (error.status) {
console.log(chalk.red('Error: ' + error.status));
console.log(chalk.red('Error: ' + error.description));
} else {
console.log(chalk.red('Error: ' + error));
}
console.log('');
console.log(chalk.red('IMPORTANT!! Server setup is completed by password is not changed.'));
console.log(chalk.red('You CANNOT use serversetup anymore. To continue: '));
console.log(chalk.red('- Go to https://' + customDomainFromUser
+ ' login with default password and change the password in settings.'));
console.log(chalk.red('- In terminal (here), type captainduckduck login and enter this as your root domain: ' + customDomainFromUser));
process.exit(0);
});
})
}
},
{
type: 'input',
name: 'captainName',
message: 'Enter a name for this Captain machine:',
default: findDefaultCaptainName(),
validate: function (value) {
let machines = configs.get('captainMachines');
for (let i = 0; i < machines.length; i++) {
if (machines[i].name === value) {
return value + ' already exist. If you want to replace the existing entry, you have to first use <logout> command, and then re-login.'
}
}
if (value.match(/^[-\d\w]+$/i)) {
return true;
}
return 'Please enter a Captain Name.';
}
}
];
inquirer.prompt(questions).then(function (answers) {
var captainAddress = 'https://' + customDomainFromUser;
let machines = configs.get('captainMachines');
machines.push({
authToken: authTokenFromLogin,
baseUrl: captainAddress,
name: answers.captainName
});
configs.set('captainMachines', machines);
console.log(' ');
console.log(' ');
console.log('Captain is available at ' + captainAddress);
console.log(' ');
console.log('For more details and docs see http://www.captainduckduck.com');
console.log(' ');
console.log(' ');
});
+41 -15
View File
@@ -1,22 +1,48 @@
#!/usr/bin/env node
const packagejson = require('./package.json');
const updateNotifier = require('update-notifier');
const packagejson = require("./package.json")
const updateNotifier = require("update-notifier")
updateNotifier({ pkg: packagejson }).notify({ isGlobal: true });
updateNotifier({ pkg: packagejson }).notify({ isGlobal: true })
const program = require("commander")
const fs = require("fs")
const path = require("path")
const chalk = require("chalk")
// Command actions
const list = require("./lib/list")
const logout = require("./lib/logout")
const program = require('commander');
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
// Setup
program.version(packagejson.version + "").description(packagejson.description)
program
.version(packagejson.version + '')
.description(packagejson.description)
.command('serversetup', 'Performs necessary actions and prepares your Captain server.')
.command('login', 'Login to a CaptainDuckDuck machine. You can be logged in to multiple machines simultaneously.')
.command('logout', 'Logout from a specific Captain machine.')
.command('list', 'List all Captain machines currently logged in.')
.command('deploy', 'Deploy your app (current directory) to a specific Captain machine. You\'ll be prompted to choose your Captain machine.')
.parse(process.argv);
.command(
"serversetup",
"Performs necessary actions and prepares your Captain server."
)
.command(
"login",
"Login to a CaptainDuckDuck machine. You can be logged in to multiple machines simultaneously."
)
.command(
"deploy",
"Deploy your app (current directory) to a specific Captain machine. You'll be prompted to choose your Captain machine."
)
// Commands
program
.command("logout")
.description("Logout from a specific Captain machine.")
.action(() => {
logout()
})
program
.command("list")
.description("List all Captain machines currently logged in.")
.action(() => {
list()
})
program.parse(process.argv)
+440
View File
@@ -0,0 +1,440 @@
#!/usr/bin/env node
const program = require("commander")
const fs = require("fs-extra")
const path = require("path")
const chalk = require("chalk")
const inquirer = require("inquirer")
const configstore = require("configstore")
const request = require("request")
const commandExistsSync = require("command-exists").sync
const { exec } = require("child_process")
const { sendFileToCaptain } = require("./utils/fileHandler")
const {
requestLogin,
requestLoginAuth,
isAuthTokenValid
} = require("./utils/loginHandler")
const {
validateIsGitRepository,
validateDefinitionFile
} = require("./utils/validationsHandler")
const { printErrorAndExit } = require("./utils/errorHandler")
const packagejson = require("./package.json")
const configs = new configstore(packagejson.name, {
captainMachines: [],
apps: []
})
const BRANCH_TO_PUSH = "branchToPush"
const APP_NAME = "appName"
const MACHINE_TO_DEPLOY = "machineToDeploy"
const EMPTY_STRING = ""
console.log("\n")
program
.description("Deploy current directory to a Captain machine.")
.option(
"-t, --tarFile <value>",
"Specify file to be uploaded (rather than using git archive)"
)
.option("-d, --default", "Run with default options")
.option("-s, --stateless", "Run deploy stateless")
.option(
"-h, --host <value>",
"Only for stateless mode: Host of the captain machine"
)
.option("-a, --appName <value>", "Only for stateless mode: Name of the app")
.option("-p, --pass <value>", "Only for stateless mode: Password for Captain")
.option(
"-b, --branch <value>",
"Only for stateless mode: Branch name (default master)"
)
.parse(process.argv)
if (program.args.length) {
console.error(chalk.red("Unrecognized commands:"))
program.args.forEach(function(arg) {
console.log(chalk.red(arg))
})
console.error(chalk.red("Deploy does not require any options. "))
process.exit(1)
}
function getSuppliedTarFile() {
return program.tarFile
}
if (!getSuppliedTarFile()) {
validateIsGitRepository()
validateDefinitionFile()
const contents = fs.readFileSync("./captain-definition", "utf8")
let contentsJson = null
try {
contentsJson = JSON.parse(contents)
} catch (e) {
console.log(e)
console.log("")
printErrorAndExit(
"**** ERROR: captain-definition file is not a valid JSON! ****"
)
}
if (!contentsJson.schemaVersion) {
printErrorAndExit(
"**** ERROR: captain-definition needs schemaVersion. Please see docs! ****"
)
}
if (!contentsJson.templateId && !contentsJson.dockerfileLines) {
printErrorAndExit(
"**** ERROR: captain-definition needs templateId or dockerfileLines. Please see docs! ****"
)
}
if (contentsJson.templateId && contentsJson.dockerfileLines) {
printErrorAndExit(
"**** ERROR: captain-definition needs templateId or dockerfileLines, NOT BOTH! Please see docs! ****"
)
}
}
let listOfMachines = [
{
name: "-- CANCEL --",
value: EMPTY_STRING,
short: EMPTY_STRING
}
]
let machines = configs.get("captainMachines")
for (let i = 0; i < machines.length; i++) {
let m = machines[i]
listOfMachines.push({
name: m.name + " at " + m.baseUrl,
value: m.name,
short: m.name + " at " + m.baseUrl
})
}
// Gets default value for propType that is stored in a directory.
// Replaces getAppForDirectory
function getPropForDirectory(propType) {
let apps = configs.get("apps")
for (let i = 0; i < apps.length; i++) {
let app = apps[i]
if (app.cwd === process.cwd()) {
return app[propType]
}
}
return undefined
}
function getDefaultMachine() {
let machine = getPropForDirectory(MACHINE_TO_DEPLOY)
if (machine) {
return machine.name
}
if (listOfMachines.length == 2) {
return 1
}
return EMPTY_STRING
}
console.log("Preparing deployment to Captain...\n")
const questions = [
{
type: "list",
name: "captainNameToDeploy",
default: getDefaultMachine(),
message: "Select the Captain Machine you want to deploy to:",
choices: listOfMachines
},
{
type: "input",
default: getPropForDirectory(BRANCH_TO_PUSH) || "master",
name: BRANCH_TO_PUSH,
message: "Enter the 'git' branch you would like to deploy:",
when: function(answers) {
return !!answers.captainNameToDeploy
}
},
{
type: "input",
default: getPropForDirectory(APP_NAME),
name: APP_NAME,
message: "Enter the Captain app name this directory will be deployed to:",
when: function(answers) {
return !!answers.captainNameToDeploy
}
},
{
type: "confirm",
name: "confirmedToDeploy",
message:
"Note that uncommitted files and files in gitignore (if any) will not be pushed to server. Please confirm so that deployment process can start.",
default: true,
when: function(answers) {
return !!answers.captainNameToDeploy
}
}
]
let defaultInvalid = false
if (program.default) {
if (
!getDefaultMachine() ||
!getPropForDirectory(BRANCH_TO_PUSH) ||
!getPropForDirectory(APP_NAME)
) {
console.log("Default deploy failed. Please select deploy options.")
defaultInvalid = true
} else {
console.log("Deploying to " + getPropForDirectory(MACHINE_TO_DEPLOY).name)
deployTo(
getPropForDirectory(MACHINE_TO_DEPLOY),
getPropForDirectory(BRANCH_TO_PUSH),
getPropForDirectory(APP_NAME)
)
}
}
const isStateless =
program.stateless && program.host && program.appName && program.pass
if (isStateless) {
// login first
console.log("Trying to login to", program.host)
requestLoginAuth(program.host, program.pass, function(authToken) {
// deploy
console.log(
"Starting stateless deploy to",
program.host,
program.branch,
program.appName
)
deployTo(
{
baseUrl: program.host,
authToken
},
program.branch || "master",
program.appName
)
})
} else if (!program.default || defaultInvalid) {
inquirer.prompt(questions).then(function(answers) {
console.log(" ")
console.log(" ")
if (!answers.confirmedToDeploy) {
console.log("Operation cancelled by the user...")
console.log(" ")
} else {
let machines = configs.get("captainMachines")
let machineToDeploy = null
for (let i = 0; i < machines.length; i++) {
if (machines[i].name === answers.captainNameToDeploy) {
console.log("Deploying to " + answers.captainNameToDeploy)
machineToDeploy = machines[i]
break
}
}
console.log(" ")
deployTo(machineToDeploy, answers.branchToPush, answers.appName)
}
})
}
function deployTo(machineToDeploy, branchToPush, appName) {
function checkAuthAndSendFile(zipFileFullPath, gitHash) {
function isAuthTokenValidCallback(isValid) {
if (isValid) {
sendFileToCaptain(
machineToDeploy,
zipFileFullPath,
appName,
gitHash,
branchToPush
)
} else {
requestLogin(
machineToDeploy.name,
machineToDeploy.baseUrl,
function callback(machineToDeployNew) {
deployTo(machineToDeployNew, branchToPush, appName)
}
)
}
}
isAuthTokenValid(machineToDeploy, appName, isAuthTokenValidCallback)
}
if (getSuppliedTarFile()) {
checkAuthAndSendFile(
path.join(process.cwd(), getSuppliedTarFile()),
"sendviatarfile"
)
return
}
if (!commandExistsSync("git")) {
console.log(chalk.red('"git" command not found...'))
console.log(
chalk.red(
'Captain needs "git" to create tar file of your source files...'
)
)
console.log(" ")
process.exit(1)
}
let zipFileNameToDeploy = "temporary-captain-to-deploy.tar"
let zipFileFullPath = path.join(process.cwd(), zipFileNameToDeploy)
console.log("Saving tar file to:")
console.log(zipFileFullPath)
console.log(" ")
try {
fs.removeSync(zipFileFullPath)
} catch (ignoreError) {}
exec(
'git archive --format tar --output "' +
zipFileFullPath +
'" ' +
branchToPush,
function(err, stdout, stderr) {
if (err) {
console.log(chalk.red("TAR file failed"))
console.log(chalk.red(err + " "))
console.log(" ")
fs.removeSync(zipFileFullPath)
return
}
exec("git rev-parse " + branchToPush, function(err, stdout, stderr) {
const gitHash = (stdout || "").trim()
if (err || !/^[a-f0-9]{40}$/.test(gitHash)) {
console.log(
chalk.red(
"Cannot find hash of last commit on this branch: " + branchToPush
)
)
console.log(chalk.red(gitHash + " "))
console.log(chalk.red(err + " "))
console.log(" ")
return
}
console.log("Pushing last commit on " + branchToPush + ": " + gitHash)
checkAuthAndSendFile(zipFileFullPath, gitHash)
})
}
)
}
var lastLineNumberPrinted = -10000 // we want to show all lines to begin with!
function startFetchingBuildLogs(machineToDeploy, appName) {
let options = {
url: machineToDeploy.baseUrl + "/api/v1/user/appData/" + appName,
headers: {
"x-namespace": "captain",
"x-captain-auth": machineToDeploy.authToken
},
method: "GET"
}
function onLogRetrieved(data) {
if (data) {
var lines = data.logs.lines
var firstLineNumberOfLogs = data.logs.firstLineNumber
var firstLinesToPrint = 0
if (firstLineNumberOfLogs > lastLineNumberPrinted) {
if (firstLineNumberOfLogs < 0) {
// This is the very first fetch, probably firstLineNumberOfLogs is around -50
firstLinesToPrint = -firstLineNumberOfLogs
} else {
console.log("[[ TRUNCATED ]]")
}
} else {
firstLinesToPrint = lastLineNumberPrinted - firstLineNumberOfLogs
}
lastLineNumberPrinted = firstLineNumberOfLogs + lines.length
for (var i = firstLinesToPrint; i < lines.length; i++) {
console.log((lines[i] || "").trim())
}
}
if (data && !data.isAppBuilding) {
console.log(" ")
if (!data.isBuildFailed) {
console.log(chalk.green("Deployed successfully: ") + appName)
console.log(
chalk.magenta("App is available at ") +
machineToDeploy.baseUrl
.replace("//captain.", "//" + appName + ".")
.replace("https://", "http://")
)
} else {
console.error(
chalk.red(
'\nSomething bad happened. Cannot deploy "' + appName + '"\n'
)
)
}
console.log(" ")
return
}
setTimeout(function() {
startFetchingBuildLogs(machineToDeploy, appName)
}, 2000)
}
function callback(error, response, body) {
try {
if (!error && response.statusCode === 200) {
let data = JSON.parse(body)
if (data.status !== 100) {
throw new Error(JSON.stringify(data, null, 2))
}
onLogRetrieved(data.data)
return
}
if (error) {
throw new Error(error)
}
throw new Error(
response ? JSON.stringify(response, null, 2) : "Response NULL"
)
} catch (error) {
console.error(
chalk.red(
'\nSomething while retrieving app build logs.. "' + error + '"\n'
)
)
onLogRetrieved(null)
}
}
request(options, callback)
}
+29
View File
@@ -0,0 +1,29 @@
const chalk = require("chalk")
const configstore = require("configstore")
const packagejson = require("../package.json")
const configs = new configstore(packagejson.name, {
captainMachines: []
})
function _displayMachine(machine) {
console.log(
">> " +
chalk.greenBright(machine.name) +
" at " +
chalk.cyan(machine.baseUrl)
)
}
function list() {
console.log("\nLogged in Captain Machines:\n")
const machines = configs.get("captainMachines")
machines.map(machine => {
_displayMachine(machine)
})
console.log("")
}
module.exports = list
+239
View File
@@ -0,0 +1,239 @@
const program = require("commander")
const fs = require("fs")
const path = require("path")
const chalk = require("chalk")
const inquirer = require("inquirer")
const configstore = require("configstore")
const request = require("request")
const packagejson = require("./package.json")
const configs = new configstore(packagejson.name, {
captainMachines: []
})
program
.description(
"Login to a CaptainDuckDuck machine. You can be logged in to multiple machines simultaneously."
)
.parse(process.argv)
function cleanUpUrl(url) {
if (!url) {
return url
}
url = url.trim()
url = url.replace("http://", "").replace("https://", "")
if (!url || !url.length) {
return url
}
if (url.substr(url.length - 1, 1) === "/") {
url = url.substr(0, url.length - 1)
}
return url
}
if (program.args.length) {
console.error(chalk.red("Unrecognized commands:"))
program.args.forEach(function(arg) {
console.log(chalk.red(arg))
})
console.error(chalk.red("Login does not require any options. "))
process.exit(1)
}
function findDefaultCaptainName() {
const machines = configs.get("captainMachines")
let currentSuffix = machines.length + 1
function getCaptainFullName(suffix) {
if (suffix < 10) {
suffix = "0" + suffix
}
return "captain-" + suffix
}
function isSuffixValid(suffixNumber) {
for (let i = 0; i < machines.length; i++) {
const m = machines[i]
if (m.name === getCaptainFullName(suffixNumber)) {
return false
}
}
return true
}
while (!isSuffixValid(currentSuffix)) {
currentSuffix++
}
return getCaptainFullName(currentSuffix)
}
console.log("Login to a Captain Machine")
const SAMPLE_DOMAIN = "captain.captainroot.yourdomain.com"
const questions = [
{
type: "input",
default: SAMPLE_DOMAIN,
name: "captainAddress",
message:
"Enter address of the Captain machine. \nIt is captain.[your-captain-root-domain] :",
validate: function(value) {
if (value === SAMPLE_DOMAIN) {
return "Enter a valid URL"
}
if (!cleanUpUrl(value)) {
return "This is an invalid URL: " + value
}
const machines = configs.get("captainMachines")
for (let i = 0; i < machines.length; i++) {
if (cleanUpUrl(machines[i].baseUrl) === cleanUpUrl(value)) {
return (
value +
" already exist as " +
machines[i].name +
". If you want to replace the existing entry, you have to first use <logout> command, and then re-login."
)
}
}
if (value && value.trim()) {
return true
}
return "Please enter a valid address."
}
},
{
type: "confirm",
name: "captainHasRootSsl",
message: "Is HTTPS activated for this Captain machine?",
default: true
},
{
type: "password",
name: "captainPassword",
message: "Enter your password:",
validate: function(value) {
if (value && value.trim()) {
return true
}
return "Please enter your password."
}
},
{
type: "input",
name: "captainName",
message: "Enter a name for this Captain machine:",
default: findDefaultCaptainName(),
validate: function(value) {
const machines = configs.get("captainMachines")
for (let i = 0; i < machines.length; i++) {
if (machines[i].name === value) {
return (
value +
" already exist. If you want to replace the existing entry, you have to first use <logout> command, and then re-login."
)
}
}
if (value.match(/^[-\d\w]+$/i)) {
return true
}
return "Please enter a Captain Name."
}
}
]
function setCaptainMachine(data) {
const machines = configs.get("captainMachines")
machines.push(data)
configs.set("captainMachines", machines)
}
inquirer.prompt(questions).then(function(answers) {
console.log("\n")
const baseUrl =
(answers.captainHasRootSsl ? "https://" : "http://") +
cleanUpUrl(answers.captainAddress)
const options = {
url: baseUrl + "/api/v1/login",
headers: {
"x-namespace": "captain"
},
method: "POST",
form: {
password: answers.captainPassword
}
}
function callback(error, response, body) {
const captainNameEnterByUser = answers.captainName
try {
if (!error && response.statusCode === 200) {
const data = JSON.parse(body)
if (data.status !== 100) {
throw new Error(JSON.stringify(data, null, 2))
}
console.log(chalk.green("Logged in successfully to ") + baseUrl)
console.log(
chalk.green(
`Authorization token is now saved as ${captainNameEnterByUser} \n`
)
)
setCaptainMachine({
authToken: data.token,
baseUrl: baseUrl,
name: captainNameEnterByUser
})
return
}
if (error) {
throw new Error(error)
}
throw new Error(
response ? JSON.stringify(response, null, 2) : "Response NULL"
)
} catch (error) {
console.error(
chalk.red(
`Something bad happened. Cannot save "${captainNameEnterByUser}"`
)
)
const errorMessage = error.message ? error.message : error
console.error(`${chalk.red(errorMessage)} \n`)
}
}
request(options, callback)
})
+79
View File
@@ -0,0 +1,79 @@
const inquirer = require("inquirer")
const configstore = require("configstore")
const packagejson = require("../package.json")
const configs = new configstore(packagejson.name, {
captainMachines: []
})
const machines = configs.get("captainMachines")
function getMachineList() {
const firstItemInOption = [
{
name: "-- CANCEL --",
value: "",
short: ""
}
]
const listOfMachines = machines.map(machine => {
return {
name: `${machine.name} at ${machine.baseUrl}`,
value: `${machine.name}`,
short: `${machine.name} at ${machine.baseUrl}`
}
})
return [...firstItemInOption, ...listOfMachines]
}
function generateQuestions() {
const listOfMachines = getMachineList()
return [
{
type: "list",
name: "captainNameToLogout",
message: "Select the Captain Machine you want to logout from:",
choices: listOfMachines
},
{
type: "confirm",
name: "confirmedToLogout",
message: "Are you sure you want to logout from this Captain machine?",
default: false,
when: answers => !!answers.captainNameToLogout
}
]
}
function removeMachineFromLocalStorage(machineName) {
const removedMachine = machines.filter(
machine => machine.name === machineName
)
const newMachines = machines.filter(machine => machine !== machineName)
configs.set("captainMachines", newMachines)
console.log(
`You are now logged out from ${removedMachine.name} at ${
removedMachine.baseUrl
}...\n`
)
}
function logout() {
const questions = generateQuestions()
console.log("Logout from a Captain Machine and clear auth info")
inquirer.prompt(questions).then(answers => {
if (!answers.captainNameToLogout) {
console.log("\nOperation cancelled by the user...\n")
return
}
removeMachineFromLocalStorage(answers.captainNameToLogout)
})
}
module.exports = logout
+514
View File
@@ -0,0 +1,514 @@
const program = require("commander")
const chalk = require("chalk")
const inquirer = require("inquirer")
const configstore = require("configstore")
const request = require("request")
const spinnerUtil = require("./utils/spinner")
const packagejson = require("./package.json")
const configs = new configstore(packagejson.name, {
captainMachines: []
})
program.description("Easy setup for your Captain server.").parse(process.argv)
if (program.args.length) {
console.error(chalk.red("Unrecognized commands:"))
program.args.forEach(function(arg) {
console.log(chalk.red(arg))
})
console.error(chalk.red("This command does not require any options. "))
process.exit(1)
}
function findDefaultCaptainName() {
const machines = configs.get("captainMachines")
let currentSuffix = machines.length + 1
function getCaptainFullName(suffix) {
if (suffix < 10) {
suffix = "0" + suffix
}
return "captain-" + suffix
}
function isSuffixValid(suffixNumber) {
for (let i = 0; i < machines.length; i++) {
const m = machines[i]
if (m.name === getCaptainFullName(suffixNumber)) {
return false
}
}
return true
}
while (!isSuffixValid(currentSuffix)) {
currentSuffix++
}
return getCaptainFullName(currentSuffix)
}
function isIpAddress(ipaddress) {
if (
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(
ipaddress
)
) {
return true
}
return false
}
console.log("\nSetup your Captain server\n")
const SAMPLE_IP = "123.123.123.123"
let authTokenFromLogin = null
let ipAddressOfServer = null
let customDomainFromUser = null
let newPasswordFirstTry = null
let oldPassword = "captain42"
function setCustomDomain(baseApiUrl, customDomain) {
return apiEndpointWithLoading("Changing Domain...")(
"POST",
baseApiUrl,
"/api/v1/user/system/changerootdomain/",
{
rootDomain: customDomain
}
)
}
function enableHttps(baseApiUrl, emailAddress) {
return apiEndpointWithLoading("Enabling SSL...")(
"POST",
baseApiUrl,
"/api/v1/user/system/enablessl/",
{
emailAddress: emailAddress
}
)
}
function changePass(baseApiUrl, newPass) {
return apiEndpointWithLoading("Changing Password...")(
"POST",
baseApiUrl,
"/api/v1/user/changepassword/",
{
oldPassword: oldPassword,
newPassword: newPass
}
)
}
function forceHttps(baseApiUrl) {
return apiEndpointWithLoading("Forcing SSL...")(
"POST",
baseApiUrl,
"/api/v1/user/system/forcessl/",
{
isEnabled: true
}
)
}
// returns promise that resolves to auth token
// rejects with ErrorCode or null
function login(baseApiUrl, password) {
return apiEndpointWithLoading("Login...")(
"POST",
baseApiUrl,
"/api/v1/login",
{
password: password
}
).then(function(data) {
return data.token
})
}
function apiEndpointWithLoading(message) {
return (method, baseApiUrl, endpoint, dataToSend) => {
return apiEndpoint(method, baseApiUrl, endpoint, dataToSend, message)
}
}
function apiEndpoint(method, baseApiUrl, endpoint, dataToSend, message) {
const options = {
url: baseApiUrl + endpoint,
headers: {
"x-namespace": "captain",
"x-captain-auth": authTokenFromLogin
},
method: method,
form: dataToSend
}
return new Promise(function(res, rej) {
let spinner
if (message) {
spinner = spinnerUtil.start(message)
}
var callback = function(error, response, body) {
try {
if (!error && response.statusCode === 200) {
const data = JSON.parse(body)
if (data.status !== 100) {
if (spinner) {
spinnerUtil.fail(spinner)
}
rej(data)
return
}
if (spinner) {
spinnerUtil.succeed(spinner)
}
res(data)
return
}
if (error) {
throw new Error(error)
}
throw new Error(
response ? JSON.stringify(response, null, 2) : "Response NULL"
)
} catch (error) {
if (spinner) {
spinnerUtil.fail(spinner)
}
console.error(
chalk.red(
`Something bad happened. Cannot connect to "${baseApiUrl} ${endpoint}"`
)
)
const errorMessage = error.message ? error.message : error
console.error(`${chalk.red(errorMessage)}\n`)
process.exit(0)
}
}
request(options, callback)
})
}
const questions = [
{
type: "list",
name: "hasInstalledCaptain",
message:
"Have you already installed Captain on your server by running the following line:" +
"\nmkdir /captain && docker run -v /var/run/docker.sock:/var/run/docker.sock dockersaturn/captainduckduck ?",
default: "Yes",
choices: ["Yes", "No"],
filter: function(value) {
return new Promise(function(res, rej) {
if (value === "Yes") {
res(true)
return
}
console.log(
"\n\nCannot start the setup process if Captain is not installed."
)
console.log(
"Please read tutorial on CaptainDuckDuck.com\n to learn how to install CaptainDuckDuck on a server.\n"
)
process.exit(0)
})
}
},
{
type: "input",
default: SAMPLE_IP,
name: "captainAddress",
message: "Enter IP address of your captain server:",
filter: function(value) {
return new Promise(function(res, rej) {
if (value === SAMPLE_IP) {
rej("Enter a valid IP Address")
return
}
if (!isIpAddress(value.trim())) {
rej("This is an invalid IP Address: " + value)
return
}
ipAddressOfServer = value.trim()
//login using captain42.
return login("http://" + ipAddressOfServer + ":3000", oldPassword)
.then(function(authTokenFetched) {
authTokenFromLogin = authTokenFetched
res(ipAddressOfServer)
})
.catch(function(error) {
// if error is anything but password wrong, exit here...
if (error.status == 1105) {
authTokenFromLogin = null
res(ipAddressOfServer)
return
}
console.log("")
if (error.status) {
console.log(chalk.red(`Error: ${error.status}`))
console.log(chalk.red(`Error: ${error.description}`))
} else {
console.log(chalk.red(`Error: ${error}`))
}
process.exit(0)
})
})
}
},
{
type: "password",
name: "captainOriginalPassword",
message: "Enter your current password:",
when: function() {
return !authTokenFromLogin
},
filter: function(value) {
return new Promise(function(res, rej) {
console.log("")
return login("http://" + ipAddressOfServer + ":3000", value)
.then(function(authTokenFetched) {
authTokenFromLogin = authTokenFetched
oldPassword = value
res(value)
})
.catch(function(error) {
console.log("")
if (error.status) {
console.log(chalk.red("Error: " + error.status))
console.log(chalk.red("Error: " + error.description))
} else {
console.log(chalk.red("Error: " + error))
}
process.exit(0)
})
})
}
},
{
type: "input",
name: "captainRootDomain",
message:
"Enter a root domain for this Captain server. For example, enter test.yourdomain.com if you" +
" setup your DNS to point *.test.yourdomain.com to ip address of your server" +
": ",
filter: function(value) {
return new Promise(function(res, rej) {
value = value.trim()
customDomainFromUser = "captain." + value
return setCustomDomain("http://" + ipAddressOfServer + ":3000", value)
.then(function() {
res(customDomainFromUser)
})
.catch(function(error) {
if (error.status) {
console.log(chalk.red(`Error: ${error.status}`))
console.log(chalk.red(`Error: ${error.description}`))
} else {
console.log(chalk.red(`Error: ${error}`))
}
process.exit(0)
})
})
}
},
{
type: "input",
name: "emailAddress",
message: "Enter your 'valid' email address to enable HTTPS: ",
filter: function(value) {
return new Promise(function(res, rej) {
console.log("")
value = value.trim()
return enableHttps("http://" + customDomainFromUser, value)
.then(function() {
return forceHttps("https://" + customDomainFromUser)
})
.then(function() {
res(value)
})
.catch(function(error) {
console.log("")
if (error.status) {
console.log(chalk.red(`Error: ${error.status}`))
console.log(chalk.red(`Error: ${error.description}`))
} else {
console.log(chalk.red(`Error: ${error}`))
}
process.exit(0)
})
})
}
},
{
type: "password",
name: "newPasswordFirstTry",
message: "Enter a new password:",
filter: function(value) {
return new Promise(function(res, rej) {
newPasswordFirstTry = value
res(value)
})
}
},
{
type: "password",
name: "newPassword",
message: "Enter a new password:",
filter: function(value) {
return new Promise(function(res, rej) {
if (newPasswordFirstTry !== value) {
rej("Passwords do not match")
//process.exit(0);
return
}
return changePass("https://" + customDomainFromUser, value)
.then(function() {
return login("https://" + customDomainFromUser, value)
})
.then(function(token) {
authTokenFromLogin = token
res(value)
})
.catch(function(error) {
console.log("")
if (error.status) {
console.log(chalk.red(`Error: ${error.status}`))
console.log(chalk.red(`Error: ${error.description}`))
} else {
console.log(chalk.red(`Error: ${error}`))
}
console.log(
chalk.red(
"\nIMPORTANT!! Server setup is completed by password is not changed."
)
)
console.log(
chalk.red("You CANNOT use serversetup anymore. To continue: ")
)
console.log(
chalk.red(
"- Go to https://" +
customDomainFromUser +
" login with default password and change the password in settings."
)
)
console.log(
chalk.red(
"- In terminal (here), type captainduckduck login and enter this as your root domain: " +
customDomainFromUser
)
)
process.exit(0)
})
})
}
},
{
type: "input",
name: "captainName",
message: "Enter a name for this Captain machine:",
default: findDefaultCaptainName(),
validate: function(value) {
const machines = configs.get("captainMachines")
for (let i = 0; i < machines.length; i++) {
if (machines[i].name === value) {
return `${value} already exist. If you want to replace the existing entry, you have to first use <logout> command, and then re-login.`
}
}
if (value.match(/^[-\d\w]+$/i)) {
return true
}
return "Please enter a Captain Name."
}
}
]
inquirer.prompt(questions).then(function(answers) {
var captainAddress = "https://" + customDomainFromUser
const machines = configs.get("captainMachines")
machines.push({
authToken: authTokenFromLogin,
baseUrl: captainAddress,
name: answers.captainName
})
configs.set("captainMachines", machines)
console.log(`\n\nCaptain is available at ${captainAddress}`)
console.log(
"\nFor more details and docs see http://www.captainduckduck.com\n\n"
)
})
+9
View File
@@ -0,0 +1,9 @@
const chalk = require("chalk")
function printErrorAndExit(error) {
console.log(`${chalk.bold.red(error)}\n\n`)
process.exit(0)
}
module.exports = printErrorAndExit
+178
View File
@@ -0,0 +1,178 @@
const fs = require("fs-extra")
const chalk = require("chalk")
const request = require("request")
const ProgressBar = require("progress")
const ora = require("ora")
const configstore = require("configstore")
const packagejson = require("./package.json")
const { requestLogin } = require("./utils/loginHandler")
const configs = new configstore(packagejson.name, {
captainMachines: [],
apps: []
})
function sendFileToCaptain(
machineToDeploy,
zipFileFullPath,
appName,
gitHash,
branchToPush
) {
console.log("Uploading file to " + machineToDeploy.baseUrl)
const fileSize = fs.statSync(zipFileFullPath).size
const fileStream = fs.createReadStream(zipFileFullPath)
const barOpts = {
width: 20,
total: fileSize,
clear: true
}
const bar = new ProgressBar(
" uploading [:bar] :percent (ETA :etas)",
barOpts
)
fileStream.on("data", function(chunk) {
bar.tick(chunk.length)
})
let spinner
fileStream.on("end", function() {
console.log(" ")
console.log("This might take several minutes. PLEASE BE PATIENT...")
spinner = ora("Building your source code...").start()
spinner.color = "yellow"
})
const options = {
url:
machineToDeploy.baseUrl +
"/api/v1/user/appData/" +
appName +
"/?detached=1",
headers: {
"x-namespace": "captain",
"x-captain-auth": machineToDeploy.authToken
},
method: "POST",
formData: {
sourceFile: fileStream,
gitHash: gitHash
}
}
function callback(error, response, body) {
if (spinner) {
spinner.stop()
}
if (fs.pathExistsSync(zipFileFullPath) && !getSuppliedTarFile()) {
fs.removeSync(zipFileFullPath)
}
try {
if (!error && response.statusCode === 200) {
const data = JSON.parse(body)
if (data.status === 1106) {
// expired token
requestLogin(
machineToDeploy.name,
machineToDeploy.baseUrl,
function callback(machineToDeployNew) {
deployTo(machineToDeployNew, branchToPush, appName)
}
)
return
}
if (data.status !== 100 && data.status !== 101) {
throw new Error(JSON.stringify(data, null, 2))
}
savePropForDirectory(APP_NAME, appName)
savePropForDirectory(BRANCH_TO_PUSH, branchToPush)
savePropForDirectory(MACHINE_TO_DEPLOY, machineToDeploy)
if (data.status === 100) {
console.log(chalk.green("Deployed successfully: ") + appName)
console.log(" ")
} else if (data.status === 101) {
console.log(chalk.green("Building started: ") + appName)
console.log(" ")
startFetchingBuildLogs(machineToDeploy, appName)
}
return
}
if (error) {
throw new Error(error)
}
throw new Error(
response ? JSON.stringify(response, null, 2) : "Response NULL"
)
} catch (error) {
console.error(
chalk.red('\nSomething bad happened. Cannot deploy "' + appName + '"\n')
)
if (error.message) {
try {
var errorObj = JSON.parse(error.message)
if (errorObj.status) {
console.error(chalk.red("\nError code: " + errorObj.status))
console.error(
chalk.red("\nError message:\n\n " + errorObj.description)
)
} else {
throw new Error("NOT API ERROR")
}
} catch (ignoreError) {
console.error(chalk.red(error.message))
}
} else {
console.error(chalk.red(error))
}
console.log(" ")
}
}
request(options, callback)
}
// Sets default value for propType that is stored in a directory to propValue.
// Replaces saveAppForDirectory
function savePropForDirectory(propType, propValue) {
const apps = configs.get("apps")
for (let i = 0; i < apps.length; i++) {
const app = apps[i]
if (app.cwd === process.cwd()) {
app[propType] = propValue
configs.set("apps", apps)
return
}
}
apps.push({
cwd: process.cwd(),
[propType]: propValue
})
configs.set("apps", apps)
}
module.exports = sendFileToCaptain
+194
View File
@@ -0,0 +1,194 @@
const inquirer = require("inquirer")
function requestLogin(serverName, serverAddress, loginCallback) {
console.log("Your auth token is not valid anymore. Try to login again.")
const questions = [
{
type: "password",
name: "captainPassword",
message: "Please enter your password for " + serverAddress,
validate: function(value) {
if (value && value.trim()) {
return true
}
return "Please enter your password for " + serverAddress
}
}
]
inquirer.prompt(questions).then(function(passwordAnswers) {
var password = passwordAnswers.captainPassword
let options = {
url: serverAddress + "/api/v1/login",
headers: {
"x-namespace": "captain"
},
method: "POST",
form: {
password: password
}
}
function callback(error, response, body) {
try {
if (!error && response.statusCode === 200) {
let data = JSON.parse(body)
if (data.status !== 100) {
throw new Error(JSON.stringify(data, null, 2))
}
var newMachineToDeploy = updateAuthTokenInConfigStoreAndReturn(
data.token
)
loginCallback(newMachineToDeploy)
return
}
if (error) {
throw new Error(error)
}
throw new Error(
response ? JSON.stringify(response, null, 2) : "Response NULL"
)
} catch (error) {
if (error.message) {
try {
var errorObj = JSON.parse(error.message)
if (errorObj.status) {
console.error(chalk.red("\nError code: " + errorObj.status))
console.error(
chalk.red("\nError message:\n\n " + errorObj.description)
)
} else {
throw new Error("NOT API ERROR")
}
} catch (ignoreError) {
console.error(chalk.red(error.message))
}
} else {
console.error(chalk.red(error))
}
console.log(" ")
}
process.exit(0)
}
request(options, callback)
})
}
function requestLoginAuth(serverAddress, password, authCallback) {
let options = {
url: serverAddress + "/api/v1/login",
headers: {
"x-namespace": "captain"
},
method: "POST",
form: {
password: password
}
}
function callback(error, response, body) {
try {
if (!error && response.statusCode === 200) {
let data = JSON.parse(body)
if (data.status !== 100) {
throw new Error(JSON.stringify(data, null, 2))
}
authCallback(data.token)
return
}
if (error) {
throw new Error(error)
}
throw new Error(
response ? JSON.stringify(response, null, 2) : "Response NULL"
)
} catch (error) {
if (error.message) {
try {
var errorObj = JSON.parse(error.message)
if (errorObj.status) {
console.error(chalk.red("\nError code: " + errorObj.status))
console.error(
chalk.red("\nError message:\n\n " + errorObj.description)
)
} else {
throw new Error("NOT API ERROR")
}
} catch (ignoreError) {
console.error(chalk.red(error.message))
}
} else {
console.error(chalk.red(error))
}
console.log(" ")
}
process.exit(0)
}
request(options, callback)
}
function isAuthTokenValid(machineToDeploy, appName, isAuthTokenValidCallback) {
let options = {
url: machineToDeploy.baseUrl + "/api/v1/user/appDefinitions/",
headers: {
"x-namespace": "captain",
"x-captain-auth": machineToDeploy.authToken
},
method: "GET"
}
function callback(error, response, body) {
try {
if (!error && response.statusCode === 200) {
let data = JSON.parse(body)
if (data.status === 1106 || data.status === 1105) {
isAuthTokenValidCallback(false)
} else {
isAuthTokenValidCallback(true)
}
}
} catch (error) {
// This is just a sanity check. We only fire FALSE (i.e. expired) if we know it's expired or password is wrong
isAuthTokenValidCallback(true)
}
}
request(options, callback)
}
function updateAuthTokenInConfigStoreAndReturn(authToken) {
let machines = configs.get("captainMachines")
for (let i = 0; i < machines.length; i++) {
if (machines[i].name === serverName) {
var baseUrl = (machines[i].authToken = authToken)
configs.set("captainMachines", machines)
console.log("You are now logged back in to " + serverAddress)
return machines[i]
}
}
}
module.exports = {
requestLogin,
requestLoginAuth,
isAuthTokenValid
}
+10 -10
View File
@@ -1,24 +1,24 @@
const ora = require('ora');
const ora = require("ora")
function start(message) {
return ora(message).start()
return ora(message).start()
}
function stop(spinner) {
spinner.stop();
spinner.stop()
}
function succeed(spinner) {
spinner.succeed();
spinner.succeed()
}
function fail(spinner) {
spinner.fail();
spinner.fail()
}
module.exports = {
start: start,
stop: stop,
succeed: succeed,
fail: fail,
}
start: start,
stop: stop,
succeed: succeed,
fail: fail
}
+23
View File
@@ -0,0 +1,23 @@
const fs = require("fs-extra")
const printErrorAndExit = require("./errorHandler")
function validateIsGitRepository() {
if (!fs.pathExistsSync("./.git")) {
printErrorAndExit(
"**** ERROR: You are not in a git root directory. This command will only deploys the current directory ****"
)
}
}
function validateDefinitionFile() {
if (!fs.pathExistsSync("./captain-definition")) {
printErrorAndExit(
"**** ERROR: captain-definition file cannot be found. Please see docs! ****"
)
}
}
module.exports = {
validateIsGitRepository,
validateDefinitionFile
}