cpitkin/redirect-test

View on GitHub
index.js

Summary

Maintainability
A
1 hr
Test Coverage
A
98%
#!/usr/bin/env node

const path = require('path');
const fs = require('fs');
const URL = require('url');
const csv = require('csv');
const request = require('request');
const _ = require('underscore');
const chalk = require('chalk');
const async = require('async');
const writeCSV = require('write-csv');
const isAbsoluteUrl = require('is-absolute-url');
const ora = require('ora');
const program = require('commander');
const pkgVersion = require('./package.json').version;

let fileName;
let siteUrl;
const options = {
  method: 'get',
  headers: {
    'User-Agent': `Redirect Tester v${pkgVersion}`,
  },
  followRedirect: false,
}
const errorCodes = /4[0-9][0-3|5-9]|5[0-9][0-9]/g;
const updatedCSV = [];
const error = chalk.bold.red;
const good = chalk.bold.green;
const concurrency = program.number || 5;
const spinner = ora({ spinner: 'dots2', text: `Making HTTP(S) requests with concurrency ${concurrency}` });

const q = async.queue((urls, cb) => {
  let update;
  options.url = siteUrl + urls[0];
  request(options, (err, res) => {
    if (err) {
      cb(`Error: ${err}`, null);
    } else {
      if (res.statusCode === 301) {
        const locPath = URL.parse(res.headers.location).pathname;
        if (locPath !== urls[1]) {
          update = {
            old: urls[0],
            new: urls[1],
            status_code: res.statusCode,
            actual_url: locPath,
          };
        }
      } else {
        update = {
          old: urls[0],
          new: urls[1],
          status_code: res.statusCode,
          actual_url: '',
        };
      }
      if (update) {
        updatedCSV.push(update);
      }
      if (res.statusCode.toString().match(errorCodes)) {
        cb(`Error: Server returned a ${res.statusCode} status code.`, null);
      } else {
        cb(null, res);
      }
    }
  });
}, concurrency);

q.error = (err) => {
  spinner.fail(error(err));
  q.kill();
};

q.drain = () => {
  if (_.isEmpty(updatedCSV) && !program.quiet) {
    spinner.succeed(good('All links look good.'));
    spinner.succeed(good('No errors so nothing written to the csv file'));
  } else if (!program.quiet) {
    spinner.fail(error('See errors in the csv file'));
    writeCSV(program.csv, updatedCSV);
  } else if (program.quiet) {
    spinner.stop();
    writeCSV(program.csv, updatedCSV);
  }
};

function parseCsv(contents) {
  csv.parse(contents, (err, data) => {
    if (err) {
      console.error(error('Error: It looks like your file is either not a csv or has some bad formatting.'));
      program.help();
    } else {
      q.push(data);
      spinner.start();
    }
  });
}

function readFile(file) {
  fs.readFile(file, 'utf8', (err, contents) => {
    if (err) {
      console.error(error(err[0]));
    } else {
      parseCsv(contents);
    }
  });
}

program
  .version(pkgVersion)
  .usage('<file> <url> [options]')
  .description('Check a list of new URLs for 301 status code and path for correctness.')
  .option('-c, --csv <file>', 'Save the results to a csv file. Default: ./results.csv')
  .option('-q, --quiet', 'Don\'t output error results to the terminal.')
  .option('-n, --number <integer>', 'Number of concurrent requests. Default: 5')
  .option('-a, --auth <username:password>', 'The username and password for basic auth.')
  .arguments('<file> <url>')
  .action((file, url) => {
    fileName = path.resolve(file.trim());
    siteUrl = url.trim();
  });

program.parse(process.argv);

if (!isAbsoluteUrl(siteUrl)) {
  console.error((error('Error: URL must be an absolute path. eg. https://www.example.com')));
  program.help();
} else if (!fs.existsSync(fileName)) {
  console.error((error('Error: File or directory doesn\'t exist.')));
  program.help();
} else {
  readFile(fileName);
}

program.csv = program.csv || './results.csv';

program.csv = path.resolve(program.csv.trim());

if (program.auth) {
  const authString = program.auth.split(':');
  options.auth = {};
  options.auth.user = authString[0];
  options.auth.pass = authString[1];
}