"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VersionActions = void 0;
exports.resolveVersionActionsForProject = resolveVersionActionsForProject;
const node_path_1 = require("node:path");
const register_1 = require("../../../plugins/js/utils/register");
const typescript_1 = require("../../../plugins/js/utils/typescript");
const utils_1 = require("../../../tasks-runner/utils");
const workspace_root_1 = require("../../../utils/workspace-root");
const config_1 = require("../config/config");
const semver_1 = require("../utils/semver");
const release_group_processor_1 = require("./release-group-processor");
function resolveVersionActionsPath(path, projectGraphNode) {
    try {
        return require.resolve(path);
    }
    catch {
        try {
            return require.resolve((0, node_path_1.join)(workspace_root_1.workspaceRoot, path));
        }
        catch {
            if (path === config_1.DEFAULT_VERSION_ACTIONS_PATH) {
                throw new Error(`Unable to resolve the default "versionActions" implementation for project "${projectGraphNode.name}" at path: "${path}"

- If this is a JavaScript/TypeScript project, it is likely that you simply need to install the "@nx/js" plugin.

- If this not a JavaScript/TypeScript project, you can should provide an alternative "versionActions" implementation path via the "release.version.versionActions" configuration option.
`);
            }
            throw new Error(`Unable to resolve the "versionActions" implementation for project "${projectGraphNode.name}" at the configured path: "${path}"`);
        }
    }
}
const versionActionsResolutionCache = new Map();
async function resolveVersionActionsForProject(tree, releaseGroup, projectGraphNode, finalConfigForProject) {
    // Project level release version config takes priority, if set
    const projectVersionConfig = projectGraphNode.data.release?.version;
    const releaseGroupVersionConfig = releaseGroup.version;
    const versionActionsPathConfig = projectVersionConfig?.versionActions ??
        releaseGroupVersionConfig?.versionActions ??
        null;
    if (!versionActionsPathConfig) {
        // Should be an impossible state, as we should have defaulted to the JS implementation during config processing
        throw new Error(`No versionActions implementation found for project "${projectGraphNode.name}", please report this on https://github.com/nrwl/nx/issues`);
    }
    let cachedData = versionActionsResolutionCache.get(versionActionsPathConfig);
    const versionActionsPath = resolveVersionActionsPath(versionActionsPathConfig, projectGraphNode);
    let VersionActionsClass;
    let afterAllProjectsVersioned;
    if (cachedData) {
        VersionActionsClass = cachedData.VersionActionsClass;
        afterAllProjectsVersioned = cachedData.afterAllProjectsVersioned;
    }
    else {
        let cleanupTranspiler;
        if (versionActionsPath.endsWith('.ts')) {
            cleanupTranspiler = (0, register_1.registerTsProject)((0, typescript_1.getRootTsConfigPath)());
        }
        const loaded = require(versionActionsPath);
        cleanupTranspiler?.();
        VersionActionsClass = loaded.default ?? loaded;
        if (!VersionActionsClass) {
            throw new Error(`For project "${projectGraphNode.name}" it was not possible to resolve the VersionActions implementation from: "${versionActionsPath}"`);
        }
        afterAllProjectsVersioned =
            loaded.afterAllProjectsVersioned ??
                // no-op fallback for ecosystems/use-cases where it is not applicable
                (() => Promise.resolve({
                    changedFiles: [],
                    deletedFiles: [],
                }));
        versionActionsResolutionCache.set(versionActionsPath, {
            VersionActionsClass,
            afterAllProjectsVersioned,
        });
    }
    const versionActions = new VersionActionsClass(releaseGroup, projectGraphNode, finalConfigForProject);
    // Initialize the version actions with all the required manifest paths etc
    await versionActions.init(tree);
    return {
        versionActionsPath,
        versionActions,
        afterAllProjectsVersioned,
    };
}
class VersionActions {
    constructor(releaseGroup, projectGraphNode, finalConfigForProject) {
        this.releaseGroup = releaseGroup;
        this.projectGraphNode = projectGraphNode;
        this.finalConfigForProject = finalConfigForProject;
        /**
         * The interpolated manifest paths to update, if applicable based on the user's configuration, when new
         * versions and dependencies are determined. If no manifest files should be updated based on the user's
         * configuration, this will be an empty array.
         *
         * The final value for preserveLocalDependencyProtocols will be based on the resolved config for the current
         * project and any overrides from the user's configuration for the manifestRootsToUpdate.
         */
        this.manifestsToUpdate = [];
    }
    /**
     * Asynchronous initialization of the version actions and validation of certain configuration options.
     */
    async init(tree) {
        // Default to the first available source manifest root, if applicable, if no custom manifest roots are provided
        if (this.validManifestFilenames?.length &&
            this.finalConfigForProject.manifestRootsToUpdate.length === 0) {
            for (const manifestFilename of this.validManifestFilenames) {
                if (tree.exists((0, node_path_1.join)(this.projectGraphNode.data.root, manifestFilename))) {
                    this.finalConfigForProject.manifestRootsToUpdate.push({
                        path: this.projectGraphNode.data.root,
                        preserveLocalDependencyProtocols: this.finalConfigForProject.preserveLocalDependencyProtocols,
                    });
                    break;
                }
            }
        }
        const interpolatedManifestRoots = this.finalConfigForProject.manifestRootsToUpdate.map((manifestRoot) => {
            return {
                ...manifestRoot,
                path: (0, utils_1.interpolate)(manifestRoot.path, {
                    workspaceRoot: '',
                    projectRoot: this.projectGraphNode.data.root,
                    projectName: this.projectGraphNode.name,
                }),
            };
        });
        for (const interpolatedManifestRoot of interpolatedManifestRoots) {
            let hasValidManifest = false;
            for (const manifestFilename of this.validManifestFilenames) {
                const manifestPath = (0, node_path_1.join)(interpolatedManifestRoot.path, manifestFilename);
                if (tree.exists(manifestPath)) {
                    this.manifestsToUpdate.push({
                        ...interpolatedManifestRoot,
                        manifestPath,
                    });
                    hasValidManifest = true;
                    break;
                }
            }
            if (!hasValidManifest) {
                const validManifestFilenames = this.validManifestFilenames?.join(' or ');
                throw new Error(`The project "${this.projectGraphNode.name}" does not have a ${validManifestFilenames} file available in ./${interpolatedManifestRoot.path}.
          
To fix this you will either need to add a ${validManifestFilenames} file at that location, or configure "release" within your nx.json to exclude "${this.projectGraphNode.name}" from the current release group, or amend the "release.version.manifestRootsToUpdate" configuration to point to where the relevant manifest should be.`);
            }
        }
    }
    /**
     * The default implementation will calculate the new version based on semver. If semver is not applicable to a
     * particular versioning use-case, this method should be overridden with custom logic.
     *
     * @param {string | null} currentVersion - The current version of the project, or null if the current version resolver is set to 'none'
     * @param {string} newVersionInput - The new version input provided by the user, such as a semver relative bump type, or an explicit version
     * @param {string} newVersionInputReason - The reason for the new version input used to inform the log message to show to the user
     * @param {Record<string, unknown>} newVersionInputReasonData - The data to interpolate into the new version input reason
     * @param {string} preid - The preid to use for the new version, if applicable
     */
    async calculateNewVersion(currentVersion, newVersionInput, newVersionInputReason, newVersionInputReasonData, preid) {
        const isSemverRelativeBump = (0, semver_1.isRelativeVersionKeyword)(newVersionInput);
        const newVersionReasonText = release_group_processor_1.BUMP_TYPE_REASON_TEXT[newVersionInputReason];
        if (!newVersionReasonText) {
            throw new Error(`Unhandled bump type reason for ${this.projectGraphNode.name} with newVersionInput ${newVersionInput} and newVersionInputReason ${newVersionInputReason}, please report this as a bug on https://github.com/nrwl/nx/issues`);
        }
        const interpolatedNewVersionInputReasonText = (0, utils_1.interpolate)(newVersionReasonText, newVersionInputReasonData);
        if (isSemverRelativeBump && !currentVersion) {
            throw new Error(`There was no current version resolved for project "${this.projectGraphNode.name}", but it was configured to be bumped via a semver relative keyword "${newVersionInput}"${interpolatedNewVersionInputReasonText}. This is not a valid combination, please review your release configuration and CLI arguments`);
        }
        const newVersion = (0, semver_1.deriveNewSemverVersion)(currentVersion, newVersionInput, preid);
        const newVersionInputText = (0, semver_1.isRelativeVersionKeyword)(newVersionInput)
            ? `semver relative bump "${newVersionInput}"`
            : `explicit semver value "${newVersionInput}"`;
        return {
            newVersion,
            logText: `❓ Applied ${newVersionInputText}${interpolatedNewVersionInputReasonText}to get new version ${newVersion}`,
        };
    }
    /**
     * Implementation details of resolving the dependencies of a project.
     *
     * The default implementation will read dependencies from the Nx project graph. In many cases this will be sufficient,
     * because the project graph will have been constructed using plugins from relevant ecosystems that should have applied
     * any and all relevant metadata to the project nodes and dependency edges.
     *
     * If, however, the project graph cannot be used as the source of truth for whatever reason, then this default method
     * can simply be overridden in the final version actions implementation.
     */
    async readDependencies(tree, projectGraph) {
        return (projectGraph.dependencies[this.projectGraphNode.name] ?? []).filter(
        // Skip implicit dependencies for now to match legacy versioning behavior
        // TODO: holistically figure out how to handle implicit dependencies with nx release
        (dep) => dep.type !== 'implicit');
    }
}
exports.VersionActions = VersionActions;
