"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.create = create;
exports.default = createParcelConfigRequest;
exports.getCachedParcelConfig = getCachedParcelConfig;
exports.getResolveFrom = getResolveFrom;
exports.loadParcelConfig = loadParcelConfig;
exports.mergeConfigs = mergeConfigs;
exports.mergeMaps = mergeMaps;
exports.mergePipelines = mergePipelines;
exports.parseAndProcessConfig = parseAndProcessConfig;
exports.processConfig = processConfig;
exports.processConfigChain = processConfigChain;
exports.resolveExtends = resolveExtends;
exports.resolveParcelConfig = resolveParcelConfig;
exports.validateConfigFile = validateConfigFile;
exports.validateNotEmpty = validateNotEmpty;
function _utils() {
  const data = require("@parcel/utils");
  _utils = function () {
    return data;
  };
  return data;
}
function _diagnostic() {
  const data = _interopRequireWildcard(require("@parcel/diagnostic"));
  _diagnostic = function () {
    return data;
  };
  return data;
}
function _json() {
  const data = require("json5");
  _json = function () {
    return data;
  };
  return data;
}
function _path() {
  const data = _interopRequireDefault(require("path"));
  _path = function () {
    return data;
  };
  return data;
}
function _assert() {
  const data = _interopRequireDefault(require("assert"));
  _assert = function () {
    return data;
  };
  return data;
}
var _ParcelConfig = _interopRequireDefault(require("../ParcelConfig.schema"));
var _utils2 = require("../utils");
var _ParcelConfig2 = _interopRequireDefault(require("../ParcelConfig"));
var _buildCache = require("../buildCache");
var _projectPath = require("../projectPath");
var _RequestTracker = require("../RequestTracker");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const type = 'parcel_config_request';
function createParcelConfigRequest() {
  return {
    id: type,
    type: _RequestTracker.requestTypes[type],
    async run({
      api,
      options
    }) {
      let {
        config,
        extendedFiles,
        usedDefault
      } = await loadParcelConfig((0, _utils2.optionsProxy)(options, api.invalidateOnOptionChange));
      api.invalidateOnFileUpdate(config.filePath);
      api.invalidateOnFileDelete(config.filePath);
      for (let filePath of extendedFiles) {
        let pp = (0, _projectPath.toProjectPath)(options.projectRoot, filePath);
        api.invalidateOnFileUpdate(pp);
        api.invalidateOnFileDelete(pp);
      }
      if (usedDefault) {
        let resolveFrom = getResolveFrom(options.inputFS, options.projectRoot);
        api.invalidateOnFileCreate({
          fileName: '.parcelrc',
          aboveFilePath: (0, _projectPath.toProjectPath)(options.projectRoot, resolveFrom)
        });
      }
      let cachePath = (0, _utils().hashObject)(config);
      await options.cache.set(cachePath, config);
      let result = {
        config,
        cachePath
      };
      // TODO: don't store config twice (once in the graph and once in a separate cache entry)
      api.storeResult(result);
      return result;
    },
    input: null
  };
}
const parcelConfigCache = (0, _buildCache.createBuildCache)();
function getCachedParcelConfig(result, options) {
  let {
    config: processedConfig,
    cachePath
  } = result;
  let config = parcelConfigCache.get(cachePath);
  if (config) {
    return config;
  }
  config = new _ParcelConfig2.default(processedConfig, options);
  parcelConfigCache.set(cachePath, config);
  return config;
}
async function loadParcelConfig(options) {
  let parcelConfig = await resolveParcelConfig(options);
  if (!parcelConfig) {
    throw new Error('Could not find a .parcelrc');
  }
  return parcelConfig;
}
async function resolveParcelConfig(options) {
  let resolveFrom = getResolveFrom(options.inputFS, options.projectRoot);
  let configPath = options.config != null ? (await options.packageManager.resolve(options.config, resolveFrom)).resolved : await (0, _utils().resolveConfig)(options.inputFS, resolveFrom, ['.parcelrc'], options.projectRoot);
  let usedDefault = false;
  if (configPath == null && options.defaultConfig != null) {
    usedDefault = true;
    configPath = (await options.packageManager.resolve(options.defaultConfig, resolveFrom)).resolved;
  }
  if (configPath == null) {
    return null;
  }
  let contents;
  try {
    contents = await options.inputFS.readFile(configPath, 'utf8');
  } catch (e) {
    throw new (_diagnostic().default)({
      diagnostic: {
        message: (0, _diagnostic().md)`Could not find parcel config at ${_path().default.relative(options.projectRoot, configPath)}`,
        origin: '@parcel/core'
      }
    });
  }
  let {
    config,
    extendedFiles
  } = await parseAndProcessConfig(configPath, contents, options);
  if (options.additionalReporters.length > 0) {
    config.reporters = [...options.additionalReporters.map(({
      packageName,
      resolveFrom
    }) => ({
      packageName,
      resolveFrom
    })), ...(config.reporters ?? [])];
  }
  return {
    config,
    extendedFiles,
    usedDefault
  };
}
function create(config, options) {
  return processConfigChain(config, config.filePath, options);
}

// eslint-disable-next-line require-await
async function parseAndProcessConfig(configPath, contents, options) {
  let config;
  try {
    config = (0, _json().parse)(contents);
  } catch (e) {
    let pos = {
      line: e.lineNumber,
      column: e.columnNumber
    };
    throw new (_diagnostic().default)({
      diagnostic: {
        message: `Failed to parse .parcelrc`,
        origin: '@parcel/core',
        codeFrames: [{
          filePath: configPath,
          language: 'json5',
          code: contents,
          codeHighlights: [{
            start: pos,
            end: pos,
            message: (0, _diagnostic().escapeMarkdown)(e.message)
          }]
        }]
      }
    });
  }
  return processConfigChain(config, configPath, options);
}
function processPipeline(options, pipeline, keyPath, filePath) {
  if (pipeline) {
    return pipeline.map((pkg, i) => {
      // $FlowFixMe
      if (pkg === '...') return pkg;
      return {
        packageName: pkg,
        resolveFrom: (0, _projectPath.toProjectPath)(options.projectRoot, filePath),
        keyPath: `${keyPath}/${i}`
      };
    });
  }
}
const RESERVED_PIPELINES = new Set(['node:', 'npm:', 'http:', 'https:', 'data:', 'tel:', 'mailto:']);
async function processMap(
// $FlowFixMe
map, keyPath, filePath, options
// $FlowFixMe
) {
  if (!map) return undefined;

  // $FlowFixMe
  let res = {};
  for (let k in map) {
    let i = k.indexOf(':');
    if (i > 0 && RESERVED_PIPELINES.has(k.slice(0, i + 1))) {
      let code = await options.inputFS.readFile(filePath, 'utf8');
      throw new (_diagnostic().default)({
        diagnostic: {
          message: `Named pipeline '${k.slice(0, i + 1)}' is reserved.`,
          origin: '@parcel/core',
          codeFrames: [{
            filePath: filePath,
            language: 'json5',
            code,
            codeHighlights: (0, _diagnostic().generateJSONCodeHighlights)(code, [{
              key: `${keyPath}/${k}`,
              type: 'key'
            }])
          }],
          documentationURL: 'https://parceljs.org/features/dependency-resolution/#url-schemes'
        }
      });
    }
    if (typeof map[k] === 'string') {
      res[k] = {
        packageName: map[k],
        resolveFrom: (0, _projectPath.toProjectPath)(options.projectRoot, filePath),
        keyPath: `${keyPath}/${k}`
      };
    } else {
      res[k] = processPipeline(options, map[k], `${keyPath}/${k}`, filePath);
    }
  }
  return res;
}
async function processConfig(configFile, options) {
  return {
    filePath: (0, _projectPath.toProjectPath)(options.projectRoot, configFile.filePath),
    ...(configFile.resolveFrom != null ? {
      resolveFrom: (0, _projectPath.toProjectPath)(options.projectRoot, configFile.resolveFrom)
    } : {
      /*::...null*/
    }),
    resolvers: processPipeline(options, configFile.resolvers, '/resolvers', configFile.filePath),
    transformers: await processMap(configFile.transformers, '/transformers', configFile.filePath, options),
    bundler: configFile.bundler != null ? {
      packageName: configFile.bundler,
      resolveFrom: (0, _projectPath.toProjectPath)(options.projectRoot, configFile.filePath),
      keyPath: '/bundler'
    } : undefined,
    namers: processPipeline(options, configFile.namers, '/namers', configFile.filePath),
    runtimes: processPipeline(options, configFile.runtimes, '/runtimes', configFile.filePath),
    packagers: await processMap(configFile.packagers, '/packagers', configFile.filePath, options),
    optimizers: await processMap(configFile.optimizers, '/optimizers', configFile.filePath, options),
    compressors: await processMap(configFile.compressors, '/compressors', configFile.filePath, options),
    reporters: processPipeline(options, configFile.reporters, '/reporters', configFile.filePath),
    validators: await processMap(configFile.validators, '/validators', configFile.filePath, options)
  };
}
async function processConfigChain(configFile, filePath, options) {
  // Validate config...
  let relativePath = _path().default.relative(options.inputFS.cwd(), filePath);
  validateConfigFile(configFile, relativePath);

  // Process config...
  let config = await processConfig({
    filePath,
    ...configFile
  }, options);
  let extendedFiles = [];
  if (configFile.extends != null) {
    let exts = Array.isArray(configFile.extends) ? configFile.extends : [configFile.extends];
    let errors = [];
    if (exts.length !== 0) {
      let extStartConfig;
      let i = 0;
      for (let ext of exts) {
        try {
          let key = Array.isArray(configFile.extends) ? `/extends/${i}` : '/extends';
          let resolved = await resolveExtends(ext, filePath, key, options);
          extendedFiles.push(resolved);
          let {
            extendedFiles: moreExtendedFiles,
            config: nextConfig
          } = await processExtendedConfig(filePath, key, ext, resolved, options);
          extendedFiles = extendedFiles.concat(moreExtendedFiles);
          extStartConfig = extStartConfig ? mergeConfigs(extStartConfig, nextConfig) : nextConfig;
        } catch (err) {
          errors.push(err);
        }
        i++;
      }

      // Merge with the inline config last
      if (extStartConfig) {
        config = mergeConfigs(extStartConfig, config);
      }
    }
    if (errors.length > 0) {
      throw new (_diagnostic().default)({
        diagnostic: errors.flatMap(e => e.diagnostics ?? (0, _diagnostic().errorToDiagnostic)(e))
      });
    }
  }
  return {
    config,
    extendedFiles
  };
}
async function resolveExtends(ext, configPath, extendsKey, options) {
  if (ext.startsWith('.')) {
    return _path().default.resolve(_path().default.dirname(configPath), ext);
  } else {
    try {
      let {
        resolved
      } = await options.packageManager.resolve(ext, configPath);
      return options.inputFS.realpath(resolved);
    } catch (err) {
      let parentContents = await options.inputFS.readFile(configPath, 'utf8');
      let alternatives = await (0, _utils().findAlternativeNodeModules)(options.inputFS, ext, _path().default.dirname(configPath));
      throw new (_diagnostic().default)({
        diagnostic: {
          message: `Cannot find extended parcel config`,
          origin: '@parcel/core',
          codeFrames: [{
            filePath: configPath,
            language: 'json5',
            code: parentContents,
            codeHighlights: (0, _diagnostic().generateJSONCodeHighlights)(parentContents, [{
              key: extendsKey,
              type: 'value',
              message: (0, _diagnostic().md)`Cannot find module "${ext}"${alternatives[0] ? `, did you mean "${alternatives[0]}"?` : ''}`
            }])
          }]
        }
      });
    }
  }
}
async function processExtendedConfig(configPath, extendsKey, extendsSpecifier, resolvedExtendedConfigPath, options) {
  let contents;
  try {
    contents = await options.inputFS.readFile(resolvedExtendedConfigPath, 'utf8');
  } catch (e) {
    let parentContents = await options.inputFS.readFile(configPath, 'utf8');
    let alternatives = await (0, _utils().findAlternativeFiles)(options.inputFS, extendsSpecifier, _path().default.dirname(resolvedExtendedConfigPath), options.projectRoot);
    throw new (_diagnostic().default)({
      diagnostic: {
        message: 'Cannot find extended parcel config',
        origin: '@parcel/core',
        codeFrames: [{
          filePath: configPath,
          language: 'json5',
          code: parentContents,
          codeHighlights: (0, _diagnostic().generateJSONCodeHighlights)(parentContents, [{
            key: extendsKey,
            type: 'value',
            message: (0, _diagnostic().md)`"${extendsSpecifier}" does not exist${alternatives[0] ? `, did you mean "${alternatives[0]}"?` : ''}`
          }])
        }]
      }
    });
  }
  return parseAndProcessConfig(resolvedExtendedConfigPath, contents, options);
}
function validateConfigFile(config, relativePath) {
  try {
    validateNotEmpty(config, relativePath);
  } catch (e) {
    throw new (_diagnostic().default)({
      diagnostic: {
        message: e.message,
        origin: '@parcel/core'
      }
    });
  }
  _utils().validateSchema.diagnostic(_ParcelConfig.default, {
    data: config,
    filePath: relativePath
  }, '@parcel/core', 'Invalid Parcel Config');
}
function validateNotEmpty(config, relativePath) {
  _assert().default.notDeepStrictEqual(config, {}, `${relativePath} can't be empty`);
}
function mergeConfigs(base, ext) {
  return {
    filePath: ext.filePath,
    resolvers: assertPurePipeline(mergePipelines(base.resolvers, ext.resolvers)),
    transformers: mergeMaps(base.transformers, ext.transformers, mergePipelines),
    validators: mergeMaps(base.validators, ext.validators, mergePipelines),
    bundler: ext.bundler || base.bundler,
    namers: assertPurePipeline(mergePipelines(base.namers, ext.namers)),
    runtimes: assertPurePipeline(mergePipelines(base.runtimes, ext.runtimes)),
    packagers: mergeMaps(base.packagers, ext.packagers),
    optimizers: mergeMaps(base.optimizers, ext.optimizers, mergePipelines),
    compressors: mergeMaps(base.compressors, ext.compressors, mergePipelines),
    reporters: assertPurePipeline(mergePipelines(base.reporters, ext.reporters))
  };
}
function getResolveFrom(fs, projectRoot) {
  let cwd = fs.cwd();
  let dir = (0, _utils().isDirectoryInside)(cwd, projectRoot) ? cwd : projectRoot;
  return _path().default.join(dir, 'index');
}
function assertPurePipeline(pipeline) {
  return pipeline.map(s => {
    (0, _assert().default)(typeof s !== 'string');
    return s;
  });
}
function mergePipelines(base, ext) {
  if (ext == null) {
    return base ?? [];
  }
  if (ext.filter(v => v === '...').length > 1) {
    throw new Error('Only one spread element can be included in a config pipeline');
  }

  // Merge the base pipeline if a rest element is defined
  let spreadIndex = ext.indexOf('...');
  if (spreadIndex >= 0) {
    return [...ext.slice(0, spreadIndex), ...(base ?? []), ...ext.slice(spreadIndex + 1)];
  } else {
    return ext;
  }
}
function mergeMaps(base, ext, merger) {
  if (!ext || Object.keys(ext).length === 0) {
    return base || {};
  }
  if (!base) {
    return ext;
  }
  let res = {};
  // Add the extension options first so they have higher precedence in the output glob map
  for (let k in ext) {
    //$FlowFixMe Flow doesn't correctly infer the type. See https://github.com/facebook/flow/issues/1736.
    let key = k;
    res[key] = merger && base[key] != null ? merger(base[key], ext[key]) : ext[key];
  }

  // Add base options that aren't defined in the extension
  for (let k in base) {
    // $FlowFixMe
    let key = k;
    if (res[key] == null) {
      res[key] = base[key];
    }
  }
  return res;
}