"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.inFolder = exports.getLernaPackages = exports.execPromise = exports.SEMVER = exports.Auto = exports.validatePluginConfiguration = exports.getCurrentBranch = exports.DEFAULT_PRERELEASE_BRANCHES = exports.InteractiveInit = exports.LabelExistsError = exports.getAutoVersion = exports.determineNextVersion = exports.getPrNumberFromEnv = void 0;
const tslib_1 = require("tslib");
const dotenv_1 = tslib_1.__importDefault(require("dotenv"));
const env_ci_1 = tslib_1.__importDefault(require("env-ci"));
const fs_1 = tslib_1.__importDefault(require("fs"));
const path_1 = tslib_1.__importDefault(require("path"));
const terminal_link_1 = tslib_1.__importDefault(require("terminal-link"));
const log_symbols_1 = tslib_1.__importDefault(require("log-symbols"));
const chalk_1 = tslib_1.__importDefault(require("chalk"));
const parse_author_1 = tslib_1.__importDefault(require("parse-author"));
const semver_1 = require("semver");
const endent_1 = tslib_1.__importDefault(require("endent"));
const url_1 = require("url");
const await_to_js_1 = tslib_1.__importDefault(require("await-to-js"));
const https_proxy_agent_1 = tslib_1.__importDefault(require("https-proxy-agent"));
const config_1 = tslib_1.__importStar(require("./config"));
const git_1 = tslib_1.__importDefault(require("./git"));
const init_1 = tslib_1.__importDefault(require("./init"));
const release_1 = tslib_1.__importStar(require("./release"));
const semver_2 = tslib_1.__importStar(require("./semver"));
const exec_promise_1 = tslib_1.__importDefault(require("./utils/exec-promise"));
const load_plugins_1 = require("./utils/load-plugins");
const logger_1 = tslib_1.__importStar(require("./utils/logger"));
const make_hooks_1 = require("./utils/make-hooks");
const get_current_branch_1 = require("./utils/get-current-branch");
const match_sha_to_pr_1 = require("./match-sha-to-pr");
const get_repository_1 = tslib_1.__importDefault(require("./utils/get-repository"));
const validate_config_1 = require("./validate-config");
const omit_1 = require("./utils/omit");
const child_process_1 = require("child_process");
const is_binary_1 = tslib_1.__importDefault(require("./utils/is-binary"));
const git_reset_1 = require("./utils/git-reset");
try {
    if (require.resolve("typescript")) {
        require("ts-node/register/transpile-only");
    }
}
catch (error) {
    // User doesn't have TS installed, cannot write TS plugins
}
const proxyUrl = process.env.https_proxy || process.env.http_proxy;
const env = env_ci_1.default();
/** Make a HTML detail */
const makeDetail = (summary, body) => endent_1.default `
  <details>
    <summary>${summary}</summary>
    <br />

    ${body}
  </details>
`;
/** Load the .env file into process.env. Useful for local usage. */
const loadEnv = () => {
    const envFile = path_1.default.resolve(process.cwd(), ".env");
    if (!fs_1.default.existsSync(envFile)) {
        return;
    }
    const envConfig = dotenv_1.default.parse(fs_1.default.readFileSync(envFile));
    Object.entries(envConfig).forEach(([key, value]) => {
        process.env[key] = value;
    });
};
/** Get the pr number from user input or the CI env. */
function getPrNumberFromEnv(pr) {
    const envPr = "pr" in env && Number(env.pr);
    const prNumber = pr || envPr;
    return prNumber;
}
exports.getPrNumberFromEnv = getPrNumberFromEnv;
/**
 * Bump the version but no too much.
 *
 * @example
 * currentVersion = 1.0.0
 * nextVersion = 2.0.0-next.0
 * output = 2.0.0-next.1
 */
function determineNextVersion(lastVersion, currentVersion, bump, tag) {
    const next = semver_1.inc(lastVersion, `pre${bump}`, tag);
    return !next || semver_1.lte(next, currentVersion)
        ? semver_1.inc(currentVersion, "prerelease", tag) || "prerelease"
        : next;
}
exports.determineNextVersion = determineNextVersion;
/** Print the current version of "auto" */
function getAutoVersion() {
    const packagePath = path_1.default.join(__dirname, "../package.json");
    const packageJson = JSON.parse(fs_1.default.readFileSync(packagePath, "utf8"));
    return packageJson.version;
}
exports.getAutoVersion = getAutoVersion;
/** Escape a string for use in a Regex */
function escapeRegExp(str) {
    return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
}
/** Check if a repo has a branch */
function hasBranch(branch) {
    try {
        const branches = child_process_1.execSync("git branch --list --all", {
            encoding: "utf-8",
        }).split("\n");
        return branches.some((b) => {
            const parts = b.split("/");
            return b === branch || parts[parts.length - 1] === branch;
        });
    }
    catch (error) {
        return false;
    }
}
/** The Error that gets thrown when a label existence check fails */
class LabelExistsError extends Error {
    /**
     * @param label - the label we were checking existence for
     */
    constructor(label) {
        super(`The label ${label} does not exist`);
        Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
    }
}
exports.LabelExistsError = LabelExistsError;
/**
 * The "auto" node API. Its public interface matches the
 * commands you can run from the CLI
 */
class Auto {
    /** Initialize auto and it's environment */
    constructor(options = {}) {
        /** Check if `git status` is clean. */
        this.checkClean = async () => {
            const status = await exec_promise_1.default("git", ["status", "--porcelain"]);
            if (!status) {
                return;
            }
            this.logger.log.error("Changed Files:\n", status);
            throw new Error("Working directory is not clean, make sure all files are committed");
        };
        /** Prefix a version with a "v" if needed */
        this.prefixRelease = (release) => {
            var _a;
            if (!this.release) {
                throw this.createErrorMessage();
            }
            return ((_a = this.config) === null || _a === void 0 ? void 0 : _a.noVersionPrefix) || release.startsWith("v")
                ? release
                : `v${release}`;
        };
        this.options = options;
        this.baseBranch =
            options.baseBranch || (hasBranch("main") && "main") || "master";
        logger_1.setLogLevel("quiet" in options && options.quiet
            ? "quiet"
            : Array.isArray(options.verbose) && options.verbose.length > 1
                ? "veryVerbose"
                : options.verbose
                    ? "verbose"
                    : undefined);
        this.logger = logger_1.default();
        this.hooks = make_hooks_1.makeHooks();
        this.hooks.getRepository.tapPromise("Get repo info from origin", get_repository_1.default);
        this.hooks.onCreateRelease.tap("Link onCreateChangelog", (release) => {
            release.hooks.onCreateChangelog.tap("Link onCreateChangelog", (changelog, bump) => {
                this.hooks.onCreateChangelog.call(changelog, { bump });
            });
        });
        this.hooks.onCreateRelease.tap("Link onCreateLogParse", (release) => {
            release.hooks.onCreateLogParse.tap("Link onCreateLogParse", (logParse) => {
                this.hooks.onCreateLogParse.call(logParse);
            });
        });
        this.hooks.beforeCommitChangelog.tapPromise("Old Version Branches", async ({ bump }) => {
            var _a;
            if (bump === semver_2.default.major && ((_a = this.config) === null || _a === void 0 ? void 0 : _a.versionBranches)) {
                const branch = `${this.config.versionBranches}${semver_1.major(await this.hooks.getPreviousVersion.promise())}`;
                await exec_promise_1.default("git", ["branch", branch]);
                this.logger.log.success(`Created old version branch: ${branch}`);
                await exec_promise_1.default("git", ["push", this.remote, branch]);
            }
        });
        /**
         * Determine if repo is behind HEAD of current branch. We do this in
         * the "afterVersion" hook so the check happens as late as possible.
         */
        this.hooks.afterVersion.tapPromise("Check remote for commits", async ({ dryRun }) => {
            if (dryRun) {
                return;
            }
            // Credit from https://github.com/semantic-release/semantic-release/blob/b2b7b57fbd51af3fe25accdd6cd8499beb9005e5/lib/git.js#L179
            // `true` is the HEAD of the current local branch is the same as the HEAD of the remote branch, falsy otherwise.
            try {
                const currentBranch = get_current_branch_1.getCurrentBranch();
                const heads = await exec_promise_1.default("git", [
                    "ls-remote",
                    "--heads",
                    this.remote,
                    currentBranch,
                ]);
                this.logger.verbose.info("Branch:", currentBranch);
                this.logger.verbose.info("HEADs:", heads);
                const baseBranchHeadRef = new RegExp(`^(\\w+)\\s+refs/heads/${this.baseBranch}$`);
                const [, remoteHead] = heads.match(baseBranchHeadRef) || [];
                if (remoteHead) {
                    // This will throw if the branch is ahead of the current branch
                    child_process_1.execSync(`git merge-base --is-ancestor ${remoteHead} HEAD`, {
                        stdio: "ignore",
                    });
                }
                this.logger.verbose.info("Current branch is up to date, proceeding with release");
            }
            catch (error) {
                // If we are behind or there is no match, exit and skip the release
                this.logger.log.warn("Current commit is behind, skipping the release to avoid collisions.");
                this.logger.verbose.warn(error);
                process.exit(0);
            }
        });
        loadEnv();
        this.logger.verbose.info("ENV:", env);
    }
    /** List some of the plugins available to auto */
    async listPlugins() {
        await load_plugins_1.listPlugins(this.config, this.logger, this.getExtendedLocation(this.config));
    }
    /**
     * Load the default hook behaviors. Should run after loadPlugins so
     * plugins take precedence.
     */
    loadDefaultBehavior() {
        this.hooks.makeRelease.tapPromise("Default", async (options) => {
            if (options.dryRun) {
                let releaseVersion;
                if (options.useVersion) {
                    releaseVersion = options.useVersion;
                }
                else {
                    const bump = await this.getVersion({ from: options.from });
                    releaseVersion = semver_1.inc(options.newVersion, bump);
                }
                this.logger.log.info(`Would have created a release on GitHub for version: ${releaseVersion}`);
                this.logger.log.note('The above version would only get released if ran with "shipit" or a custom script that bumps the version using the "version" command');
            }
            else {
                this.logger.log.info(`Releasing ${options.newVersion} to GitHub.`);
                const release = await this.git.publish(options.fullReleaseNotes, options.newVersion, options.isPrerelease, options.to);
                this.logger.log.info(release.data.html_url);
                return release;
            }
        });
    }
    /**
     * Load the .autorc from the file system, set up defaults, combine with CLI args
     * load the extends property, load the plugins and start the git remote interface.
     */
    async loadConfig() {
        const configLoader = new config_1.default(this.logger);
        const userConfig = await configLoader.loadConfig();
        this.logger.verbose.success("Loaded `auto` with config:", userConfig);
        // Allow plugins to be overriden for testing
        this.config = Object.assign(Object.assign({}, userConfig), { plugins: this.options.plugins || userConfig.plugins });
        this.loadPlugins(this.config);
        this.loadDefaultBehavior();
        this.config = await this.hooks.modifyConfig.promise(this.config);
        this.labels = this.config.labels;
        this.semVerLabels = release_1.getVersionMap(this.config.labels);
        await this.hooks.beforeRun.promise(this.config);
        const errors = [
            ...(await validate_config_1.validateAutoRc(this.config)),
            ...(await validate_config_1.validatePlugins(this.hooks.validateConfig, this.config)),
        ];
        if (errors.length) {
            this.logger.log.error(endent_1.default `
          Found configuration errors:

          ${errors.map(validate_config_1.formatError).join("\n")}
        `, "\n");
            this.logger.log.warn("These errors are for the fully loaded configuration (this is why some paths might seem off).");
            if (this.config.extends) {
                this.logger.log.warn("Some errors might originate from an extend config.");
            }
            process.exit(1);
        }
        const config = Object.assign(Object.assign(Object.assign({}, this.config), omit_1.omit(this.options, ["_command", "_all", "main"])), { baseBranch: this.config.baseBranch || this.baseBranch });
        this.config = config;
        this.baseBranch = config.baseBranch;
        const repository = await this.getRepo(config);
        const token = (repository === null || repository === void 0 ? void 0 : repository.token) || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
        if (!token || token === "undefined") {
            this.logger.log.error("No GitHub was found. Make sure it is available on process.env.GH_TOKEN.");
            throw new Error("GitHub token not found!");
        }
        const githubOptions = Object.assign(Object.assign({ owner: config.owner, repo: config.repo }, repository), { token, agent: proxyUrl ? https_proxy_agent_1.default(proxyUrl) : undefined, baseUrl: config.githubApi, graphqlBaseUrl: config.githubGraphqlApi });
        this.git = this.startGit(githubOptions);
        this.release = new release_1.default(this.git, config, this.logger);
        this.remote = await this.getRemote();
        this.logger.verbose.info(`Using remote: ${this.remote.replace(token, `****${token.slice(-4)}`)}`);
        this.hooks.onCreateRelease.call(this.release);
        return config;
    }
    /** Determine the remote we have auth to push to. */
    async getRemote() {
        const [, configuredRemote = "origin"] = await await_to_js_1.default(exec_promise_1.default("git", ["remote", "get-url", "origin"]));
        if (!this.git) {
            return configuredRemote;
        }
        const { html_url, ssh_url } = (await this.git.getProject()) || {
            html_url: "",
            ssh_url: "",
        };
        const GIT_TOKENS = {
            // GitHub Actions require the "x-access-token:" prefix for git access
            // https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/#http-based-git-access-by-an-installation
            GITHUB_TOKEN: process.env.GITHUB_ACTION
                ? `x-access-token:${process.env.GITHUB_TOKEN}`
                : undefined,
        };
        const envVar = Object.keys(GIT_TOKENS).find((v) => process.env[v]) || "";
        const gitCredentials = GIT_TOKENS[envVar] || process.env.GH_TOKEN;
        if (ssh_url && (await this.git.verifyAuth(ssh_url))) {
            this.logger.veryVerbose.note("Using ssh URL as remote");
            return ssh_url;
        }
        if (gitCredentials) {
            const _a = url_1.parse(html_url), { port, hostname } = _a, parsed = tslib_1.__rest(_a, ["port", "hostname"]);
            const urlWithAuth = url_1.format(Object.assign(Object.assign({}, parsed), { auth: gitCredentials, host: `${hostname}${port ? `:${port}` : ""}` }));
            if (await this.git.verifyAuth(urlWithAuth)) {
                this.logger.veryVerbose.note("Using token + html URL as remote");
                return urlWithAuth;
            }
        }
        if (html_url && (await this.git.verifyAuth(html_url))) {
            this.logger.veryVerbose.note("Using bare html URL as remote");
            return html_url;
        }
        this.logger.veryVerbose.note("Using remote set in environment");
        return configuredRemote;
    }
    /** Interactive prompt for initializing an .autorc */
    async init() {
        const init = new init_1.default(this);
        await init.run();
    }
    /** Check if auto is set up correctly */
    async info(args) {
        var _a, _b, _c;
        if (!this.git) {
            return { hasError: false };
        }
        const [, gitVersion = ""] = await await_to_js_1.default(exec_promise_1.default("git", ["--version"]));
        const [noProject, project] = await await_to_js_1.default(this.git.getProject());
        const repo = (await this.getRepo(this.config)) || {};
        const repoLink = terminal_link_1.default(`${repo.owner}/${repo.repo}`, (_a = project === null || project === void 0 ? void 0 : project.html_url) !== null && _a !== void 0 ? _a : "");
        const author = (await this.getGitUser()) || {};
        const [, lastRelease = "0.0.0"] = await await_to_js_1.default(this.git.getLatestRelease());
        const version = await this.getCurrentVersion(lastRelease);
        const [err, latestRelease] = await await_to_js_1.default(this.git.getLatestReleaseInfo());
        const latestReleaseLink = latestRelease
            ? terminal_link_1.default(latestRelease.tag_name, latestRelease.html_url)
            : "";
        const { headers } = await this.git.github.request("HEAD /");
        const access = headers;
        const rateLimitRefresh = new Date(Number(access["x-ratelimit-reset"]) * 1000);
        const token = this.git.options.token || "";
        const tokenRefresh = `${rateLimitRefresh.toLocaleTimeString()} ${rateLimitRefresh.toLocaleDateString("en-us")}`;
        const projectLabels = await this.git.getProjectLabels();
        const hasLabels = (_b = this.config) === null || _b === void 0 ? void 0 : _b.labels.reduce((acc, label) => {
            var _a, _b;
            if (label.name === "release" &&
                !((_a = this.config) === null || _a === void 0 ? void 0 : _a.onlyPublishWithReleaseLabel)) {
                return acc;
            }
            if (label.name === "skip-release" &&
                ((_b = this.config) === null || _b === void 0 ? void 0 : _b.onlyPublishWithReleaseLabel)) {
                return acc;
            }
            return acc && projectLabels.includes(label.name);
        }, true);
        const { permission, user } = (await this.git.getTokenPermissionLevel()) || {};
        let hasError = false;
        /** Log if a configuration is correct. */
        const logSuccess = (err) => {
            if (err) {
                hasError = true;
                return log_symbols_1.default.error;
            }
            return log_symbols_1.default.success;
        };
        console.log("");
        // prettier-ignore
        console.log(endent_1.default `
      ${chalk_1.default.underline.white('Environment Information:')}

      "auto" version: v${getAutoVersion()}
      "git"  version: v${gitVersion.replace('git version ', '')}
      "node" version: ${process.version.trim()}${access['x-github-enterprise-version']
            ? `\nGHE version:    v${access['x-github-enterprise-version']}\n`
            : '\n'}
      ${chalk_1.default.underline.white('Project Information:')}

      ${logSuccess(noProject)} Repository:      ${repoLink}
      ${logSuccess(!author.name)} Author Name:     ${author.name}
      ${logSuccess(!author.email)} Author Email:    ${author.email}
      ${logSuccess(!version)} Current Version: ${this.prefixRelease(version)}
      ${logSuccess(err)} Latest Release:  ${latestReleaseLink}

      ${logSuccess(!hasLabels)} Labels configured on GitHub project ${hasLabels ? '' : '(Try running "auto create-labels")'}

      ${chalk_1.default.underline.white('GitHub Token Information:')}

      ${logSuccess(!token)} Token:            ${`[Token starting with ${token.substring(0, 4)}]`}
      ${logSuccess(!(permission === 'admin' || permission === 'write'))} Repo Permission:  ${permission}
      ${logSuccess(!(user === null || user === void 0 ? void 0 : user.login))} User:             ${user === null || user === void 0 ? void 0 : user.login}
      ${logSuccess()} API:              ${terminal_link_1.default(this.git.options.baseUrl, this.git.options.baseUrl)}
      ${logSuccess(!((_c = access['x-oauth-scopes']) === null || _c === void 0 ? void 0 : _c.includes('repo')))} Enabled Scopes:   ${access['x-oauth-scopes']}
      ${logSuccess(Number(access['x-ratelimit-remaining']) === 0)} Rate Limit:       ${access['x-ratelimit-remaining'] || '∞'}/${access['x-ratelimit-limit'] || '∞'} ${access['ratelimit-reset'] ? `(Renews @ ${tokenRefresh})` : ''}
    `);
        console.log("");
        if (args.listPlugins) {
            await this.listPlugins();
        }
        return { hasError };
    }
    /** Determine if the repo is currently in a prerelease branch */
    inPrereleaseBranch() {
        var _a, _b;
        const branch = get_current_branch_1.getCurrentBranch();
        const prereleaseBranches = (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.prereleaseBranches) !== null && _b !== void 0 ? _b : config_1.DEFAULT_PRERELEASE_BRANCHES;
        return Boolean(branch && prereleaseBranches.includes(branch));
    }
    /** Determine if the repo is currently in a old-version branch */
    inOldVersionBranch() {
        var _a;
        const branch = get_current_branch_1.getCurrentBranch();
        const oldVersionBranchPrefix = (_a = this.config) === null || _a === void 0 ? void 0 : _a.versionBranches;
        return Boolean(oldVersionBranchPrefix &&
            branch &&
            new RegExp(`^${escapeRegExp(oldVersionBranchPrefix)}`).test(branch));
    }
    /**
     * Create all of the user's labels on the git remote if the don't already exist
     */
    async createLabels(options = {}) {
        if (!this.release || !this.labels) {
            throw this.createErrorMessage();
        }
        await this.release.addLabelsToProject(this.labels, options);
    }
    /**
     * Get the labels on a specific PR. Defaults to the labels of the last merged PR
     */
    async label({ pr, exists } = {}) {
        if (!this.git) {
            throw this.createErrorMessage();
        }
        this.logger.verbose.info("Using command: 'label'");
        const number = getPrNumberFromEnv(pr);
        let labels = [];
        if (number) {
            labels = await this.git.getLabels(number);
        }
        else {
            const pulls = await this.git.getPullRequests({
                state: "closed",
            });
            const lastMerged = pulls
                .sort((a, b) => {
                const aDate = a.merged_at ? new Date(a.merged_at).getTime() : 0;
                const bDate = b.merged_at ? new Date(b.merged_at).getTime() : 0;
                return bDate - aDate;
            })
                .find((pull) => pull.merged_at);
            if (lastMerged) {
                labels = lastMerged.labels
                    .map((label) => label.name)
                    .filter((l) => Boolean(l));
            }
        }
        if (exists) {
            if (labels.indexOf(exists) === -1) {
                throw new LabelExistsError(exists);
            }
            return;
        }
        if (labels.length) {
            console.log(labels.join("\n"));
        }
    }
    /**
     * Create a status on a PR.
     */
    async prStatus(_a) {
        var { dryRun, pr, url } = _a, options = tslib_1.__rest(_a, ["dryRun", "pr", "url"]);
        if (!this.git) {
            throw this.createErrorMessage();
        }
        let { sha } = options;
        let prNumber;
        try {
            prNumber = this.getPrNumber("pr", pr);
        }
        catch (error) {
            // default to sha if no PR found
        }
        this.logger.verbose.info("Using command: 'pr-status'");
        if (!sha && prNumber) {
            this.logger.verbose.info("Getting commit SHA from PR.");
            const res = await this.git.getPullRequest(prNumber);
            sha = res.data.head.sha;
        }
        else if (!sha) {
            this.logger.verbose.info("No PR found, getting commit SHA from HEAD.");
            sha = await this.git.getSha();
        }
        this.logger.verbose.info("Found PR SHA:", sha);
        const target_url = url;
        if (dryRun) {
            this.logger.verbose.info("`pr` dry run complete.");
        }
        else {
            try {
                await this.git.createStatus(Object.assign(Object.assign({}, options), { sha,
                    target_url }));
            }
            catch (error) {
                throw new Error(`Failed to post status to Pull Request with error code ${error.status}`);
            }
            this.logger.log.success("Posted status to Pull Request.");
        }
        this.logger.verbose.success("Finished `pr` command");
    }
    /**
     * Check that a PR has a SEMVER label. Set a success status on the PR.
     */
    async prCheck(_a) {
        var _b, _c;
        var { dryRun, pr, url } = _a, options = tslib_1.__rest(_a, ["dryRun", "pr", "url"]);
        if (!this.git || !this.release || !this.semVerLabels) {
            throw this.createErrorMessage();
        }
        this.logger.verbose.info(`Using command: 'pr-check' for '${url}'`);
        const target_url = url;
        const prNumber = getPrNumberFromEnv(pr);
        if (!prNumber) {
            // If pr-check is ran on CI on baseBranch then we exit successfully since
            // running pr-check in this scenario wouldn't make sense anyway. Enables
            // adding this command without resorting to bash if/else statements.
            if (env.isCi &&
                (env.branch === this.baseBranch || this.inPrereleaseBranch())) {
                process.exit(0);
            }
            // Otherwise the command should fail since no PR number was provided or found
            this.logger.log.error(endent_1.default `
          Could not detect PR number. pr-check must be run from either a PR or have the PR number supplied via the --pr flag.

          In some CIs your branch might be built before you open a PR and posting the canary version will fail. In this case subsequent builds should succeed.
        `);
            process.exit(1);
        }
        let msg;
        let sha;
        try {
            const res = await this.git.getPullRequest(prNumber);
            await this.hooks.prCheck.promise({ pr: res.data, dryRun });
            sha = res.data.head.sha;
            const labels = await this.git.getLabels(prNumber);
            const labelValues = [...this.semVerLabels.values()];
            const releaseTag = labels.find((l) => l === "release");
            const skipReleaseLabels = (((_b = this.config) === null || _b === void 0 ? void 0 : _b.labels.filter((l) => l.releaseType === "skip")) || []).map((l) => l.name);
            const skipReleaseTag = labels.find((l) => skipReleaseLabels.includes(l));
            const semverTag = labels.find((l) => labelValues.some((labelValue) => labelValue.includes(l)) &&
                !skipReleaseLabels.includes(l) &&
                l !== "release");
            const branch = get_current_branch_1.getCurrentBranch();
            if (branch && ((_c = this.config) === null || _c === void 0 ? void 0 : _c.prereleaseBranches.includes(branch))) {
                msg = {
                    description: "PR will graduate prerelease once merged",
                    state: "success",
                };
            }
            else if (semverTag === undefined && !skipReleaseTag) {
                throw new Error("No semver label!");
            }
            else {
                this.logger.log.success(`PR is using label: ${semverTag || skipReleaseTag}`);
                let description;
                if (skipReleaseTag) {
                    description = "PR will not create a release";
                }
                else if (releaseTag) {
                    description = `PR will create release once merged - ${semverTag}`;
                }
                else {
                    description = `CI - ${semverTag}`;
                }
                msg = {
                    description,
                    state: "success",
                };
            }
        }
        catch (error) {
            msg = {
                description: error.message,
                state: "error",
            };
        }
        this.logger.verbose.info("Posting status to GitHub\n", msg);
        if (dryRun) {
            this.logger.verbose.info("`pr-check` dry run complete.");
        }
        else {
            try {
                await this.git.createStatus(Object.assign(Object.assign(Object.assign({}, options), msg), { target_url,
                    sha }));
                this.logger.log.success("Posted status to Pull Request.");
            }
            catch (error) {
                throw new Error(`Failed to post status to Pull Request with error code ${error.status}`);
            }
        }
        this.logger.verbose.success("Finished `pr-check` command");
    }
    /**
     * Comment on a PR. Only one comment will be present on the PR, Older comments are removed.
     * You can use the "context" option to multiple comments on a PR.
     */
    async comment(args) {
        const options = Object.assign(Object.assign({}, this.getCommandDefault("comment")), args);
        const { message, pr, context = "default", dryRun, delete: deleteFlag, edit: editFlag, } = options;
        if (!this.git) {
            throw this.createErrorMessage();
        }
        this.logger.verbose.info("Using command: 'comment'");
        const prNumber = this.getPrNumber("comment", pr);
        if (dryRun) {
            if (deleteFlag) {
                this.logger.log.info(`Would have deleted comment on ${prNumber} under "${context}" context`);
            }
            else if (editFlag) {
                this.logger.log.info(`Would have edited the comment on ${prNumber} under "${context}" context.\n\nNew message: ${message}`);
            }
            else {
                this.logger.log.info(`Would have commented on ${prNumber} under "${context}" context:\n\n${message}`);
            }
        }
        else if (editFlag && message) {
            await this.git.editComment(message, prNumber, context);
            this.logger.log.success(`Edited comment on PR #${prNumber} under context "${context}"`);
        }
        else {
            if (deleteFlag) {
                await this.git.deleteComment(prNumber, context);
                this.logger.log.success(`Deleted comment on PR #${prNumber} under context "${context}"`);
            }
            if (message) {
                await this.git.createComment(message, prNumber, context);
                this.logger.log.success(`Commented on PR #${prNumber}`);
            }
        }
    }
    /**
     * Update the body of a PR with a message. Only one message will be present in the PR,
     * Older messages are removed. You can use the "context" option to multiple message
     * in a PR body.
     */
    async prBody(options) {
        const { message, pr, context = "default", dryRun, delete: deleteFlag, } = options;
        if (!this.git) {
            throw this.createErrorMessage();
        }
        this.logger.verbose.info("Using command: 'pr-body'");
        const prNumber = this.getPrNumber("pr-body", pr);
        if (dryRun) {
            if (deleteFlag) {
                this.logger.log.info(`Would have deleted PR body on ${prNumber} under "${context}" context`);
            }
            else {
                this.logger.log.info(`Would have appended to PR body on ${prNumber} under "${context}" context:\n\n${message}`);
            }
        }
        else {
            if (deleteFlag) {
                await this.git.addToPrBody("", prNumber, context);
            }
            if (message) {
                await this.git.addToPrBody(message, prNumber, context);
            }
            this.logger.log.success(`Updated body on PR #${prNumber}`);
        }
    }
    /**
     * Calculate the version bump for the current state of the repository.
     */
    async version(options = {}) {
        this.logger.verbose.info("Using command: 'version'");
        const bump = await this.getVersion(options);
        console.log(bump);
    }
    /**
     * Calculate the the changelog and commit it.
     */
    async changelog(options) {
        this.logger.verbose.info("Using command: 'changelog'");
        await this.makeChangelog(options);
    }
    /**
     * Make a release to the git remote with the changes.
     */
    async runRelease(options = {}) {
        this.logger.verbose.info("Using command: 'release'");
        await this.makeRelease(options);
    }
    /** Create a canary (or test) version of the project */
    // eslint-disable-next-line complexity
    async canary(args = {}) {
        const options = Object.assign(Object.assign({}, this.getCommandDefault("canary")), args);
        if (!this.git || !this.release) {
            throw this.createErrorMessage();
        }
        if (!this.hooks.canary.isUsed()) {
            this.logger.log.warn(endent_1.default `
        None of the plugins that you are using implement the \`canary\` command!

        "canary" releases are versions that are used solely to test changes. They make sense on some platforms (ex: npm) but not all!

        If you think your package manager has the ability to support canaries please file an issue or submit a pull request,
      `);
            process.exit(0);
        }
        if (!options.dryRun) {
            await this.checkClean();
        }
        let { pr, build } = await this.getPrEnvInfo();
        pr = options.pr ? String(options.pr) : pr;
        build = options.build ? String(options.build) : build;
        this.logger.verbose.info("Canary info found:", { pr, build });
        const from = (await this.git.shaExists("HEAD^")) ? "HEAD^" : "HEAD";
        const commitsInRelease = await this.release.getCommitsInRelease(from);
        this.logger.veryVerbose.info("Found commits in canary release", commitsInRelease);
        const labels = commitsInRelease.map((commit) => commit.labels);
        if (pr) {
            const prLabels = await this.git.getLabels(Number(pr));
            labels.unshift(prLabels);
        }
        let bump = semver_2.calculateSemVerBump(labels, this.semVerLabels, this.config);
        if (bump === semver_2.default.noVersion) {
            if (options.force) {
                bump = semver_2.default.patch;
            }
            else {
                this.logger.log.info("Skipping canary release due to PR specifying no release. Use `auto canary --force` to override this setting");
                return;
            }
        }
        let canaryIdentifier = "";
        let newVersion = "";
        if (pr) {
            canaryIdentifier = `${canaryIdentifier}.${pr}`;
        }
        if (build) {
            canaryIdentifier = `${canaryIdentifier}.${build}`;
        }
        if (!pr || !build) {
            canaryIdentifier = `${canaryIdentifier}.${(await this.git.getSha(true)).slice(0, 7)}`;
        }
        canaryIdentifier = `-canary${canaryIdentifier}`;
        this.logger.verbose.info("Calling canary hook");
        const result = await this.hooks.canary.promise({
            bump,
            canaryIdentifier,
            dryRun: args.dryRun,
            quiet: args.quiet,
        });
        if (typeof result === "object" && "error" in result) {
            this.logger.log.warn(result.error);
            return;
        }
        if (!result) {
            return;
        }
        newVersion = typeof result === "string" ? result : result.newVersion;
        const messageHeader = (options.message || "📦 Published PR as canary version: %v").replace("%v", !newVersion || newVersion.includes("\n")
            ? newVersion
            : `<code>${newVersion}</code>`);
        if (!options.dryRun && options.message !== "false" && pr) {
            const prNumber = Number(pr);
            const message = typeof result === "string"
                ? messageHeader
                : makeDetail(messageHeader, result.details);
            switch (options.target) {
                case "comment":
                    await this.comment({
                        pr: prNumber,
                        context: "canary-version",
                        message,
                    });
                    break;
                case "status":
                    await this.prStatus({
                        pr: prNumber,
                        context: "canary-version",
                        description: messageHeader,
                        state: "success",
                        url: "buildUrl" in env ? env.buildUrl : "",
                    });
                    break;
                default:
                    await this.prBody({
                        pr: prNumber,
                        context: "canary-version",
                        message,
                    });
                    break;
            }
        }
        const verb = options.dryRun ? "Would have published" : "Published";
        this.logger.log.success(`${verb} canary version${newVersion ? `: ${newVersion}` : ""}`);
        if (args.quiet) {
            console.log(newVersion);
        }
        if (!options.dryRun) {
            await git_reset_1.gitReset();
        }
        return { newVersion, commitsInRelease, context: "canary" };
    }
    /**
     * Create a next (or test) version of the project. If on baseBranch will
     * release to the default "next" branch.
     */
    async next(args) {
        const options = Object.assign(Object.assign({}, this.getCommandDefault("next")), args);
        if (!this.git || !this.release) {
            throw this.createErrorMessage();
        }
        if (!this.hooks.next.isUsed()) {
            this.logger.log.warn(endent_1.default `
        None of the plugins that you are using implement the \`next\` command!

        "next" releases are pre-releases such as betas or alphas. They make sense on some platforms (ex: npm) but not all!

        If you think your package manager has the ability to support "next" releases please file an issue or submit a pull request,
      `);
            process.exit(0);
        }
        if (!options.dryRun) {
            await this.checkClean();
        }
        await this.setGitUser();
        this.hooks.onCreateLogParse.tap(`Omit merges from ${this.baseBranch}`, (logParse) => {
            logParse.hooks.omitCommit.tap(`Omit merges from ${this.baseBranch}`, (commit) => {
                const shouldOmit = commit.subject.match(new RegExp(`^Merge (?:\\S+\\/)*${this.baseBranch}`));
                if (shouldOmit) {
                    this.logger.verbose.info(`Omitting merge commit from ${this.baseBranch}: ${commit.subject}`);
                    return true;
                }
            });
        });
        const currentBranch = get_current_branch_1.getCurrentBranch();
        const baseBranch = (await this.git.shaExists(`origin/${this.baseBranch}`))
            ? `origin/${this.baseBranch}`
            : this.baseBranch;
        const forkPoints = (await exec_promise_1.default("git", [
            "rev-list",
            "--boundary",
            `${currentBranch}...${baseBranch}`,
            "--left-only",
        ]))
            .split("\n")
            .filter((line) => line.startsWith("-"));
        const initialForkCommit = (forkPoints[forkPoints.length - 1] || "").slice(1);
        const lastRelease = initialForkCommit || (await this.git.getLatestRelease());
        const [, lastTagNotInBaseBranch] = await await_to_js_1.default(this.git.getLastTagNotInBaseBranch(currentBranch));
        const [, latestTagInBranch] = await await_to_js_1.default(this.git.getLatestTagInBranch(currentBranch));
        const [, tagsInBaseBranch] = await await_to_js_1.default(this.git.getTags(baseBranch));
        const [latestTagInBaseBranch] = (tagsInBaseBranch || []).reverse();
        const lastTag = lastTagNotInBaseBranch ||
            latestTagInBranch ||
            latestTagInBaseBranch ||
            (await this.git.getFirstCommit());
        const fullReleaseNotes = await this.release.generateReleaseNotes(lastRelease);
        const commits = await this.release.getCommitsInRelease(lastTag);
        const releaseNotes = await this.release.generateReleaseNotes(lastTag);
        const labels = commits.map((commit) => commit.labels);
        let bump = semver_2.calculateSemVerBump(labels, this.semVerLabels, this.config);
        if (bump === semver_2.default.noVersion) {
            if (options.force) {
                bump = semver_2.default.patch;
            }
            else {
                this.logger.log.info("No version published.");
                return;
            }
        }
        if (!args.quiet) {
            this.logger.log.info("Full Release notes for next release:");
            console.log(fullReleaseNotes);
            if (releaseNotes) {
                this.logger.log.info("Release notes for last change in next release");
                console.log(releaseNotes);
            }
        }
        this.logger.verbose.info(`Calling "next" hook with: ${bump}`);
        const result = await this.hooks.next.promise([], {
            bump,
            commits,
            fullReleaseNotes,
            releaseNotes,
            dryRun: args.dryRun,
        });
        if (args.dryRun) {
            console.log(result.join("\n"));
            return;
        }
        const newVersion = result.join(", ");
        const release = await this.hooks.makeRelease.promise({
            commits,
            from: lastTag,
            to: await this.git.getSha(),
            isPrerelease: true,
            newVersion,
            fullReleaseNotes: releaseNotes,
        });
        this.logger.verbose.info(release);
        if (release) {
            await this.hooks.afterRelease.promise({
                lastRelease: lastTag,
                newVersion,
                commits,
                releaseNotes,
                response: release,
            });
        }
        this.logger.log.success(`Published next version${result.length > 1 ? `s` : ""}: ${newVersion}`);
        const { pr } = await this.getPrEnvInfo();
        if (pr) {
            const message = options.message || "Published prerelease version: %v";
            if (pr) {
                await this.prBody({
                    pr: Number(pr),
                    context: "prerelease-version",
                    message: endent_1.default `
            # Version

            ${message.replace("%v", result.map((r) => `\`${r}\``).join("\n"))}

            <details>
              <summary>Changelog</summary>

              ${fullReleaseNotes}
            </details>
          `,
                });
            }
        }
        if (options.quiet) {
            console.log(newVersion);
        }
        await git_reset_1.gitReset();
        return { newVersion, commitsInRelease: commits, context: "next" };
    }
    /** Force a release to latest and bypass `shipit` safeguards. */
    async latest(args = {}) {
        const options = Object.assign(Object.assign({}, this.getCommandDefault("latest")), args);
        return this.publishFullRelease(options);
    }
    /**
     * Run the full workflow.
     *
     * 1. Calculate version
     * 2. Make changelog
     * 3. Publish code
     * 4. Create a release
     */
    async shipit(args = {}) {
        var _a, _b;
        const options = Object.assign(Object.assign({}, this.getCommandDefault("shipit")), args);
        if (!this.git || !this.release) {
            throw this.createErrorMessage();
        }
        this.logger.verbose.info("Using command: 'shipit'");
        const isPR = "isPr" in env && env.isPr;
        const from = (await this.git.shaExists("HEAD^")) ? "HEAD^" : "HEAD";
        const head = await this.release.getCommitsInRelease(from);
        // env-ci sets branch to target branch (ex: main) in some CI services.
        // so we should make sure we aren't in a PR just to be safe
        const currentBranch = get_current_branch_1.getCurrentBranch();
        const isBaseBranch = !isPR && currentBranch === this.baseBranch;
        const shouldGraduate = !options.onlyGraduateWithReleaseLabel ||
            (options.onlyGraduateWithReleaseLabel &&
                head[0].labels.some((l) => { var _a, _b; return (_b = (_a = this.semVerLabels) === null || _a === void 0 ? void 0 : _a.get("release")) === null || _b === void 0 ? void 0 : _b.includes(l); }));
        const isPrereleaseBranch = (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.prereleaseBranches) === null || _b === void 0 ? void 0 : _b.some((branch) => currentBranch === branch);
        const publishPrerelease = isPrereleaseBranch ||
            (currentBranch === this.baseBranch &&
                options.onlyGraduateWithReleaseLabel);
        this.logger.veryVerbose.info({
            currentBranch,
            isBaseBranch,
            isPR,
            shouldGraduate,
            isPrereleaseBranch,
            publishPrerelease,
        });
        let publishInfo;
        let releaseType = "canary";
        if (isBaseBranch && shouldGraduate) {
            releaseType = "latest";
        }
        else if (this.inOldVersionBranch()) {
            releaseType = "old";
        }
        else if (publishPrerelease) {
            releaseType = "next";
        }
        await this.hooks.beforeShipIt.promise({
            releaseType,
            dryRun: options.dryRun,
        });
        if (releaseType === "latest") {
            publishInfo = await this.latest(options);
        }
        else if (releaseType === "old") {
            publishInfo = await this.oldRelease(options);
        }
        else if (releaseType === "next") {
            publishInfo = await this.next(options);
        }
        else {
            publishInfo = await this.canary(options);
            if (options.dryRun && !options.quiet) {
                this.logger.log.success(`Below is what would happen upon merge of the current branch into ${this.baseBranch}`);
                await this.publishFullRelease(options);
            }
        }
        if (!publishInfo) {
            return;
        }
        const { newVersion, commitsInRelease, context } = publishInfo;
        await this.hooks.afterShipIt.promise({
            newVersion,
            commitsInRelease,
            context,
            dryRun: args.dryRun,
        });
    }
    /** Get the latest version number of the project */
    async getCurrentVersion(lastRelease) {
        this.hooks.getPreviousVersion.tap("None", () => {
            this.logger.veryVerbose.info("No previous release found, using 0.0.0 as previous version.");
            return this.prefixRelease("0.0.0");
        });
        const lastVersion = await this.hooks.getPreviousVersion.promise();
        if (semver_1.parse(lastRelease) &&
            semver_1.parse(lastVersion) &&
            semver_1.gt(lastRelease, lastVersion)) {
            this.logger.veryVerbose.info("Using latest release as previous version");
            return lastRelease;
        }
        return lastVersion;
    }
    /**
     * A utility function for plugins to check the process for tokens.
     */
    checkEnv(pluginName, key) {
        if (!process.env[key]) {
            this.logger.log.warn(`${pluginName}: No "${key}" found in environment`);
        }
    }
    /** Make a release to an old version */
    async oldRelease(options) {
        var _a;
        const latestTag = await ((_a = this.git) === null || _a === void 0 ? void 0 : _a.getLatestTagInBranch(get_current_branch_1.getCurrentBranch() || ""));
        const result = await this.publishFullRelease(Object.assign(Object.assign({}, options), { from: latestTag }));
        if (result) {
            result.context = "old";
        }
        return result;
    }
    /** Publish a new version with changelog, publish, and release */
    async publishFullRelease(options) {
        if (!this.git || !this.release) {
            throw this.createErrorMessage();
        }
        const bump = await this.getVersion(options);
        this.logger.log.success(`Calculated version bump: ${options.useVersion || bump || "none"}`);
        if (bump === "" && !options.useVersion) {
            this.logger.log.info("No version published.");
            return;
        }
        const lastRelease = options.from || (await this.git.getLatestRelease());
        const commitsInRelease = await this.release.getCommitsInRelease(lastRelease);
        await this.makeChangelog(Object.assign(Object.assign({}, options), { quiet: undefined, noCommit: options.noChangelog }));
        if (!options.dryRun) {
            await this.checkClean();
        }
        this.logger.verbose.info("Calling version hook");
        await this.hooks.version.promise({
            bump,
            useVersion: options.useVersion,
            dryRun: options.dryRun,
            quiet: options.quiet,
        });
        this.logger.verbose.info("Calling after version hook");
        await this.hooks.afterVersion.promise({ dryRun: options.dryRun });
        if (!options.dryRun) {
            this.logger.verbose.info("Calling publish hook");
            await this.hooks.publish.promise({
                bump,
                useVersion: options.useVersion
            });
            this.logger.verbose.info("Calling after publish hook");
            await this.hooks.afterPublish.promise();
        }
        return {
            newVersion: await this.makeRelease(options),
            commitsInRelease,
            context: "latest",
        };
    }
    /** Get a pr number from user input or the env */
    getPrNumber(command, pr) {
        const prNumber = getPrNumberFromEnv(pr);
        if (!prNumber) {
            this.logger.log.error(endent_1.default `
          Could not detect PR number. ${command} must be run from either a PR or have the PR number supplied via the --pr flag.

          In some CIs your branch might be built before you open a PR and posting the canary version will fail. In this case subsequent builds should succeed.
        `);
            process.exit(1);
        }
        return prNumber;
    }
    /** Create a client to interact with git */
    startGit(gitOptions) {
        if (!gitOptions.owner || !gitOptions.repo || !gitOptions.token) {
            throw new Error("Must set owner, repo, and GitHub token.");
        }
        this.logger.verbose.info("Options contain repo information.");
        // So that --verbose can be used on public CIs
        const tokenlessArgs = Object.assign(Object.assign({}, gitOptions), { token: `[Token starting with ${gitOptions.token.substring(0, 4)}]` });
        this.logger.verbose.info("Initializing GitHub API with:\n", tokenlessArgs);
        return new git_1.default({
            owner: gitOptions.owner,
            repo: gitOptions.repo,
            token: gitOptions.token,
            baseUrl: gitOptions.baseUrl,
            baseBranch: this.baseBranch,
            graphqlBaseUrl: gitOptions.graphqlBaseUrl,
            agent: gitOptions.agent,
        }, this.logger);
    }
    /** Calculate a version from a tag using labels */
    async getVersion({ from } = {}) {
        if (!this.git || !this.release) {
            throw this.createErrorMessage();
        }
        const isPrerelease = this.inPrereleaseBranch();
        const [, latestTagInBranch] = await await_to_js_1.default(this.git.getLatestTagInBranch());
        const lastRelease = from ||
            (isPrerelease && latestTagInBranch) ||
            (await this.git.getLatestRelease());
        let calculatedBump = await this.release.getSemverBump(lastRelease);
        // For next releases we also want to take into account any labels on
        // the PR of next into main
        if (isPrerelease) {
            const pr = getPrNumberFromEnv();
            if (pr && this.semVerLabels) {
                const prLabels = await this.git.getLabels(pr);
                this.logger.verbose.info(`Found labels on prerelease branch PR`, prLabels);
                calculatedBump = semver_2.calculateSemVerBump([prLabels, [calculatedBump]], this.semVerLabels, this.config);
            }
        }
        const bump = (isPrerelease && semver_2.preVersionMap.get(calculatedBump)) || calculatedBump;
        this.versionBump = bump;
        return bump;
    }
    /** Make a changelog over a range of commits */
    async makeChangelog(args = {}) {
        const options = Object.assign(Object.assign({}, this.getCommandDefault("changelog")), args);
        const { dryRun, from, to, title, message = "Update CHANGELOG.md [skip ci]", noCommit, } = options;
        if (!this.release || !this.git) {
            throw this.createErrorMessage();
        }
        await this.setGitUser();
        if (title) {
            this.release.hooks.createChangelogTitle.tap("Changelog Flag", () => title);
        }
        const lastRelease = from || (await this.git.getLatestRelease());
        const bump = await this.release.getSemverBump(lastRelease, to);
        const releaseNotes = await this.release.generateReleaseNotes(lastRelease, to, this.versionBump);
        if (args.quiet) {
            console.log(releaseNotes);
        }
        if (dryRun) {
            this.logger.log.info("Potential Changelog Addition:\n", releaseNotes);
            this.logger.verbose.info("`changelog` dry run complete.");
            return;
        }
        this.logger.log.info("New Release Notes\n", releaseNotes);
        const currentVersion = await this.getCurrentVersion(lastRelease);
        const context = {
            bump,
            commits: await this.release.getCommits(lastRelease, to || undefined),
            releaseNotes,
            lastRelease,
            currentVersion,
        };
        if (!noCommit) {
            await this.release.addToChangelog(releaseNotes, lastRelease, currentVersion);
            await this.hooks.beforeCommitChangelog.promise(context);
            await exec_promise_1.default("git", ["commit", "-m", `"${message}"`, "--no-verify"]);
            this.logger.verbose.info("Committed new changelog.");
        }
        await this.hooks.afterChangelog.promise(context);
    }
    /** Make a release over a range of commits */
    async makeRelease(args = {}) {
        const options = Object.assign(Object.assign({}, this.getCommandDefault("release")), args);
        const { dryRun, from, to, useVersion, prerelease = false } = options;
        if (!this.release || !this.git) {
            throw this.createErrorMessage();
        }
        // This will usually resolve to something on head
        const [err, latestTag] = await await_to_js_1.default(this.git.getLatestTagInBranch());
        // If its a dry run we want to show what would happen. Otherwise no
        // tags indicates that something would definitely go wrong.
        if ((err === null || err === void 0 ? void 0 : err.message.includes("No names found")) && !args.dryRun) {
            this.logger.log.error(endent_1.default `
          Could not find any tags in the local repository. Exiting early.

          The "release" command creates GitHub releases for tags that have already been created in your repo.

          If there are no tags there is nothing to release. If you don't use "shipit" ensure you tag your releases with the new version number.
        `, "\n");
            this.logger.verbose.error(err);
            return process.exit(1);
        }
        const isPrerelease = prerelease || this.inPrereleaseBranch();
        let lastRelease = from ||
            (isPrerelease && (await this.git.getPreviousTagInBranch())) ||
            (await this.git.getLatestRelease());
        // Find base commit or latest release to generate the changelog to HEAD (new tag)
        this.logger.veryVerbose.info(`Using ${lastRelease} as previous release.`);
        if (lastRelease.match(/^\d+\.\d+\.\d+/)) {
            lastRelease = this.prefixRelease(lastRelease);
        }
        this.logger.log.info('Current "Latest Release" on Github:', lastRelease);
        const commitsInRelease = await this.release.getCommitsInRelease(lastRelease, to);
        const releaseNotes = await this.release.generateReleaseNotes(lastRelease, to, this.versionBump);
        this.logger.log.info(`Using release notes:\n${releaseNotes}`);
        const rawVersion = useVersion ||
            (isPrerelease && latestTag) ||
            (await this.getCurrentVersion(lastRelease)) ||
            latestTag;
        if (!rawVersion) {
            this.logger.log.error("Could not calculate next version from last tag.");
            return;
        }
        const newVersion = semver_1.parse(rawVersion)
            ? this.prefixRelease(rawVersion)
            : rawVersion;
        if (!dryRun &&
            semver_1.parse(newVersion) &&
            semver_1.parse(lastRelease) &&
            semver_1.compareBuild(newVersion, lastRelease) === 0) {
            this.logger.log.warn(`Nothing released to Github. Version to be released is the same as the latest release on Github: ${newVersion}`);
            return;
        }
        const release = await this.hooks.makeRelease.promise({
            dryRun,
            from: lastRelease,
            to: to || (await this.git.getSha()),
            useVersion,
            isPrerelease,
            newVersion,
            fullReleaseNotes: releaseNotes,
            commits: commitsInRelease,
        });
        if (release) {
            await this.hooks.afterRelease.promise({
                lastRelease,
                newVersion,
                commits: commitsInRelease,
                releaseNotes,
                response: release,
            });
        }
        return newVersion;
    }
    /** Create an auto initialization error */
    createErrorMessage() {
        return new Error(`Auto is not initialized! Make sure the have run Auto.loadConfig`);
    }
    /** Get the current git user */
    async getGitUser() {
        var _a;
        try {
            return {
                /** The git user is already set in the current env */
                system: true,
                email: await exec_promise_1.default("git", ["config", "user.email"]),
                name: await exec_promise_1.default("git", ["config", "user.name"]),
            };
        }
        catch (error) {
            this.logger.verbose.warn("Could not find git user or email configured in git config");
            const { author } = this.config || {};
            let { email, name } = this.config || {};
            if (author) {
                const parsedAuthor = typeof author === "string" ? parse_author_1.default(author) : author;
                if (typeof parsedAuthor === "object" &&
                    parsedAuthor.email &&
                    parsedAuthor.name) {
                    ({ name, email } = parsedAuthor);
                }
                else {
                    this.logger.log.error(endent_1.default `
            .autorc author parsing failed!

            The author must either be in one of the following formats:

            1. Your Name <your_email@mail.com>
            2. An object with "name" and "email"

            But you supplied:

            ${author}
          `);
                }
            }
            this.logger.verbose.warn(`Got author from options: email: ${email}, name ${name}`);
            const packageAuthor = (await this.hooks.getAuthor.promise()) || {};
            const tokenUser = await ((_a = this.git) === null || _a === void 0 ? void 0 : _a.getUser());
            email = email || packageAuthor.email || (tokenUser === null || tokenUser === void 0 ? void 0 : tokenUser.email) || undefined;
            name = name || packageAuthor.name || (tokenUser === null || tokenUser === void 0 ? void 0 : tokenUser.name) || undefined;
            this.logger.verbose.warn(`Using author: ${name} <${email}>`);
            return { email, name };
        }
    }
    /**
     * Set the git user to make releases and commit with.
     */
    async setGitUser() {
        const user = await this.getGitUser();
        if (user && !user.system) {
            if (!env.isCi) {
                this.logger.log.note(endent_1.default `
          Detected local environment, will not set git user. This happens automatically in a CI environment.

          If a command fails manually run:

            - git config user.email your@email.com
            - git config user.name "Your Name"
        `);
                return;
            }
            if (user.email) {
                await exec_promise_1.default("git", ["config", "user.email", `"${user.email}"`]);
                this.logger.verbose.warn(`Set git email to ${user.email}`);
            }
            if (user.name) {
                await exec_promise_1.default("git", ["config", "user.name", `"${user.name}"`]);
                this.logger.verbose.warn(`Set git name to ${user.name}`);
            }
            if (!user.name && !user.email) {
                this.logger.log.error(endent_1.default `
            Could find a git name and email to commit with!

            Name: ${user.name}
            Email: ${user.email}

            You must do one of the following:

            - configure the author for your package manager (ex: set "author" in package.json)
            - Set "name" and "email in your .autorc
          `, "");
                process.exit(1);
            }
        }
    }
    /** Get the repo to interact with */
    async getRepo(config) {
        if (config.owner && config.repo) {
            return config;
        }
        const author = await this.hooks.getRepository.promise();
        if (!author || !author.owner || !author.repo) {
            this.logger.log.error(endent_1.default `
          Cannot find project owner and repository name!

          You must do one of the following:

          - configure the repo for your package manager (ex: set "repository" in package.json)
          - configure your git remote 'origin' to point to your project on GitHub.
        `, "");
            process.exit(1);
        }
        return author;
    }
    /** Find the location of the extended configuration */
    getExtendedLocation(config) {
        let extendedLocation;
        try {
            if (config.extends) {
                extendedLocation = require.resolve(config.extends);
            }
        }
        catch (error) {
            try {
                if (config.extends) {
                    extendedLocation = require.resolve(path_1.default.join(process.cwd(), config.extends));
                }
            }
            catch (error) {
                this.logger.veryVerbose.error(error);
            }
        }
        return extendedLocation;
    }
    /**
     * Apply all of the plugins in the config.
     */
    loadPlugins(config) {
        config.plugins = config.plugins || [is_binary_1.default() ? "git-tag" : "npm"];
        const extendedLocation = this.getExtendedLocation(config);
        const pluginsPaths = [
            require.resolve("./plugins/filter-non-pull-request"),
            ...config.plugins,
        ];
        pluginsPaths
            .map((plugin) => 
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        typeof plugin === "string" ? [plugin, {}] : plugin)
            .map((plugin) => load_plugins_1.loadPlugin(plugin, this.logger, extendedLocation))
            .filter((plugin) => Boolean(plugin))
            .forEach((plugin) => {
            this.logger.verbose.info(`Using ${plugin.name} Plugin...`);
            plugin.apply(this);
        });
    }
    /** Get the branch and build number when in CI environment */
    async getPrEnvInfo() {
        var _a, _b, _c, _d, _e, _f;
        // SailEnv falls back to commit SHA
        let pr;
        let build;
        if ("pr" in env && "build" in env) {
            ({ pr } = env);
            ({ build } = env);
        }
        else if ("pr" in env && "commit" in env) {
            ({ pr } = env);
            build = env.commit;
        }
        // If we haven't detected the PR from the env vars try to match
        // the commit to a PR
        if (env.isCi && !pr && ((_a = this.git) === null || _a === void 0 ? void 0 : _a.options.owner) && ((_b = this.git) === null || _b === void 0 ? void 0 : _b.options.repo)) {
            const commit = await this.git.getSha();
            const query = match_sha_to_pr_1.buildSearchQuery((_c = this.git) === null || _c === void 0 ? void 0 : _c.options.owner, (_d = this.git) === null || _d === void 0 ? void 0 : _d.options.repo, [commit]);
            if (query) {
                const result = await this.git.graphql(query);
                if (result === null || result === void 0 ? void 0 : result[`hash_${commit}`]) {
                    const number = (_f = (_e = result[`hash_${commit}`].edges[0]) === null || _e === void 0 ? void 0 : _e.node) === null || _f === void 0 ? void 0 : _f.number;
                    if (number) {
                        pr = String(number);
                    }
                }
            }
        }
        return { pr, build };
    }
    /** Get the default for a command from the config */
    getCommandDefault(name) {
        if (!this.config) {
            return {};
        }
        const commandConfig = this.config[name];
        return typeof commandConfig === "object" ? commandConfig : {};
    }
}
exports.default = Auto;
// Plugin Utils
tslib_1.__exportStar(require("./auto-args"), exports);
var init_2 = require("./init");
Object.defineProperty(exports, "InteractiveInit", { enumerable: true, get: function () { return tslib_1.__importDefault(init_2).default; } });
var config_2 = require("./config");
Object.defineProperty(exports, "DEFAULT_PRERELEASE_BRANCHES", { enumerable: true, get: function () { return config_2.DEFAULT_PRERELEASE_BRANCHES; } });
var get_current_branch_2 = require("./utils/get-current-branch");
Object.defineProperty(exports, "getCurrentBranch", { enumerable: true, get: function () { return get_current_branch_2.getCurrentBranch; } });
var validate_config_2 = require("./validate-config");
Object.defineProperty(exports, "validatePluginConfiguration", { enumerable: true, get: function () { return validate_config_2.validatePluginConfiguration; } });
var auto_1 = require("./auto");
Object.defineProperty(exports, "Auto", { enumerable: true, get: function () { return tslib_1.__importDefault(auto_1).default; } });
var semver_3 = require("./semver");
Object.defineProperty(exports, "SEMVER", { enumerable: true, get: function () { return tslib_1.__importDefault(semver_3).default; } });
tslib_1.__exportStar(require("./semver"), exports);
var exec_promise_2 = require("./utils/exec-promise");
Object.defineProperty(exports, "execPromise", { enumerable: true, get: function () { return tslib_1.__importDefault(exec_promise_2).default; } });
var get_lerna_packages_1 = require("./utils/get-lerna-packages");
Object.defineProperty(exports, "getLernaPackages", { enumerable: true, get: function () { return tslib_1.__importDefault(get_lerna_packages_1).default; } });
var in_folder_1 = require("./utils/in-folder");
Object.defineProperty(exports, "inFolder", { enumerable: true, get: function () { return tslib_1.__importDefault(in_folder_1).default; } });
//# sourceMappingURL=auto.js.map