tenon-io/tenon-cli

View on GitHub
tenon-cli.js

Summary

Maintainability
D
2 days
Test Coverage
#!/usr/bin/env node

import fs from 'fs';
import tenonNode from 'tenon-node';
import reporters from 'tenon-reporters';
import getStdin from 'get-stdin';
import { parseCommand } from './';

// This line takes any piped HTML that will be sent to Tenon
getStdin().then(pipedHTML => {
  // Call the module that holds the Commander.js logic for CLI options and commands
  const program = parseCommand(process.argv);
  // Check if there are more than 1 non-option arguments
  // If there are, there are too many and the URL to test cannot be found
  if (program.commands[0].args.length > 1) {
    console.error('You have supplied too many arguments, for help type \'tenon --help\'');
    process.exit(1);
  }

  // This finds the URL command, which should be the only non-option argument
  const command = program.commands[0].args[0];

  // Load the config file for options if it exists
  let configuration = {};
  if (program.commands[0].config) {
    try {
      configuration = fs.readFileSync(program.commands[0].config, 'utf8');
      configuration = JSON.parse(configuration);
    } catch (e) {
      // The json could not be parsed or read
      console.error('Failed to parse or read configuration file...');
      console.error(e.message);
      process.exit(1);
    }
  }

  // Merge the two sources of options
  const allOptions = Object.assign({},
    configuration,
    program.commands[0]
  );

  // Defaults, can't do this in index.js because of the config.js file
  // There is no way to differenciate between default and user entered which is the problem
  const defaults = {
    endpoint: 'https://tenon.io/api/',
    format: 'json',
  };

  Object.keys(defaults).forEach((key) => {
    if (!allOptions[key]) {
      allOptions[key] = defaults[key];
    }
  });

  if (process.env.TENON_API_KEY && !allOptions.key) {
    allOptions.key = process.env.TENON_API_KEY;
  }

  // Options that can be sent to Tenon
  const tenonOptions = {
    waitFor: 'waitFor',
    level: 'level',
    certainty: 'certainty',
    importance: 'importance',
    priority: 'priority',
    store: 'store',
    docId: 'docID',
    fragment: 'fragment',
    projectId: 'projectID',
    userAgent: 'uaString',
    viewPortHeight: 'viewPortHeight',
    viewPortWidth: 'viewPortWidth',
  };

  const options = {};
  // Generate the array of options for Tenon
  Object.keys(tenonOptions).forEach((key) => {
    const value = tenonOptions[key];
    if (allOptions[key]) {
      if (key === 'store' || key === 'fragment') {
        allOptions[key] = +allOptions[key];
      }
      options[value] = allOptions[key];
    }
  });

  // Delete all of the options that haven't been set
  Object.keys(options).forEach((key) => {
    if (options[key] === undefined) {
      delete options[key];
    }
  });

  // Check and make sure all of the required fields have been entered
  const requiredFields = ['key'];
  requiredFields.forEach((field) => {
    if (!allOptions[field]) {
      console.log('\'' + field + '\' is required');
      process.exit(1);
    }
  });

  // Now we determine what is getting analyzed (input file, stdin, or url)
  // url > stdin > file
  let source;
  if (command) {
    source = command;
  } else if (pipedHTML) {
    source = pipedHTML;
  } else if (allOptions.in) {
    try {
      source = fs.readFileSync(allOptions.in, 'utf8');
    } catch (e) {
      // Failed to read input file
      console.error('Failed to read input file, ' + allOptions.in);
      process.exit(1);
    }
  } else {
    // No URL, stdin or file specified
    console.error('No input HTML specified for analysis');
    process.exit(1);
  }

  // Initialize the Tenon API object
  const tenonApi = new tenonNode({
    key: allOptions.key,
    endPoint: allOptions.endpoint, // or your private tenon instance
  });

  const writeResultFile = (result, file) => {
    try {
      fs.writeFileSync(file, result);
      console.log(`Analysis complete, report at ${file}`);
    } catch (e) {
      console.error('Failed to write file...');
      console.error(e.message);
      process.exit(1);
    }
  };

  const parseFormat = (json) => {
    switch (allOptions.format) {
      case 'json':
        // Tenon returns resuls in JSON, so it's already formatted correctly
        return new Promise((resolve) => {
          resolve(JSON.stringify(json, null, '\t'));
        });
      case 'csv':
        return new Promise((resolve) => {
          reporters.CSV(json, (err, result) => {
            if (err) {
              console.error('Failed to parse Tenon response into CSV format');
              console.error(err);
              process.exit(1);
            } else {
              resolve(result);
            }
          });
        });
      case 'html':
        return new Promise((resolve) => {
          reporters.HTML(json, (err, result) => {
            if (err) {
              console.error('Failed to parse Tenon response into HTML format');
              console.error(err);
              process.exit(1);
            } else {
              resolve(result);
            }
          });
        });
      case 'xunit':
        return new Promise((resolve) => {
          reporters.XUnit(json, (err, result) => {
            if (err) {
              console.error('Failed to parse Tenon response into XUnit format');
              console.error(err);
              process.exit(1);
            } else {
              resolve(result);
            }
          });
        });
      default:
        console.error('Error occured, format not found');
        process.exit(1);
        break;
    }
  };

  // Call the Tenon API for analysis
  console.log('Sending request to Tenon...');

  tenonApi.analyze(source, options, (err, result) => {
    if (err) {
      console.error(err);
    } else {
      if (result.status >= 400) {
        console.error('Tenon reported an error:');
        console.error(JSON.stringify(result, null, '\t'));
        process.exit(1);
      }

      console.log('Tenon analysis completed.');
      parseFormat(result).then((formattedResult) => {
        if (allOptions.out) {
          console.log('Writing results to file...');
          try {
            writeResultFile(formattedResult, allOptions.out);
          } catch (e) {
            console.error('Failed to write result to file...');
            console.error(e.message);
            process.exit(1);
          }
        } else {
          console.log('Writing results to console...');
          process.stdout.write(formattedResult);
        }
      });
    }
  });
});