utils/utilities.js

/**
 * Exports an object, which contains utility methods for validation and managing files
 * @module utilities
 */

const fs = require("fs");
const path = require("path");
const logger = require("./logger")("dev");
/**
 * Looks inside of a directory, then recursively searches for the paths of all files nested inside of the given directory. Returns an array of file paths.
 * @param {String} dir a path to a directory
 * @returns {Array} an array of file paths
 * @example 
 * const path = require("path");
 * const { getFilePaths } = require('./utilities')
 * const dirPath = path.join(__dirname, "..", "..", "dirName");
 * const filePaths = getFilePaths(dirPath);
 * // [
 * //  '/path/to/file1.js',
 * //  '/path/to/file2.jpg',
 * //  '/path/to/file3.md'
 * // ]
 */
const getFilePaths = (dir) => {
  const filePaths = [];

  const getPaths = (dir) => {
    fs.readdirSync(dir).forEach((name) => {
      const filePath = path.join(dir, name);

      const stat = fs.statSync(filePath);

      if (stat.isFile()) {
        filePaths.push(filePath);
      } else if (stat.isDirectory()) {
        getPaths(filePath);
      }
    });
  };

  getPaths(dir);
  return filePaths;
};

/**
 * Takes a string and outputs a string for use as a content-type value.
 * @param {String} extension a file's extension name
 * @returns {String} a content type in the form of "text/extension". Returns undefined if it is an invalid extension
 */
const getContentType = (extension) => {
  let imageExtensions = ["gif", "png", "jpeg"];
  let textExtensions = ["css", "html", "js"];

  if (imageExtensions.includes(extension)) {
    return `image/${extension}`;
  } else if (textExtensions.includes(extension)) {
    if (extension === "js") {
      extension = "javascript";
    }

    return `text/${extension}`;
  }
};

/**
 * Given data, and a file path, asynchronously creates a new file with that data inserted.  Will overwrite the file if it already exists.
 * @param {String} data the data that you want in the file
 * @param {String} filePath the path and filename that you want to write to
 * @returns {undefined}
 */
const createFile = async (data, filePath) => {
  const fileName = filePath.split("/").slice(-1)[0];

  try {
    await fs.promises.writeFile(filePath, data);
    logger.debugSuccess(`Successfully created ${fileName}`);
  } catch (err) {
    logger.debugError("Error", err);
    throw new Error(err);
  }
};

/**
 * Asynchronously reads the contents of a file, and returns the contents of that file
 * @param {String} filePath a path and filename
 * @returns {String} returns text from the file in utf8 format
 */
const readFile = async (filePath) => {
  const fileName = filePath.split("/").slice(-1)[0];

  try {
    const data = await fs.promises.readFile(filePath, "utf8");
    logger.debugSuccess(`Successfully read ${fileName}`);
    return data;
  } catch (err) {
    logger.debugError("Error", err);
    throw new Error(err);
  }
};

/**
 * Checks if a given file exists
 * @param {String} filePath a path and file name
 * @returns {Boolean}
 */
const fileExists = (filePath) => {
  try {
    return fs.existsSync(filePath);
  } catch (err) {
    logger.debugError("Error", err);
    throw new Error(err);
  }
}

/**
 * Check if user executed `beekeeper init` before any other commands
 * @param {String} answersFilePath a path and file name
 * @returns {Boolean}
 */
const validateInitRan = async (answersFilePath) => {
  let fileFound = await fileExists(answersFilePath);
  
  if (!fileFound) {
    logger.error("Error: Run 'beekeeper init' first.");
    return false;
  }

  return true;
}

/**
 * // Check if user provided a valid profile name as part of command
 * @param {String} profileName the profile name given during bookeeper init
 * @param {Object} profiles all of the data stored in user-answers.json
 * @param {String} command a command given to the CLI
 * @returns {Boolean}
 */
const validateProfileName = (profileName, profiles, command) => {
  if (!profileName) {
    logger.error(`Error: Please provide a profile name 'beekeeper ${command} [PROFILE_NAME]'.`);
    return false;
  }

  const profileNames = Object.keys(profiles);

  if (!profileNames.includes(profileName)) {
    logger.error(`Error: Profile '${profileName}' does not exist. Enter 'beekeeper config' to see a list of all profiles.`);
    return false;
  }

  return true;
}

/**
 * Exports the an object containing the utility functions.
 * @returns {Object}
 */
module.exports = {
  getFilePaths,
  getContentType,
  createFile,
  readFile,
  fileExists,
  validateInitRan,
  validateProfileName
};