'use strict';

var fs = require('fs-extra');
var chalk = require('chalk');
var ora = require('ora');
var semver = require('semver');
var minimatch = require('minimatch');
var errors = require('@backstage/errors');
var path = require('path');
var run = require('./run-BcxUFacd.cjs.js');
var index = require('./index-CCLafGQH.cjs.js');
var Lockfile = require('./Lockfile-C7rtIlD6.cjs.js');
var packages = require('./packages-O225IQzG.cjs.js');
var lint = require('./lint-BHw1vR1K.cjs.js');
var cliCommon = require('@backstage/cli-common');
var parallel = require('./parallel-BszNaKyc.cjs.js');
var releaseManifests = require('@backstage/release-manifests');
require('global-agent/bootstrap');
var cliNode = require('@backstage/cli-node');
var replace = require('replace-in-file');

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

var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
var chalk__default = /*#__PURE__*/_interopDefaultCompat(chalk);
var ora__default = /*#__PURE__*/_interopDefaultCompat(ora);
var semver__default = /*#__PURE__*/_interopDefaultCompat(semver);
var replace__default = /*#__PURE__*/_interopDefaultCompat(replace);

var migrate = async (options) => {
  const changed = await migrateMovedPackages({
    pattern: options.pattern,
    skipCodeChanges: options.skipCodeChanges
  });
  if (changed) {
    await runYarnInstall();
  }
};
async function migrateMovedPackages(options) {
  var _a;
  console.log(
    "Checking for moved packages to the @backstage-community namespace..."
  );
  const packages = await cliNode.PackageGraph.listTargetPackages();
  let didAnythingChange = false;
  for (const pkg of packages) {
    const pkgName = pkg.packageJson.name;
    let didPackageChange = false;
    const movedPackages = /* @__PURE__ */ new Map();
    for (const depType of [
      "dependencies",
      "devDependencies",
      "peerDependencies"
    ]) {
      const depsObj = pkg.packageJson[depType];
      if (!depsObj) {
        continue;
      }
      for (const [depName, depVersion] of Object.entries(depsObj)) {
        if ((options == null ? void 0 : options.pattern) && !minimatch.minimatch(depName, options.pattern)) {
          continue;
        }
        let packageInfo;
        try {
          packageInfo = await fs.readJson(
            require.resolve(`${depName}/package.json`, {
              paths: [pkg.dir]
            })
          );
        } catch (ex) {
          console.warn(
            chalk__default.default.yellow(
              `Could not find package.json for ${depName}@${depVersion} in ${pkgName} (${depType})`
            )
          );
          continue;
        }
        const movedPackageName = (_a = packageInfo.backstage) == null ? void 0 : _a.moved;
        if (movedPackageName) {
          movedPackages.set(depName, movedPackageName);
          console.log(
            chalk__default.default.yellow(
              `Found a moved package ${depName}@${depVersion} -> ${movedPackageName} in ${pkgName} (${depType})`
            )
          );
          didPackageChange = true;
          didAnythingChange = true;
          depsObj[movedPackageName] = depsObj[depName];
          delete depsObj[depName];
        }
      }
    }
    if (didPackageChange) {
      await fs.writeJson(path.resolve(pkg.dir, "package.json"), pkg.packageJson, {
        spaces: 2
      });
      if (!(options == null ? void 0 : options.skipCodeChanges)) {
        const files = await replace__default.default({
          files: path.join(pkg.dir, "src", "**"),
          allowEmptyPaths: true,
          processor: (content) => {
            return Array.from(movedPackages.entries()).reduce(
              (newContent, [oldName, newName]) => {
                return newContent.replace(new RegExp(`"${oldName}"`, "g"), `"${newName}"`).replace(new RegExp(`'${oldName}'`, "g"), `'${newName}'`).replace(new RegExp(`${oldName}/`, "g"), `${newName}/`);
              },
              content
            );
          }
        });
        if (files.length > 0) {
          console.log(
            chalk__default.default.green(
              `Updated ${files.length} files in ${pkgName} to use the new package names`
            )
          );
        }
      }
    }
  }
  return didAnythingChange;
}

var migrate$1 = /*#__PURE__*/Object.freeze({
  __proto__: null,
  default: migrate,
  migrateMovedPackages: migrateMovedPackages
});

const DEP_TYPES = [
  "dependencies",
  "devDependencies",
  "peerDependencies",
  "optionalDependencies"
];
const DEFAULT_PATTERN_GLOB = "@backstage/*";
var bump = async (opts) => {
  var _a;
  const lockfilePath = index.paths.resolveTargetRoot("yarn.lock");
  const lockfile = await Lockfile.Lockfile.load(lockfilePath);
  let pattern = opts.pattern;
  if (!pattern) {
    console.log(`Using default pattern glob ${DEFAULT_PATTERN_GLOB}`);
    pattern = DEFAULT_PATTERN_GLOB;
  } else {
    console.log(`Using custom pattern glob ${pattern}`);
  }
  let findTargetVersion;
  let releaseManifest;
  if (semver__default.default.valid(opts.release)) {
    releaseManifest = await releaseManifests.getManifestByVersion({ version: opts.release });
    findTargetVersion = createStrictVersionFinder({
      releaseManifest
    });
  } else {
    if (opts.release === "next") {
      const next = await releaseManifests.getManifestByReleaseLine({
        releaseLine: "next"
      });
      const main = await releaseManifests.getManifestByReleaseLine({
        releaseLine: "main"
      });
      releaseManifest = semver__default.default.gt(next.releaseVersion, main.releaseVersion) ? next : main;
    } else {
      releaseManifest = await releaseManifests.getManifestByReleaseLine({
        releaseLine: opts.release
      });
    }
    findTargetVersion = createVersionFinder({
      releaseLine: opts.releaseLine,
      releaseManifest
    });
  }
  const dependencyMap = await packages.mapDependencies(index.paths.targetDir, pattern);
  const versionBumps = /* @__PURE__ */ new Map();
  const unlocked = Array();
  await parallel.runParallelWorkers({
    parallelismFactor: 4,
    items: dependencyMap.entries(),
    async worker([name, pkgs]) {
      var _a2;
      let target;
      try {
        target = await findTargetVersion(name);
      } catch (error) {
        if (errors.isError(error) && error.name === "NotFoundError") {
          console.log(`Package info not found, ignoring package ${name}`);
          return;
        }
        throw error;
      }
      for (const pkg of pkgs) {
        versionBumps.set(
          pkg.name,
          ((_a2 = versionBumps.get(pkg.name)) != null ? _a2 : []).concat({
            name,
            location: pkg.location,
            range: `^${target}`,
            // TODO(Rugvip): Option to use something else than ^?
            target
          })
        );
      }
    }
  });
  const filter = (name) => minimatch.minimatch(name, pattern);
  await parallel.runParallelWorkers({
    parallelismFactor: 4,
    items: lockfile.keys(),
    async worker(name) {
      var _a2;
      if (!filter(name)) {
        return;
      }
      let target;
      try {
        target = await findTargetVersion(name);
      } catch (error) {
        if (errors.isError(error) && error.name === "NotFoundError") {
          console.log(`Package info not found, ignoring package ${name}`);
          return;
        }
        throw error;
      }
      for (const entry of (_a2 = lockfile.get(name)) != null ? _a2 : []) {
        if (!semver__default.default.satisfies(target, entry.range)) {
          continue;
        }
        unlocked.push({ name, range: entry.range, target });
      }
    }
  });
  console.log();
  if (versionBumps.size === 0 && unlocked.length === 0) {
    console.log(chalk__default.default.green("All Backstage packages are up to date!"));
  } else {
    console.log(chalk__default.default.yellow("Some packages are outdated, updating"));
    console.log();
    if (unlocked.length > 0) {
      const removed = /* @__PURE__ */ new Set();
      for (const { name, range, target } of unlocked) {
        const existingEntry = (_a = lockfile.get(name)) == null ? void 0 : _a.find((e) => e.range === range);
        if ((existingEntry == null ? void 0 : existingEntry.version) === target) {
          continue;
        }
        const key = JSON.stringify({ name, range });
        if (!removed.has(key)) {
          removed.add(key);
          console.log(
            `${chalk__default.default.magenta("unlocking")} ${name}@${chalk__default.default.yellow(
              range
            )} ~> ${chalk__default.default.yellow(target)}`
          );
          lockfile.remove(name, range);
        }
      }
      await lockfile.save(lockfilePath);
    }
    const breakingUpdates = /* @__PURE__ */ new Map();
    await parallel.runParallelWorkers({
      parallelismFactor: 4,
      items: versionBumps.entries(),
      async worker([name, deps]) {
        var _a2;
        const pkgPath = path.resolve(deps[0].location, "package.json");
        const pkgJson = await fs__default.default.readJson(pkgPath);
        for (const dep of deps) {
          console.log(
            `${chalk__default.default.cyan("bumping")} ${dep.name} in ${chalk__default.default.cyan(
              name
            )} to ${chalk__default.default.yellow(dep.range)}`
          );
          for (const depType of DEP_TYPES) {
            if (depType in pkgJson && dep.name in pkgJson[depType]) {
              const oldRange = pkgJson[depType][dep.name];
              pkgJson[depType][dep.name] = dep.range;
              const lockfileEntry = (_a2 = lockfile.get(dep.name)) == null ? void 0 : _a2.find((entry) => entry.range === oldRange);
              if (lockfileEntry) {
                const from = lockfileEntry.version;
                const to = dep.target;
                if (!semver__default.default.satisfies(to, `^${from}`)) {
                  breakingUpdates.set(dep.name, { from, to });
                }
              }
            }
          }
        }
        await fs__default.default.writeJson(pkgPath, pkgJson, { spaces: 2 });
      }
    });
    console.log();
    if (pattern === DEFAULT_PATTERN_GLOB) {
      await bumpBackstageJsonVersion(releaseManifest.releaseVersion);
    } else {
      console.log(
        chalk__default.default.yellow(
          `Skipping backstage.json update as custom pattern is used`
        )
      );
    }
    if (!opts.skipInstall) {
      await runYarnInstall();
    } else {
      console.log();
      console.log(chalk__default.default.yellow(`Skipping yarn install`));
    }
    if (!opts.skipMigrate) {
      console.log();
      const changed = await migrateMovedPackages({
        pattern: opts.pattern
      });
      if (changed && !opts.skipInstall) {
        await runYarnInstall();
      }
    }
    if (breakingUpdates.size > 0) {
      console.log();
      console.log(
        chalk__default.default.yellow("\u26A0\uFE0F  The following packages may have breaking changes:")
      );
      console.log();
      for (const [name, { from, to }] of Array.from(
        breakingUpdates.entries()
      ).sort()) {
        console.log(
          `  ${chalk__default.default.yellow(name)} : ${chalk__default.default.yellow(from)} ~> ${chalk__default.default.yellow(
            to
          )}`
        );
        let path;
        if (name.startsWith("@backstage/plugin-")) {
          path = `plugins/${name.replace("@backstage/plugin-", "")}`;
        } else if (name.startsWith("@backstage/")) {
          path = `packages/${name.replace("@backstage/", "")}`;
        }
        if (path) {
          console.log(
            `    https://github.com/backstage/backstage/blob/master/${path}/CHANGELOG.md`
          );
        }
        console.log();
      }
    } else {
      console.log();
    }
    console.log(chalk__default.default.green("Version bump complete!"));
  }
  console.log();
  const dedupLockfile = await Lockfile.Lockfile.load(lockfilePath);
  const result = dedupLockfile.analyze({
    filter,
    localPackages: cliNode.PackageGraph.fromPackages(
      await cliNode.PackageGraph.listTargetPackages()
    )
  });
  const forbiddenNewRanges = result.newRanges.filter(
    ({ name }) => lint.forbiddenDuplicatesFilter(name)
  );
  if (forbiddenNewRanges.length > 0) {
    throw new Error(
      `Version bump failed for ${forbiddenNewRanges.map((i) => i.name).join(", ")}`
    );
  }
  const allowedDuplicates = result.newRanges.filter(
    ({ name }) => !lint.forbiddenDuplicatesFilter(name)
  );
  if (allowedDuplicates.length > 0) {
    console.log(
      chalk__default.default.yellow(
        "The following packages have duplicates but have been allowed:"
      )
    );
    console.log(chalk__default.default.yellow(allowedDuplicates.map((i) => i.name).join(", ")));
  }
};
function createStrictVersionFinder(options) {
  const releasePackages = new Map(
    options.releaseManifest.packages.map((p) => [p.name, p.version])
  );
  return async function findTargetVersion(name) {
    console.log(`Checking for updates of ${name}`);
    const manifestVersion = releasePackages.get(name);
    if (manifestVersion) {
      return manifestVersion;
    }
    throw new errors.NotFoundError(`Package ${name} not found in release manifest`);
  };
}
function createVersionFinder(options) {
  const {
    releaseLine = "latest",
    packageInfoFetcher = packages.fetchPackageInfo,
    releaseManifest
  } = options;
  const distTag = releaseLine === "main" ? "latest" : releaseLine;
  const found = /* @__PURE__ */ new Map();
  const releasePackages = new Map(
    releaseManifest == null ? void 0 : releaseManifest.packages.map((p) => [p.name, p.version])
  );
  return async function findTargetVersion(name) {
    const existing = found.get(name);
    if (existing) {
      return existing;
    }
    console.log(`Checking for updates of ${name}`);
    const manifestVersion = releasePackages.get(name);
    if (manifestVersion) {
      return manifestVersion;
    }
    const info = await packageInfoFetcher(name);
    const latestVersion = info["dist-tags"].latest;
    if (!latestVersion) {
      throw new Error(`No target 'latest' version found for ${name}`);
    }
    const taggedVersion = info["dist-tags"][distTag];
    if (distTag === "latest" || !taggedVersion) {
      found.set(name, latestVersion);
      return latestVersion;
    }
    const latestVersionDateStr = info.time[latestVersion];
    const taggedVersionDateStr = info.time[taggedVersion];
    if (!latestVersionDateStr) {
      throw new Error(
        `No time available for version '${latestVersion}' of ${name}`
      );
    }
    if (!taggedVersionDateStr) {
      throw new Error(
        `No time available for version '${taggedVersion}' of ${name}`
      );
    }
    const latestVersionRelease = new Date(latestVersionDateStr).getTime();
    const taggedVersionRelease = new Date(taggedVersionDateStr).getTime();
    if (latestVersionRelease > taggedVersionRelease) {
      found.set(name, latestVersion);
      return latestVersion;
    }
    found.set(name, taggedVersion);
    return taggedVersion;
  };
}
async function bumpBackstageJsonVersion(version) {
  const backstageJsonPath = index.paths.resolveTargetRoot(cliCommon.BACKSTAGE_JSON);
  const backstageJson = await fs__default.default.readJSON(backstageJsonPath).catch((e) => {
    if (e.code === "ENOENT") {
      return;
    }
    throw e;
  });
  const prevVersion = backstageJson == null ? void 0 : backstageJson.version;
  if (prevVersion === version) {
    return;
  }
  const { yellow, cyan, green } = chalk__default.default;
  if (prevVersion) {
    const from = encodeURIComponent(prevVersion);
    const to = encodeURIComponent(version);
    const link = `https://backstage.github.io/upgrade-helper/?from=${from}&to=${to}`;
    console.log(
      yellow(
        `Upgraded from release ${green(prevVersion)} to ${green(
          version
        )}, please review these template changes:`
      )
    );
    console.log();
    console.log(`  ${cyan(link)}`);
    console.log();
  } else {
    console.log(
      yellow(
        `Your project is now at version ${version}, which has been written to ${cliCommon.BACKSTAGE_JSON}`
      )
    );
  }
  await fs__default.default.writeJson(
    backstageJsonPath,
    { ...backstageJson, version },
    {
      spaces: 2,
      encoding: "utf8"
    }
  );
}
async function runYarnInstall() {
  const spinner = ora__default.default({
    prefixText: `Running ${chalk__default.default.blue("yarn install")} to install new versions`,
    spinner: "arc",
    color: "green"
  }).start();
  const installOutput = new Array();
  try {
    await run.run("yarn", ["install"], {
      env: {
        FORCE_COLOR: "true",
        // We filter out all of the npm_* environment variables that are added when
        // executing through yarn. This works around an issue where these variables
        // incorrectly override local yarn or npm config in the project directory.
        ...Object.fromEntries(
          Object.entries(process.env).map(
            ([name, value]) => name.startsWith("npm_") ? [name, void 0] : [name, value]
          )
        )
      },
      stdoutLogFunc: (data) => installOutput.push(data),
      stderrLogFunc: (data) => installOutput.push(data)
    });
    spinner.succeed();
  } catch (error) {
    spinner.fail();
    process.stdout.write(Buffer.concat(installOutput));
    throw error;
  }
}

var bump$1 = /*#__PURE__*/Object.freeze({
  __proto__: null,
  bumpBackstageJsonVersion: bumpBackstageJsonVersion,
  createStrictVersionFinder: createStrictVersionFinder,
  createVersionFinder: createVersionFinder,
  default: bump,
  runYarnInstall: runYarnInstall
});

exports.bump = bump$1;
exports.migrate = migrate$1;
//# sourceMappingURL=bump-XGQA1gKg.cjs.js.map
