HEX
Server: Apache/2.4.52 (Ubuntu)
System: Linux spn-python 5.15.0-89-generic #99-Ubuntu SMP Mon Oct 30 20:42:41 UTC 2023 x86_64
User: arjun (1000)
PHP: 8.1.2-1ubuntu2.20
Disabled: NONE
Upload Files
File: //home/arjun/projects/good-life-be/node_modules/dotenv-flow/lib/dotenv-flow.js
'use strict';

const fs = require('fs');
const p = require('path');
const dotenv = require('dotenv');
const {version} = require('../package.json');

const DEFAULT_PATTERN = '.env[.node_env][.local]';

const LOCAL_PLACEHOLDER_REGEX = /\[(\W*\blocal\b\W*)]/g;
const NODE_ENV_PLACEHOLDER_REGEX = /\[(\W*\b)node_env(\b\W*)]/g;

/**
 * Compose a filename from a given `patten`.
 *
 * @param {string} pattern
 * @param {object} [options]
 * @param {boolean} [options.local]
 * @param {string} [options.node_env]
 * @return {string} filename
 */
function composeFilename(pattern, options) {
  let filename = pattern;

  filename = filename.replace(
    LOCAL_PLACEHOLDER_REGEX,
    (options && options.local) ? '$1' : ''
  );

  filename = filename.replace(
    NODE_ENV_PLACEHOLDER_REGEX,
    (options && options.node_env) ? `$1${options.node_env}$2` : ''
  );

  return filename;
}

/**
 * Returns a list of existing `.env*` filenames depending on the given `options`.
 *
 * The resulting list is ordered by the env files'
 * variables overwriting priority from lowest to highest.
 *
 * This can also be referenced as "env files' environment cascade"
 * or "order of ascending priority."
 *
 * ⚠️ Note that the `.env.local` file is not listed for "test" environment,
 * since normally you expect tests to produce the same results for everyone.
 *
 * @param {object} [options] - `.env*` files listing options
 * @param {string} [options.node_env] - node environment (development/test/production/etc.)
 * @param {string} [options.path] - path to the working directory (default: `process.cwd()`)
 * @param {string} [options.pattern] - `.env*` files' naming convention pattern
 *                                       (default: ".env[.node_env][.local]")
 * @param {boolean} [options.debug] - turn on debug messages
 * @return {string[]}
 */
function listFiles(options = {}) {
  options.debug && debug('listing effective `.env*` files…');

  const {
    node_env,
    path = process.cwd(),
    pattern = DEFAULT_PATTERN,
  } = options;

  const hasLocalPlaceholder = LOCAL_PLACEHOLDER_REGEX.test(pattern);

  const filenames = {};

  if (pattern === DEFAULT_PATTERN) {
    filenames['.env.defaults'] = '.env.defaults'; // for seamless transition from ".env + .env.defaults"
  }

  filenames['.env'] = composeFilename(pattern);

  if (hasLocalPlaceholder) {
    const envlocal = composeFilename(pattern, { local: true });

    if (node_env !== 'test') {
      filenames['.env.local'] = envlocal;
    }
    else if (options.debug && fs.existsSync(p.resolve(path, envlocal))) {
      debug(
        '[!] note that `%s` is being skipped for "test" environment',
        envlocal
      );
    }
  }

  if (node_env && NODE_ENV_PLACEHOLDER_REGEX.test(pattern)) {
    filenames['.env.node_env'] = composeFilename(pattern, { node_env });

    if (hasLocalPlaceholder) {
      filenames['.env.node_env.local'] = composeFilename(pattern, { node_env, local: true });
    }
  }

  return [
    '.env.defaults',
    '.env',
    '.env.local',
    '.env.node_env',
    '.env.node_env.local'
  ]
    .reduce((list, basename) => {
      if (!filenames[basename]) {
        return list;
      }

      const filename = p.resolve(path, filenames[basename]);
      if (fs.existsSync(filename)) {
        options.debug && debug('>> %s', filename);
        list.push(filename);
      }

      return list;
    }, []);
}

/**
 * Parses a given file or a list of files.
 *
 * When a list of filenames is given, the files will be parsed and merged in the same order as given.
 *
 * @param {string|string[]} filenames - filename or a list of filenames to parse and merge
 * @param {{ encoding?: string, debug?: boolean }} [options] - parse options
 * @return {Object<string, string>} the resulting map of `{ env_var: value }` as an object
 */
function parse(filenames, options = {}) {
  if (typeof filenames === 'string') {
    options.debug && debug('parsing "%s"…', filenames);

    const parsed = dotenv.parse(
      fs.readFileSync(
        filenames,
        options.encoding && { encoding: options.encoding }
      )
    );

    if (options.debug) {
      Object.keys(parsed)
        .forEach(varname => debug('>> %s', varname));
    }

    return parsed;
  }

  return filenames.reduce((result, filename) => {
    const parsed = parse(filename, options);

    if (options.debug) {
      Object.keys(parsed)
        .filter(varname => result.hasOwnProperty(varname))
        .forEach(varname => debug('`%s` is being overwritten by merge from "%s"', varname, filename));
    }

    return Object.assign(result, parsed);
  }, {});
}

/**
 * Parses variables defined in given file(s) and assigns them to `process.env`.
 *
 * Variables that are already defined in `process.env` will not be overwritten,
 * thus giving a higher priority to environment variables predefined by the shell.
 *
 * If the loading is successful, an object with `parsed` property is returned.
 * The `parsed` property contains parsed variables' `key => value` pairs merged in order using
 * the "overwrite merge" strategy.
 *
 * If parsing fails for any of the given files, `process.env` is being left untouched,
 * and an object with `error` property is returned.
 * The `error` property, if present, references to the occurred error.
 *
 * @param {string|string[]} filenames - filename or a list of filenames to parse and merge
 * @param {object} [options] - file loading options
 * @param {string} [options.encoding="utf8"] - encoding of `.env*` files
 * @param {boolean} [options.debug=false] - turn on debug messages
 * @param {boolean} [options.silent=false] - suppress console errors and warnings
 * @return {{ error: Error } | { parsed: Object<string, string> }}
 */
function load(filenames, options = {}) {
  try {
    const parsed = parse(filenames, {
      encoding: options.encoding,
      debug: options.debug
    });

    options.debug && debug('safe-merging parsed environment variables into `process.env`…');

    for (const varname of Object.keys(parsed)) {
      if (!process.env.hasOwnProperty(varname)) {
        options.debug && debug('>> process.env.%s', varname);
        process.env[varname] = parsed[varname];
      }
      else if (options.debug && process.env[varname] !== parsed[varname]) {
        debug('environment variable `%s` is predefined and not being overwritten', varname);
      }
    }

    return { parsed };
  }
  catch (error) {
    return failure(error, options);
  }
}

/**
 * Unload variables defined in a given file(s) from `process.env`.
 *
 * This function can gracefully resolve the following issue:
 *
 * In some cases, the original "dotenv" library can be used by one of the dependent npm modules.
 * It causes calling the original `dotenv.config()` that loads the `.env` file from your project before you can call `dotenv-flow.config()`.
 * Such cases break `.env*` files priority because the previously loaded environment variables are treated as shell-defined thus having a higher priority.
 *
 * Unloading the previously loaded `.env` file can be activated when using the `dotenv-flow.config()` with the `purge_dotenv` option set to `true`.
 *
 * @param {string|string[]} filenames - filename or a list of filenames to unload
 * @param {object} [options] - `fs.readFileSync` options
 */
function unload(filenames, options = {}) {
  const parsed = parse(filenames, options);

  Object.keys(parsed).forEach((key) => {
    if (process.env[key] === parsed[key]) {
      delete process.env[key];
    }
  });
}

/**
 * Returns effective (computed) `node_env`.
 *
 * @param {object} [options]
 * @param {string} [options.node_env]
 * @param {string} [options.default_node_env]
 * @param {boolean} [options.debug]
 * @return {string|undefined} node_env
 */
function getEffectiveNodeEnv(options = {}) {
  if (options.node_env) {
    options.debug && debug(
      `operating in "${options.node_env}" environment (set by \`options.node_env\`)`
    );
    return options.node_env;
  }

  if (process.env.NODE_ENV) {
    options.debug && debug(
      `operating in "${process.env.NODE_ENV}" environment (as per \`process.env.NODE_ENV\`)`
    );
    return process.env.NODE_ENV;
  }

  if (options.default_node_env) {
    options.debug && debug(
      `operating in "${options.default_node_env}" environment (taken from \`options.default_node_env\`)`
    );
    return options.default_node_env;
  }

  options.debug && debug(
    'operating in "no environment" mode (no environment-related options are set)'
  );
  return undefined;
}

const CONFIG_OPTION_KEYS = [
  'node_env',
  'default_node_env',
  'path',
  'pattern',
  'files',
  'encoding',
  'purge_dotenv',
  'silent'
];

/**
 * "dotenv-flow" initialization function (API entry point).
 *
 * Allows configuring dotenv-flow programmatically.
 *
 * @param {object} [options] - configuration options
 * @param {string} [options.node_env=process.env.NODE_ENV] - node environment (development/test/production/etc.)
 * @param {string} [options.default_node_env] - the default node environment
 * @param {string} [options.path=process.cwd()] - path to `.env*` files directory
 * @param {string} [options.pattern=".env[.node_env][.local]"] - `.env*` files' naming convention pattern
 * @param {string[]} [options.files] - an explicit list of `.env*` files to load (note that `options.[default_]node_env` and `options.pattern` are ignored in this case)
 * @param {string} [options.encoding="utf8"] - encoding of `.env*` files
 * @param {boolean} [options.purge_dotenv=false] - perform the `.env` file {@link unload}
 * @param {boolean} [options.debug=false] - turn on detailed logging to help debug why certain variables are not being set as you expect
 * @param {boolean} [options.silent=false] - suppress all kinds of warnings including ".env*" files' loading errors
 * @return {{ parsed?: object, error?: Error }} with a `parsed` key containing the loaded content or an `error` key with an error that is occurred
 */
function config(options = {}) {
  if (options.debug) {
    debug('initializing…');

    CONFIG_OPTION_KEYS
      .filter(key => key in options)
      .forEach(key => debug(`| options.${key} =`, options[key]));
  }

  const {
    path = process.cwd(),
    pattern = DEFAULT_PATTERN
  } = options;

  if (options.purge_dotenv) {
    options.debug && debug(
      '`options.purge_dotenv` is enabled, unloading potentially pre-loaded `.env`…'
    );

    const dotenvFile = p.resolve(path, '.env');
    try {
      fs.existsSync(dotenvFile) && unload(dotenvFile, { encoding: options.encoding });
    }
    catch (error) {
      !options.silent && warn('unloading failed: ', error);
    }
  }

  try {
    let filenames;

    if (options.files) {
      options.debug && debug(
        'using explicit list of `.env*` files: %s…',
        options.files.join(', ')
      );

      filenames = options.files
        .reduce((list, basename) => {
          const filename = p.resolve(path, basename);

          if (fs.existsSync(filename)) {
            list.push(filename);
          }
          else if (options.debug) {
            debug('>> %s does not exist, skipping…', filename);
          }

          return list;
        }, []);
    }
    else {
      const node_env = getEffectiveNodeEnv(options);

      filenames = listFiles({ node_env, path, pattern, debug: options.debug });

      if (filenames.length === 0) {
        const _pattern = node_env
          ? pattern.replace(NODE_ENV_PLACEHOLDER_REGEX, `[$1${node_env}$2]`)
          : pattern;

        return failure(
          new Error(`no ".env*" files matching pattern "${_pattern}" in "${path}" dir`),
          options
        );
      }
    }

    const result = load(filenames, {
      encoding: options.encoding,
      debug: options.debug,
      silent: options.silent
    });

    options.debug && debug('initialization completed.');

    return result;
  }
  catch (error) {
    return failure(error, options);
  }
}

function failure(error, options) {
  if (!options.silent) {
    warn(`".env*" files loading failed: ${error.message || error}`);
  }

  return { error };
}

function warn(message, error) {
  if (error) {
    message += ': %s';
  }

  console.warn(`[dotenv-flow@${version}]: ${message}`, error);
}

function debug(message, ...values) {
  console.debug(`[dotenv-flow@${version}]: ${message}`, ...values);
}

module.exports = {
  DEFAULT_PATTERN,
  listFiles,
  parse,
  load,
  unload,
  config
};