UNPKG

nodely-cli

Version:

Console client for Nodely Platform

377 lines (325 loc) 9.2 kB
/** * 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, };