#!/usr/bin/env node // Packages const chalk = require("chalk"); const minimist = require("minimist"); const ms = require("ms"); const table = require("text-table"); // Ours const cfg = require("../lib/cfg"); const DomainRecords = require("../lib/domain-records"); const indent = require("../lib/indent"); const login = require("../lib/login"); const strlen = require("../lib/strlen"); const { handleError, error } = require("../lib/error"); const exit = require("../lib/utils/exit"); const logo = require("../lib/utils/output/logo"); const argv = minimist(process.argv.slice(2), { string: ["config"], boolean: ["help", "debug"], alias: { help: "h", config: "c", debug: "d", token: "t" } }); const subcommand = argv._[0]; // options const help = () => { console.log( ` ${chalk.bold(`${logo} now dns ls`)} [domain] ${chalk.bold(`${logo} now dns add`)} ${chalk.bold(`${logo} now dns add`)} MX ${chalk.bold(`${logo} now dns add`)} SRV ${chalk.bold(`${logo} now dns rm`)} ${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 your DNS records ${chalk.cyan("$ now dns ls")} ${chalk.gray("–")} Add an A record for a subdomain ${chalk.cyan("$ now dns add A ")} ${chalk.cyan("$ now dns add zeit.rocks api A 198.51.100.100")} ${chalk.gray("–")} Add an MX record (@ as a name refers to the domain) ${chalk.cyan("$ now dns add @ MX ")} ${chalk.cyan("$ now dns add zeit.rocks @ MX mail.zeit.rocks 10")} ` ); }; // options const debug = argv.debug; const apiUrl = argv.url || "https://api.zeit.co"; if (argv.config) { cfg.setConfigFile(argv.config); } if (argv.help || !subcommand) { help(); exit(0); } else { const config = cfg.read(); Promise.resolve(argv.token || config.token || login(apiUrl)) .then(async token => { try { await run(token); } catch (err) { handleError(err); exit(1); } }) .catch(e => { error(`Authentication error – ${e.message}`); exit(1); }); } async function run(token) { const domainRecords = new DomainRecords(apiUrl, token, { debug }); const args = argv._.slice(1); const start = Date.now(); if (subcommand === "ls" || subcommand === "list") { if (args.length > 1) { error( `Invalid number of arguments. Usage: ${chalk.cyan("`now dns ls [domain]`")}` ); return exit(1); } const elapsed = ms(new Date() - start); const res = await domainRecords.ls(args[0]); const text = []; let count = 0; res.forEach((records, domain) => { count += records.length; if (records.length > 0) { const cur = Date.now(); const header = [ ["", "id", "name", "type", "value", "aux", "created"].map(s => chalk.dim(s)) ]; const out = table( header.concat( records.map(record => { const time = chalk.gray( ms(cur - new Date(Number(record.created))) + " ago" ); return [ "", record.id, record.name, record.type, record.value, record.mxPriority || record.priority || "", time ]; }) ), { align: ["l", "r", "l", "l", "l", "l"], hsep: " ".repeat(2), stringLength: strlen } ); text.push(`\n\n${chalk.bold(domain)}\n${indent(out, 2)}`); } }); console.log( `> ${count} record${count === 1 ? "" : "s"} found ${chalk.gray(`[${elapsed}]`)}` ); console.log(text.join("")); } else if (subcommand === "add") { const param = parseAddArgs(args); if (!param) { error( `Invalid number of arguments. See: ${chalk.cyan("`now dns --help`")} for usage.` ); return exit(1); } const record = await domainRecords.create(param.domain, param.data); const elapsed = ms(new Date() - start); console.log( `${chalk.cyan("> Success!")} A new DNS record for domain ${chalk.bold(param.domain)} ${chalk.gray(`(${record.uid})`)} created ${chalk.gray(`[${elapsed}]`)}` ); } else if (subcommand === "rm" || subcommand === "remove") { if (args.length !== 1) { error( `Invalid number of arguments. Usage: ${chalk.cyan("`now dns rm `")}` ); return exit(1); } const record = await domainRecords.getRecord(args[0]); if (!record) { error("DNS record not found"); return exit(1); } const yes = await readConfirmation( record, "The following record will be removed permanently\n" ); if (!yes) { error("User abort"); return exit(0); } await domainRecords.delete(record.domain, record.id); const elapsed = ms(new Date() - start); console.log( `${chalk.cyan("> Success!")} Record ${chalk.gray(`${record.id}`)} removed ${chalk.gray(`[${elapsed}]`)}` ); } else { error("Please specify a valid subcommand: ls | add | rm"); help(); exit(1); } return domainRecords.close(); } process.on("uncaughtException", err => { handleError(err); exit(1); }); function parseAddArgs(args) { if (!args || args.length < 4) { return null; } const domain = args[0]; const name = args[1] === "@" ? "" : args[1]; const type = args[2]; const value = args[3]; if (!(domain && typeof name === "string" && type)) { return null; } if (type === "MX") { if (args.length !== 5) { return null; } return { domain, data: { name, type, value, mxPriority: args[4] } }; } else if (type === "SRV") { if (args.length !== 7) { return null; } return { domain, data: { name, type, srv: { priority: value, weight: args[4], port: args[5], target: args[6] } } }; } if (args.length !== 4) { return null; } return { domain, data: { name, type, value } }; } function readConfirmation(record, msg) { return new Promise(resolve => { const time = chalk.gray( ms(new Date() - new Date(Number(record.created))) + " ago" ); const tbl = table( [ [ record.id, chalk.bold( `${record.name.length > 0 ? record.name + "." : ""}${record.domain} ${record.type} ${record.value} ${record.mxPriority ? record.mxPriority : ""}` ), time ] ], { align: ["l", "r", "l"], hsep: " ".repeat(6) } ); process.stdout.write(`> ${msg}`); process.stdout.write(" " + tbl + "\n"); process.stdout.write( `${chalk.bold.red("> Are you sure?")} ${chalk.gray("[y/N] ")}` ); process.stdin .on("data", d => { process.stdin.pause(); resolve(d.toString().trim().toLowerCase() === "y"); }) .resume(); }); }