'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }

var yaml = _interopDefault(require('js-yaml'));

const VERSION = "1.1.3";

function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true
    });
  } else {
    obj[key] = value;
  }

  return obj;
}

function ownKeys(object, enumerableOnly) {
  var keys = Object.keys(object);

  if (Object.getOwnPropertySymbols) {
    var symbols = Object.getOwnPropertySymbols(object);
    if (enumerableOnly) symbols = symbols.filter(function (sym) {
      return Object.getOwnPropertyDescriptor(object, sym).enumerable;
    });
    keys.push.apply(keys, symbols);
  }

  return keys;
}

function _objectSpread2(target) {
  for (var i = 1; i < arguments.length; i++) {
    var source = arguments[i] != null ? arguments[i] : {};

    if (i % 2) {
      ownKeys(Object(source), true).forEach(function (key) {
        _defineProperty(target, key, source[key]);
      });
    } else if (Object.getOwnPropertyDescriptors) {
      Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
    } else {
      ownKeys(Object(source)).forEach(function (key) {
        Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
      });
    }
  }

  return target;
}

const SUPPORTED_FILE_EXTENSIONS = ["json", "yml", "yaml"];
/**
 * Load configuration from a given repository and path.
 *
 * @param octokit Octokit instance
 * @param options
 */

async function getConfigFile(octokit, {
  owner,
  repo,
  path,
  ref
}) {
  const fileExtension = path.split(".").pop().toLowerCase();

  if (!SUPPORTED_FILE_EXTENSIONS.includes(fileExtension)) {
    throw new Error(`[@probot/octokit-plugin-config] .${fileExtension} extension is not support for configuration (path: "${path}")`);
  } // https://docs.github.com/en/rest/reference/repos#get-repository-content


  const endpoint = _objectSpread2({
    method: "GET",
    url: "/repos/{owner}/{repo}/contents/{path}",
    owner,
    repo,
    path,
    mediaType: {
      format: "raw"
    }
  }, ref ? {
    ref
  } : {});

  const {
    url
  } = await octokit.request.endpoint(endpoint);
  const emptyConfigResult = {
    owner,
    repo,
    path,
    url,
    config: null
  };

  try {
    const {
      data,
      headers
    } = await octokit.request(endpoint); // If path is a submodule, or a folder, then a JSON string is returned with
    // the "Content-Type" header set to "application/json; charset=utf-8".
    //
    // - https://docs.github.com/en/rest/reference/repos#if-the-content-is-a-submodule
    // - https://docs.github.com/en/rest/reference/repos#if-the-content-is-a-directory
    //
    // symlinks just return the content of the linked file when requesting the raw formt,
    // so we are fine

    if (headers["content-type"] === "application/json; charset=utf-8") {
      throw new Error(`[@probot/octokit-plugin-config] ${url} exists, but is either a directory or a submodule. Ignoring.`);
    }

    if (fileExtension === "json") {
      if (typeof data === "string") {
        throw new Error(`[@probot/octokit-plugin-config] Configuration could not be parsed from ${url} (invalid JSON)`);
      }

      return _objectSpread2(_objectSpread2({}, emptyConfigResult), {}, {
        config: data
      });
    }

    const config = yaml.load(data) || {};

    if (typeof config === "string") {
      throw new Error(`[@probot/octokit-plugin-config] Configuration could not be parsed from ${url} (YAML is not an object)`);
    }

    return _objectSpread2(_objectSpread2({}, emptyConfigResult), {}, {
      config
    });
  } catch (error) {
    if (error.status === 404) {
      return emptyConfigResult;
    }

    if (error.name === "YAMLException") {
      const reason = /unknown tag/.test(error.message) ? "unsafe YAML" : "invalid YAML";
      throw new Error(`[@probot/octokit-plugin-config] Configuration could not be parsed from ${url} (${reason})`);
    }

    throw error;
  }
}

const EXTENDS_REGEX = new RegExp("^" + "(?:([a-z\\d](?:[a-z\\d]|-(?=[a-z\\d])){0,38})/)?" + // org
"([-_.\\w\\d]+)" + // project
"(?::([-_./\\w\\d]+\\.ya?ml))?" + // filename
"$", "i");
/**
 * Computes parameters to retrieve the configuration file specified in _extends
 *
 * Base can either be the name of a repository in the same organization or
 * a full slug "organization/repo".
 *
 * @param options
 * @return The params needed to retrieve a configuration file
 */

function extendsToGetContentParams({
  owner,
  path,
  url,
  extendsValue
}) {
  if (typeof extendsValue !== "string") {
    throw new Error(`[@probot/octokit-plugin-config] Invalid value ${JSON.stringify(extendsValue)} for _extends in ${url}`);
  }

  const match = extendsValue.match(EXTENDS_REGEX);

  if (match === null) {
    throw new Error(`[@probot/octokit-plugin-config] Invalid value "${extendsValue}" for _extends in ${url}`);
  }

  return {
    owner: match[1] || owner,
    repo: match[2],
    path: match[3] || path
  };
}

/**
 * Load configuration from selected repository file. If the file does not exist
 * it loads configuration from the owners `.github` repository.
 *
 * If the repository file configuration includes an `_extends` key, that file
 * is loaded. Same with the target file until no `_extends` key is present.
 *
 * @param octokit Octokit instance
 * @param options
 */

async function getConfigFiles(octokit, {
  owner,
  repo,
  path,
  branch
}) {
  const requestedRepoFile = await getConfigFile(octokit, {
    owner,
    repo,
    path,
    ref: branch
  }); // if no configuration file present in selected repository,
  // try to load it from the `.github` repository

  if (!requestedRepoFile.config) {
    if (repo === ".github") {
      return [requestedRepoFile];
    }

    const defaultRepoConfig = await getConfigFile(octokit, {
      owner,
      repo: ".github",
      path
    });
    return [requestedRepoFile, defaultRepoConfig];
  } // if the configuration has no `_extends` key, we are done here.


  if (!requestedRepoFile.config._extends) {
    return [requestedRepoFile];
  } // parse the value of `_extends` into request parameters to
  // retrieve the new configuration file


  let extendConfigOptions = extendsToGetContentParams({
    owner,
    path,
    url: requestedRepoFile.url,
    extendsValue: requestedRepoFile.config._extends
  }); // remove the `_extends` key from the configuration that is returned

  delete requestedRepoFile.config._extends;
  const files = [requestedRepoFile]; // now load the configuration linked from the `_extends` key. If that
  // configuration also includes an `_extends` key, then load that configuration
  // as well, until the target configuration has no `_extends` key

  do {
    const extendRepoConfig = await getConfigFile(octokit, extendConfigOptions);
    files.push(extendRepoConfig);

    if (!extendRepoConfig.config || !extendRepoConfig.config._extends) {
      return files;
    }

    extendConfigOptions = extendsToGetContentParams({
      owner,
      path,
      url: extendRepoConfig.url,
      extendsValue: extendRepoConfig.config._extends
    });
    delete extendRepoConfig.config._extends; // Avoid loops

    const alreadyLoaded = files.find(file => file.owner === extendConfigOptions.owner && file.repo === extendConfigOptions.repo && file.path === extendConfigOptions.path);

    if (alreadyLoaded) {
      throw new Error(`[@probot/octokit-plugin-config] Recursion detected. Ignoring  "_extends: ${extendRepoConfig.config._extends}" from ${extendRepoConfig.url} because ${alreadyLoaded.url} was already loaded.`);
    }
  } while (true);
}

/**
 * Loads configuration from one or multiple files and resolves with
 * the combined configuration as well as the list of files the configuration
 * was loaded from
 *
 * @param octokit Octokit instance
 * @param options
 */

async function composeConfigGet(octokit, {
  owner,
  repo,
  defaults,
  path,
  branch
}) {
  const files = await getConfigFiles(octokit, {
    owner,
    repo,
    path,
    branch
  });
  const configs = files.map(file => file.config).reverse().filter(Boolean);
  return {
    files,
    config: typeof defaults === "function" ? defaults(configs) : Object.assign({}, defaults, ...configs)
  };
}

/**
 * @param octokit Octokit instance
 */

function config(octokit) {
  return {
    config: {
      async get(options) {
        return composeConfigGet(octokit, options);
      }

    }
  };
}
config.VERSION = VERSION;

exports.composeConfigGet = composeConfigGet;
exports.config = config;
//# sourceMappingURL=index.js.map
