You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

254 lines
6.2 KiB

#!/usr/bin/env node
// Packages
const fs = require('fs-promise');
const minimist = require('minimist');
const chalk = require('chalk');
const ms = require('ms');
const printf = require('printf');
require('epipebomb')();
const supportsColor = require('supports-color');
// Ours
const Now = require('../lib');
const login = require('../lib/login');
const cfg = require('../lib/cfg');
const { handleError, error } = require('../lib/error');
const logo = require('../lib/utils/output/logo');
const argv = minimist(process.argv.slice(2), {
string: ['config', 'token'],
boolean: ['help', 'debug', 'all'],
alias: {
help: 'h',
config: 'c',
debug: 'd',
token: 't'
}
});
const help = () => {
console.log(
`
${chalk.bold(`${logo} now list`)} [app]
${chalk.dim('Options:')}
-h, --help Output usage information
-c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline('FILE')} Config file
-d, --debug Debug mode [off]
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline('TOKEN')} Login token
${chalk.dim('Examples:')}
${chalk.gray('–')} List all deployments
${chalk.cyan('$ now ls')}
${chalk.gray('–')} List all deployments for the app ${chalk.dim('`my-app`')}
${chalk.cyan('$ now ls my-app')}
${chalk.dim('Alias:')} ls
`
);
};
if (argv.help) {
help();
process.exit(0);
}
const app = argv._[0];
// Options
const debug = argv.debug;
const apiUrl = argv.url || 'https://api.zeit.co';
if (argv.config) {
cfg.setConfigFile(argv.config);
}
Promise.resolve().then(async () => {
const config = await cfg.read();
let token;
try {
token = argv.token || config.token || (await login(apiUrl));
} catch (err) {
error(`Authentication error – ${err.message}`);
process.exit(1);
}
try {
await list({ token, config });
} catch (err) {
error(`Unknown error: ${err}\n${err.stack}`);
process.exit(1);
}
});
async function list({ token, config: { currentTeam, user } }) {
const now = new Now({ apiUrl, token, debug, currentTeam });
const start = new Date();
if (argv.all && !app) {
console.log('> You must define an app when using `--all`');
process.exit(1);
}
let deployments;
try {
deployments = await now.list(app);
} catch (err) {
handleError(err);
process.exit(1);
}
if (!deployments || (Array.isArray(deployments) && deployments.length <= 0)) {
const match = await now.findDeployment(app);
if (match !== null && typeof match !== 'undefined') {
deployments = Array.of(match);
}
}
if (!deployments || (Array.isArray(deployments) && deployments.length <= 0)) {
const aliases = await now.listAliases();
const item = aliases.find(e => e.uid === app || e.alias === app);
if (item) {
const match = await now.findDeployment(item.deploymentId);
if (match !== null && typeof match !== 'undefined') {
deployments = Array.of(match);
}
}
}
now.close();
const apps = new Map();
if (argv.all) {
await Promise.all(
deployments.map(async ({ uid }, i) => {
deployments[i].instances = await now.listInstances(uid);
})
);
}
for (const dep of deployments) {
const deps = apps.get(dep.name) || [];
apps.set(dep.name, deps.concat(dep));
}
const sorted = await sort([...apps]);
const urlLength =
deployments.reduce((acc, i) => {
return Math.max(acc, (i.url && i.url.length) || 0);
}, 0) + 5;
const timeNow = new Date();
console.log(
`> ${deployments.length} deployment${deployments.length === 1 ? '' : 's'} found under ${chalk.bold((currentTeam && currentTeam.slug) || user.username || user.email)} ${chalk.grey('[' + ms(timeNow - start) + ']')}`
);
let shouldShowAllInfo = false;
for (const app of apps) {
shouldShowAllInfo =
app[1].length > 5 ||
app.find(depl => {
return depl.scale && depl.scale.current > 1;
});
if (shouldShowAllInfo) {
break;
}
}
if (!argv.all && shouldShowAllInfo) {
console.log(
`> To expand the list and see instances run ${chalk.cyan('`now ls --all [app]`')}`
);
}
console.log();
sorted.forEach(([name, deps]) => {
const listedDeployments = argv.all ? deps : deps.slice(0, 5);
console.log(
`${chalk.bold(name)} ${chalk.gray('(' + listedDeployments.length + ' of ' + deps.length + ' total)')}`
);
const urlSpec = `%-${urlLength}s`;
console.log(
printf(
` ${chalk.grey(urlSpec + ' %8s %-16s %8s')}`,
'url',
'inst #',
'state',
'age'
)
);
listedDeployments.forEach(dep => {
let state = dep.state;
let extraSpaceForState = 0;
if (state === null || typeof state === 'undefined') {
state = 'DEPLOYMENT_ERROR';
}
if (/ERROR/.test(state)) {
state = chalk.red(state);
extraSpaceForState = 10;
} else if (state === 'FROZEN') {
state = chalk.grey(state);
extraSpaceForState = 10;
}
let spec;
if (supportsColor) {
spec = ` %-${urlLength + 10}s %8s %-${extraSpaceForState + 16}s %8s`;
} else {
spec = ` %-${urlLength + 1}s %8s %-${16}s %8s`;
}
console.log(
printf(
spec,
chalk.underline(dep.url),
dep.scale.current,
state,
ms(timeNow - dep.created)
)
);
if (Array.isArray(dep.instances) && dep.instances.length > 0) {
dep.instances.forEach(i => {
console.log(
printf(` %-${urlLength + 10}s`, ` - ${chalk.underline(i.url)}`)
);
});
console.log();
}
});
console.log();
});
}
async function sort(apps) {
let pkg;
try {
const json = await fs.readFile('package.json');
pkg = JSON.parse(json);
} catch (err) {
pkg = {};
}
return apps
.map(([name, deps]) => {
deps = deps.slice().sort((a, b) => {
return b.created - a.created;
});
return [name, deps];
})
.sort(([nameA, depsA], [nameB, depsB]) => {
if (pkg.name === nameA) {
return -1;
}
if (pkg.name === nameB) {
return 1;
}
return depsB[0].created - depsA[0].created;
});
}