'use strict';

var lockfile = require('@yarnpkg/lockfile');
var parsers = require('@yarnpkg/parsers');
var fs = require('fs-extra');
var semver = require('semver');

function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
var semver__default = /*#__PURE__*/_interopDefaultLegacy(semver);

const ENTRY_PATTERN = /^((?:@[^/]+\/)?[^@/]+)@(.+)$/;
const NEW_HEADER = `${[
  `# This file is generated by running "yarn install" inside your project.
`,
  `# Manual changes might be lost - proceed with caution!
`
].join(``)}
`;
const LEGACY_REGEX = /^(#.*(\r?\n))*?#\s+yarn\s+lockfile\s+v1\r?\n/i;
const SPECIAL_OBJECT_KEYS = [
  `__metadata`,
  `version`,
  `resolution`,
  `dependencies`,
  `peerDependencies`,
  `dependenciesMeta`,
  `peerDependenciesMeta`,
  `binaries`
];
class Lockfile {
  constructor(packages, data, legacy = false) {
    this.packages = packages;
    this.data = data;
    this.legacy = legacy;
  }
  static async load(path) {
    const lockfileContents = await fs__default["default"].readFile(path, "utf8");
    return Lockfile.parse(lockfileContents);
  }
  static parse(content) {
    var _a;
    const legacy = LEGACY_REGEX.test(content);
    let data;
    try {
      data = parsers.parseSyml(content);
    } catch (err) {
      throw new Error(`Failed yarn.lock parse, ${err}`);
    }
    const packages = /* @__PURE__ */ new Map();
    for (const [key, value] of Object.entries(data)) {
      if (SPECIAL_OBJECT_KEYS.includes(key))
        continue;
      const [, name, ranges] = (_a = ENTRY_PATTERN.exec(key)) != null ? _a : [];
      if (!name) {
        throw new Error(`Failed to parse yarn.lock entry '${key}'`);
      }
      let queries = packages.get(name);
      if (!queries) {
        queries = [];
        packages.set(name, queries);
      }
      for (let range of ranges.split(/\s*,\s*/)) {
        if (range.startsWith(`${name}@`)) {
          range = range.slice(`${name}@`.length);
        }
        if (range.startsWith("npm:")) {
          range = range.slice("npm:".length);
        }
        queries.push({ range, version: value.version, dataKey: key });
      }
    }
    return new Lockfile(packages, data, legacy);
  }
  /** Get the entries for a single package in the lockfile */
  get(name) {
    return this.packages.get(name);
  }
  /** Returns the name of all packages available in the lockfile */
  keys() {
    return this.packages.keys();
  }
  /** Analyzes the lockfile to identify possible actions and warnings for the entries */
  analyze(options) {
    var _a;
    const { filter, localPackages } = options;
    const result = {
      invalidRanges: [],
      newVersions: [],
      newRanges: []
    };
    for (const [name, allEntries] of this.packages) {
      if (filter && !filter(name)) {
        continue;
      }
      const invalid = allEntries.filter(
        (e) => !semver__default["default"].validRange(e.range) && !e.range.startsWith("workspace:")
      );
      result.invalidRanges.push(
        ...invalid.map(({ range }) => ({ name, range }))
      );
      const entries = allEntries.filter((e) => semver__default["default"].validRange(e.range));
      if (entries.length < 2) {
        continue;
      }
      const versions = Array.from(new Set(entries.map((e) => e.version))).map((v) => {
        if (v === "0.0.0-use.local") {
          const local = localPackages.get(name);
          if (!local) {
            throw new Error(`No local package found for ${name}`);
          }
          if (!local.packageJson.version) {
            throw new Error(`No version found for local package ${name}`);
          }
          return {
            entryVersion: v,
            actualVersion: local.packageJson.version
          };
        }
        return { entryVersion: v, actualVersion: v };
      }).sort((v1, v2) => semver__default["default"].rcompare(v1.actualVersion, v2.actualVersion));
      if (versions.length < 2) {
        continue;
      }
      const acceptedVersions = /* @__PURE__ */ new Set();
      for (const { version, range } of entries) {
        const acceptedVersion = versions.find(
          (v) => semver__default["default"].satisfies(v.actualVersion, range)
        );
        if (!acceptedVersion) {
          throw new Error(
            `No existing version was accepted for range ${range}, searching through ${versions}, for package ${name}`
          );
        }
        if (acceptedVersion.entryVersion !== version) {
          result.newVersions.push({
            name,
            range,
            newVersion: acceptedVersion.entryVersion,
            oldVersion: version
          });
        }
        acceptedVersions.add(acceptedVersion.actualVersion);
      }
      if (acceptedVersions.size === 1) {
        continue;
      }
      const maxVersion = Array.from(acceptedVersions).sort(semver__default["default"].rcompare)[0];
      const maxEntry = (_a = entries.filter((e) => semver__default["default"].satisfies(maxVersion, e.range)).map((e) => ({ e, min: semver__default["default"].minVersion(e.range) })).filter((p) => p.min).sort((a, b) => semver__default["default"].rcompare(a.min, b.min))[0]) == null ? void 0 : _a.e;
      if (!maxEntry) {
        throw new Error(
          `No entry found that satisfies max version '${maxVersion}'`
        );
      }
      for (const { version, range } of entries) {
        if (semver__default["default"].satisfies(maxVersion, range)) {
          continue;
        }
        result.newRanges.push({
          name,
          oldRange: range,
          newRange: maxEntry.range,
          oldVersion: version,
          newVersion: maxVersion
        });
      }
    }
    return result;
  }
  remove(name, range) {
    var _a;
    const query = `${name}@${range}`;
    const existed = Boolean(this.data[query]);
    delete this.data[query];
    const newEntries = (_a = this.packages.get(name)) == null ? void 0 : _a.filter((e) => e.range !== range);
    if (newEntries) {
      this.packages.set(name, newEntries);
    }
    return existed;
  }
  /** Modifies the lockfile by bumping packages to the suggested versions */
  replaceVersions(results) {
    var _a;
    for (const { name, range, oldVersion, newVersion } of results) {
      const query = `${name}@${range}`;
      const entryData = this.data[query];
      if (!entryData) {
        throw new Error(`No entry data for ${query}`);
      }
      if (entryData.version !== oldVersion) {
        throw new Error(
          `Expected existing version data for ${query} to be ${oldVersion}, was ${entryData.version}`
        );
      }
      const matchingEntry = Object.entries(this.data).find(
        ([q, e]) => q.startsWith(`${name}@`) && e.version === newVersion
      );
      if (!matchingEntry) {
        throw new Error(
          `No matching entry found for ${name} at version ${newVersion}`
        );
      }
      this.data[query] = matchingEntry[1];
      const entry = (_a = this.packages.get(name)) == null ? void 0 : _a.find((e) => e.range === range);
      if (!entry) {
        throw new Error(`No entry data for ${query}`);
      }
      if (entry.version !== oldVersion) {
        throw new Error(
          `Expected existing version data for ${query} to be ${oldVersion}, was ${entryData.version}`
        );
      }
      entry.version = newVersion;
    }
  }
  async save(path) {
    await fs__default["default"].writeFile(path, this.toString(), "utf8");
  }
  toString() {
    return this.legacy ? lockfile.stringify(this.data) : NEW_HEADER + parsers.stringifySyml(this.data);
  }
}

exports.Lockfile = Lockfile;
//# sourceMappingURL=Lockfile-1c04a5bb.cjs.js.map
