nodely-cli
Version:
Console client for Nodely Platform
377 lines (325 loc) • 9.2 kB
JavaScript
/**
* Created by Ivan Solovyev <support@nodely.me>
* Date: 05/29/2018
*
* Copyright @ Nodely, 2018
*/
const path = require('path');
const fs = require('fs');
const moment = require('moment');
const unzip = require('unzipper');
const walk = require('walk');
const checksum = require('checksum');
const {
getRepo, download, check, putFiles, deleteFiles, link,
} = require('./network');
const utils = require('./utils');
const collectControlSum = dir => new Promise((resolve) => {
const walker = walk.walk(dir,
{ filters: ['node_modules', '/.idea', '/.nodely'] });
const files = [];
walker.on('file', (root, fileStats, next) => {
const p = path.join(root, fileStats.name);
checksum.file(p, (err, sum) => {
files.push({
name: p.replace(dir, ''),
sum,
});
next();
});
});
walker.on('end', () => {
resolve(files);
});
});
const writeToConfig = (dir, name, data) => {
const fullDir = path.join(dir, '.nodely');
try {
fs.mkdirSync(fullDir);
} catch (e) {
// noop
}
return new Promise((resolve) => {
fs.writeFile(path.join(fullDir, name), data, () => resolve());
});
};
const doCheckout = (input) => {
const dest = path.join(process.cwd(), input.folder || input.domain);
try {
utils.verifyDir(dest);
} catch (e) {
utils.handleError(e);
return;
}
const spinner = utils.spinner();
spinner.message('Requesting repository...');
getRepo(input.domain).then((payload) => {
let dld;
try {
dld = JSON.parse(payload);
} catch (e) {
spinner.stop();
utils.handleError(e);
return;
}
// download given file
download(dld.file, spinner).then((zip) => {
spinner.message('Extracting repository...');
fs.createReadStream(zip)
.pipe(unzip.Extract({ path: dest }))
.on('close', () => {
spinner.message('Updating repository status...');
collectControlSum(dest).then((cs) => {
spinner.stop();
const status = {
date: moment().format(),
domain: input.domain,
files: cs,
};
writeToConfig(dest, 'status.json',
JSON.stringify(status))
.then(() => {
console.log('Checkout has been completed!');
console.log(`Downloaded ${cs.length} files.`);
});
});
})
.on('error', (e) => {
spinner.stop();
utils.handleError(e);
});
}).catch((e) => {
spinner.stop();
utils.handleError(e);
});
}).catch((e) => {
spinner.stop();
if (e.statusCode === 409) {
e.message = 'This project is linked to external repository. Checkout is not possible.\n\n'
+ 'Use nodely init domain.name to init Nodely.';
}
utils.handleError(e);
});
};
const doInit = (input) => {
if (utils.isNodelyDir(process.cwd())) {
utils.handleError(
{ message: 'The current directory is Nodely repository already' },
);
return;
}
const { domain } = input;
const spinner = utils.spinner();
spinner.message('Domain verification...');
// verify domain
check(domain).then(() => {
spinner.message('Repository initialization...');
collectControlSum(process.cwd()).then((files) => {
spinner.stop();
const status = {
date: moment().format(),
domain,
files,
};
writeToConfig(process.cwd(), 'status.json', JSON.stringify(status))
.then(() => {
console.log('Repository inited successfully!');
console.log(`Found ${files.length} files.`);
});
});
}).catch((e) => {
spinner.stop();
utils.handleError(e);
});
};
const readConfig = (dir, name) => {
const fullDir = path.join(dir, '.nodely');
const str = fs.readFileSync(path.join(fullDir, name));
return JSON.parse(str);
};
const collectChanges = (dir) => {
const changed = []; const added = []; const
deleted = [];
const cfg = readConfig(dir, 'status.json');
return new Promise((resolve) => {
collectControlSum(dir).then((cs) => {
cs.map((r) => {
let exists = null;
for (let i = 0; i < cfg.files.length; i += 1) {
const file = cfg.files[i];
if (file.name === r.name) {
exists = file.sum === r.sum;
break;
}
}
if (exists !== null && !exists) {
changed.push(r);
}
if (exists == null) {
added.push(r);
}
return r;
});
// look for deleted files
cfg.files.map((f) => {
const filtered = cs.filter(item => item.name === f.name);
if (filtered.length === 0) {
deleted.push(f);
}
return f;
});
resolve({ changed, added, deleted });
});
});
};
const printStatus = ({ changed, added, deleted }) => {
if (changed.length === 0 && added.length === 0 && deleted.length === 0) {
console.log('Status: up to date');
} else {
console.log('Status:');
}
if (changed.length > 0) {
console.log(` - Changed: ${changed.length} files`);
changed.map((f) => {
console.log(` ${f.name.replace('/', '')}`);
return f;
});
}
if (added.length > 0) {
console.log(` - Added: ${added.length} files`);
added.map((f) => {
console.log(` ${f.name.replace('/', '')}`);
return f;
});
}
if (deleted.length > 0) {
console.log(` - Deleted: ${deleted.length} files`);
deleted.map((f) => {
console.log(` ${f.name.replace('/', '')}`);
return f;
});
}
};
const doPush = () => {
if (!utils.isNodelyDir(process.cwd())) {
utils.handleError(
{ message: 'The current directory is not Nodely repository' },
);
return;
}
const cfg = readConfig(process.cwd(), 'status.json');
if (cfg.link) {
utils.handleError(
{ message: 'This project is linked to external repository. Push command is disabled.' },
);
return;
}
collectChanges(process.cwd()).then(({ changed, added, deleted }) => {
printStatus({ changed, added, deleted });
if (added.length === 0
&& changed.length === 0
&& deleted.length === 0) {
return;
}
const spinner = utils.spinner();
spinner.message('Pushing changes...');
let files2push = [];
changed.concat(added).map((item) => {
const fullPath = path.join(process.cwd(), item.name);
const data = fs.readFileSync(fullPath, 'base64');
files2push.push({
name: path.basename(item.name),
content: data,
path: item.name,
encoding: 'base64',
});
return item;
});
const complete = () => {
spinner.message('Updating repository status...');
collectControlSum(process.cwd()).then((cs) => {
spinner.stop();
const status = {
date: moment().format(),
domain: cfg.domain,
files: cs,
};
writeToConfig(process.cwd(), 'status.json',
JSON.stringify(status))
.then(() => {
console.log(
'Files have been pushed. It\'s time to publish them!',
);
});
});
};
putFiles(cfg.domain, files2push).then(() => {
files2push = [];
if (deleted.length > 0) {
deleted.map((item) => {
files2push.push({
name: path.basename(item.name),
path: item.name,
});
return item;
});
deleteFiles(cfg.domain, files2push)
.then(() => complete())
.catch((e) => {
spinner.stop();
utils.handleError(e);
});
} else {
complete();
}
}).catch((e) => {
spinner.stop();
utils.handleError(e);
});
});
};
const getStatus = () => {
if (!utils.isNodelyDir(process.cwd())) {
utils.handleError(
{ message: 'The current directory is not Nodely repository' },
);
return;
}
const cfg = readConfig(process.cwd(), 'status.json');
console.log('Domain: ', cfg.domain);
console.log('Created: ', moment(cfg.date).format('L LT'));
console.log('----------------------');
collectChanges(process.cwd()).then(printStatus);
};
const doLink = ({ repo }) => {
if (!utils.isNodelyDir(process.cwd())) {
utils.handleError(
{ message: 'The current directory is not Nodely repository' },
);
return;
}
const cfg = readConfig(process.cwd(), 'status.json');
if (cfg.link) {
utils.handleError(
{ message: 'This project is already linked.' },
);
return;
}
const spinner = utils.spinner();
spinner.message(`Linking of ${repo} is in progress...`);
link(cfg.domain, { repo }).then(() => {
spinner.stop();
cfg.link = repo;
writeToConfig(process.cwd(), 'status.json',
JSON.stringify(cfg))
.then(() => {
console.log('Repository has been linked successfully!'.green);
});
}).catch((e) => {
spinner.stop();
utils.handleError(e);
});
};
module.exports = {
doCheckout, doPush, getStatus, doInit, readConfig, doLink,
};