Browse Source

add auto updater with support for timeout, exit handler

master
Guillermo Rauch 9 years ago
parent
commit
d4c5ebc3cd
  1. 36
      bin/now
  2. 115
      lib/check-update.js
  3. 8
      lib/index.js

36
bin/now

@ -2,14 +2,14 @@
import program from 'commander';
import Progress from 'progress';
import copy from '../lib/copy';
import * as cfg from '../lib/cfg';
import { resolve } from 'path';
import login from '../lib/login';
import checkUpdate from '../lib/check-update';
import bytes from 'bytes';
import chalk from 'chalk';
import Now from '../lib';
import fs from 'fs';
import ms from 'ms';
import os from 'os';
program
.usage('[options]')
@ -29,38 +29,37 @@ if (path) {
path = process.cwd();
}
let config;
const debug = !!program.debug;
const clipboard = !program.noClipboard;
const config = cfg.read();
const update = checkUpdate({ debug });
const exit = (code) => update.then(() => process.exit(code));
try {
config = fs.readFileSync(resolve(os.homedir(), '.now.json'), 'utf8');
config = JSON.parse(config);
} catch (err) {}
if (!config || !config.token || program.login) {
if (!config.token || program.login) {
login()
.then((token) => {
if (program.login) {
console.log('> Logged in successfully. Token saved in ~/.now.json');
process.exit(0);
exit(0);
} else {
sync(token).catch((err) => {
error(`Unknown error: ${err.stack}`);
exit(1);
});
}
})
.catch((e) => {
error(`Authentication error – ${e.message}`);
process.exit(1);
exit(1);
});
} else {
sync(config.token).catch((err) => {
error(`Unknown error: ${err.stack}`);
exit(1);
});
}
async function sync (token) {
const debug = !!program.debug;
const clipboard = !program.noClipboard;
const start = Date.now();
console.log(`> Deploying "${path}"`);
@ -71,6 +70,7 @@ async function sync (token) {
await now.create(path, { forceNew: program.force });
} catch (err) {
handleError(err);
return;
}
const { url } = now;
@ -92,7 +92,7 @@ async function sync (token) {
const elapsed_u = ms(new Date() - start_u);
console.log(`> Sync complete (${bytes(now.syncAmount)}) [${elapsed_u}] `);
now.close();
process.exit(0);
exit(0);
};
if (now.syncAmount) {
@ -118,24 +118,26 @@ async function sync (token) {
now.on('error', (err) => {
error('Upload failed');
handleError(err);
process.exit(1);
exit(1);
});
} else {
console.log('> Sync complete (cached)');
now.close();
process.exit(0);
exit(0);
}
}
function handleError (err) {
if (403 === err.status) {
error('Authentication error. Run `now -L` or `now --login` to log-in again.');
} else if (err.userError) {
error(err.message);
} else if (500 === err.status) {
error('Unexpected server error. Please retry.');
} else {
error(`Unexpected error. Please try later. (${err.message})`);
}
process.exit(1);
exit(1);
}
function error (err) {

115
lib/check-update.js

@ -0,0 +1,115 @@
import ms from 'ms';
import * as cfg from './cfg';
import pkg from '../../package'; // relative to `build/` :\
import request from 'https';
import chalk from 'chalk';
const TEN_MINUTES = ms('1s');
/**
* Configures auto updates.
* Sets up a `exit` listener to report them.
*/
export default function checkUpdate (opts = {}) {
let updateData;
const update = check(opts).then((data) => {
updateData = data;
// forces the `exit` event upon Ctrl + C
process.on('SIGINT', () => {
// clean up output after ^C
process.stdout.write('\n');
process.exit(1);
});
}, (err) => console.error(err.stack));
process.on('exit', (code) => {
if (updateData) {
const { current, latest, at } = updateData;
const ago = ms(Date.now() - at);
console.log(`> ${chalk.white.bgRed('UPDATE NEEDED')} ` +
`Current: ${current}` +
`Latest ${chalk.bold(latest)} (released ${ago} ago)`);
}
});
return update;
}
function check ({ debug = false, debounce = TEN_MINUTES, timeout = 1000 }) {
return new Promise((resolve, reject) => {
const { _last_update_check } = cfg.read();
if (_last_update_check && _last_update_check + debounce > Date.now()) {
if (debug) {
const ago = ms(Date.now() - _last_update_check);
console.log(`> [debug] Skipping update. Last check ${ago} ago.`);
}
return;
}
if (debug) console.log(`> [debug] Checking for updates. Timeout in ${ms(timeout)}.`);
let timer;
let req = request.get('https://registry.npmjs.org/now', (res) => {
if (200 !== res.statusCode) {
if (debug) console.log(`> [debug] Update check error. NPM ${res.statusCode}.`);
resolve(false);
return;
}
res.resume();
const bufs = [];
res.on('data', (buf) => bufs.push(buf));
res.on('error', (err) => {
if (debug) console.log(`> [debug] Update check error: ${err.message}.`);
resolve(false);
});
res.on('end', () => {
clearTimeout(timer);
const buf = Buffer.concat(bufs);
let data;
try {
data = JSON.parse(buf.toString('utf8'));
} catch (err) {
if (debug) console.log(`> [debug] Update check JSON parse error: ${err.message}.`);
resolve(false);
return;
}
const { latest } = data['dist-tags'];
const current = pkg.version;
if (latest !== pkg.version) {
if (debug) console.log(`> [debug] Needs update. Current ${current}, latest ${latest}`);
resolve({
latest,
current,
at: new Date(data.time[latest])
});
} else {
if (debug) console.log(`> [debug] Up to date (${pkg.version}).`);
resolve(false);
}
cfg.merge({ _last_update_check: Date.now() });
});
})
.on('error', (err) => {
if (debug) console.log(`> [debug] Update check error: ${err.message}.`);
resolve(false);
});
timer = setTimeout(() => {
if (debug) console.log(`> [debug] Aborting update check after ${ms(timeout)}.`);
req.abort();
resolve(false);
}, timeout);
});
}

8
lib/index.js

@ -34,12 +34,16 @@ export default class Now extends EventEmitter {
pkg = await readFile(resolve(path, 'package.json'));
pkg = JSON.parse(pkg);
} catch (err) {
throw new Error(`Failed to read JSON in "${path}/package.json"`);
const e = Error(`Failed to read JSON in "${path}/package.json"`);
e.userError = true;
throw e;
}
if (!pkg.scripts || !pkg.scripts.start) {
throw new Error('Missing `start` script in `package.json`. ' +
const e = Error('Missing `start` script in `package.json`. ' +
'See: https://docs.npmjs.com/cli/start.');
e.userError = true;
throw e;
}
if (this._debug) console.time('> [debug] Getting files');

Loading…
Cancel
Save