"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.VersionManager = void 0;
const path = __importStar(require("node:path"));
const semver = __importStar(require("semver"));
const node_core_library_1 = require("@rushstack/node-core-library");
const VersionPolicy_1 = require("../api/VersionPolicy");
const ChangeFile_1 = require("../api/ChangeFile");
const ChangeManagement_1 = require("../api/ChangeManagement");
const RushConfiguration_1 = require("../api/RushConfiguration");
const PublishUtilities_1 = require("./PublishUtilities");
const ChangeManager_1 = require("./ChangeManager");
const DependencySpecifier_1 = require("./DependencySpecifier");
const objectUtilities_1 = require("../utilities/objectUtilities");
class VersionManager {
    constructor(rushConfiguration, userEmail, versionPolicyConfiguration) {
        this._rushConfiguration = rushConfiguration;
        this._userEmail = userEmail;
        this._versionPolicyConfiguration = versionPolicyConfiguration
            ? versionPolicyConfiguration
            : this._rushConfiguration.versionPolicyConfiguration;
        this.updatedProjects = new Map();
        this.changeFiles = new Map();
    }
    /**
     * Ensures project versions follow the provided version policy. If version policy is not
     * provided, all projects will have their version checked according to the associated version policy.
     * package.json files will be updated if needed.
     * This method does not commit changes.
     * @param versionPolicyName -- version policy name
     * @param shouldCommit -- should update files to disk
     * @param force -- update even when project version is higher than policy version.
     */
    ensure(versionPolicyName, shouldCommit, force) {
        this._ensure(versionPolicyName, shouldCommit, force);
    }
    /**
     * Bumps versions following version policies.
     *
     * @param lockStepVersionPolicyName - a specified lock step version policy name. Without this value,
     * versions for all lock step policies and all individual policies will be bumped.
     * With this value, only the specified lock step policy will be bumped along with all individual policies.
     * @param bumpType - overrides the default bump type and only works for lock step policy
     * @param identifier - overrides the prerelease identifier and only works for lock step policy
     * @param shouldCommit - whether the changes will be written to disk
     */
    async bumpAsync(lockStepVersionPolicyName, bumpType, identifier, shouldCommit) {
        // Bump all the lock step version policies.
        this._versionPolicyConfiguration.bump(lockStepVersionPolicyName, bumpType, identifier, shouldCommit);
        // Update packages and generate change files due to lock step bump.
        this._ensure(lockStepVersionPolicyName, shouldCommit);
        // Refresh rush configuration since we may have modified the package.json versions
        // when calling this._ensure(...)
        this._rushConfiguration = RushConfiguration_1.RushConfiguration.loadFromConfigurationFile(this._rushConfiguration.rushJsonFile);
        // Update projects based on individual policies
        const changeManager = new ChangeManager_1.ChangeManager(this._rushConfiguration, this._getManuallyVersionedProjects());
        await changeManager.loadAsync(this._rushConfiguration.changesFolder);
        if (changeManager.hasChanges()) {
            changeManager.validateChanges(this._versionPolicyConfiguration);
            changeManager.apply(!!shouldCommit).forEach((packageJson) => {
                this.updatedProjects.set(packageJson.name, packageJson);
            });
            await changeManager.updateChangelogAsync(!!shouldCommit);
        }
        // Refresh rush configuration again, since we've further modified the package.json files
        // by calling changeManager.apply(...)
        this._rushConfiguration = RushConfiguration_1.RushConfiguration.loadFromConfigurationFile(this._rushConfiguration.rushJsonFile);
    }
    _ensure(versionPolicyName, shouldCommit, force) {
        this._updateVersionsByPolicy(versionPolicyName, force);
        let changed = false;
        do {
            changed = false;
            // Update all dependencies if needed.
            const dependenciesUpdated = this._updateDependencies();
            changed = changed || dependenciesUpdated;
        } while (changed);
        if (shouldCommit) {
            this._updatePackageJsonFiles();
            this.changeFiles.forEach((changeFile) => {
                changeFile.writeSync();
            });
        }
    }
    _getManuallyVersionedProjects() {
        const lockStepVersionPolicyNames = new Set();
        this._versionPolicyConfiguration.versionPolicies.forEach((versionPolicy) => {
            if (versionPolicy instanceof VersionPolicy_1.LockStepVersionPolicy && versionPolicy.nextBump !== undefined) {
                lockStepVersionPolicyNames.add(versionPolicy.policyName);
            }
        });
        const lockStepProjectNames = new Set();
        this._rushConfiguration.projects.forEach((rushProject) => {
            if (lockStepVersionPolicyNames.has(rushProject.versionPolicyName)) {
                lockStepProjectNames.add(rushProject.packageName);
            }
        });
        return lockStepProjectNames;
    }
    _updateVersionsByPolicy(versionPolicyName, force) {
        let changed = false;
        // Update versions based on version policy
        this._rushConfiguration.projects.forEach((rushProject) => {
            var _a;
            const projectVersionPolicyName = rushProject.versionPolicyName;
            if (projectVersionPolicyName &&
                (!versionPolicyName || projectVersionPolicyName === versionPolicyName)) {
                const versionPolicy = this._versionPolicyConfiguration.getVersionPolicy(projectVersionPolicyName);
                const oldVersion = ((_a = this.updatedProjects.get(rushProject.packageName)) === null || _a === void 0 ? void 0 : _a.version) || rushProject.packageJson.version;
                const updatedProject = versionPolicy.ensure(rushProject.packageJson, force);
                changed = changed || (updatedProject === null || updatedProject === void 0 ? void 0 : updatedProject.version) !== oldVersion;
                if (updatedProject) {
                    this.updatedProjects.set(updatedProject.name, updatedProject);
                    // No need to create an entry for prerelease version bump.
                    if (!this._isPrerelease(updatedProject.version) && rushProject.isMainProject) {
                        this._addChangeInfo(updatedProject.name, [this._createChangeInfo(updatedProject, rushProject)]);
                    }
                }
            }
        });
        return changed;
    }
    _isPrerelease(version) {
        return !!semver.prerelease(version);
    }
    _addChangeInfo(packageName, changeInfos) {
        if (!changeInfos.length) {
            return;
        }
        let changeFile = this.changeFiles.get(packageName);
        if (!changeFile) {
            changeFile = new ChangeFile_1.ChangeFile({
                changes: [],
                packageName: packageName,
                email: this._userEmail
            }, this._rushConfiguration);
            this.changeFiles.set(packageName, changeFile);
        }
        changeInfos.forEach((changeInfo) => {
            changeFile.addChange(changeInfo);
        });
    }
    _updateDependencies() {
        let updated = false;
        this._rushConfiguration.projects.forEach((rushProject) => {
            let clonedProject = this.updatedProjects.get(rushProject.packageName);
            let projectVersionChanged = true;
            if (!clonedProject) {
                clonedProject = (0, objectUtilities_1.cloneDeep)(rushProject.packageJson);
                projectVersionChanged = false;
            }
            const dependenciesUpdated = this._updateProjectAllDependencies(rushProject, clonedProject, projectVersionChanged);
            updated = updated || dependenciesUpdated;
        });
        return updated;
    }
    _updateProjectAllDependencies(rushProject, clonedProject, projectVersionChanged) {
        if (!clonedProject.dependencies && !clonedProject.devDependencies) {
            return false;
        }
        const changes = [];
        let updated = false;
        if (this._updateProjectDependencies(clonedProject.dependencies, changes, clonedProject, rushProject, projectVersionChanged)) {
            updated = true;
        }
        if (this._updateProjectDependencies(clonedProject.devDependencies, changes, clonedProject, rushProject, projectVersionChanged)) {
            updated = true;
        }
        if (this._updateProjectDependencies(clonedProject.peerDependencies, changes, clonedProject, rushProject, projectVersionChanged)) {
            updated = true;
        }
        if (updated) {
            this.updatedProjects.set(clonedProject.name, clonedProject);
            this._addChangeInfo(clonedProject.name, changes);
        }
        return updated;
    }
    _updateProjectDependencies(dependencies, changes, clonedProject, rushProject, projectVersionChanged) {
        if (!dependencies) {
            return false;
        }
        let updated = false;
        this.updatedProjects.forEach((updatedDependentProject, updatedDependentProjectName) => {
            if (dependencies[updatedDependentProjectName]) {
                if (rushProject.decoupledLocalDependencies.has(updatedDependentProjectName)) {
                    // Skip if cyclic
                    // eslint-disable-next-line no-console
                    console.log(`Found cyclic ${rushProject.packageName} ${updatedDependentProjectName}`);
                    return;
                }
                const oldDependencyVersion = dependencies[updatedDependentProjectName];
                const newDependencyVersion = PublishUtilities_1.PublishUtilities.getNewDependencyVersion(dependencies, updatedDependentProjectName, updatedDependentProject.version);
                if (newDependencyVersion !== oldDependencyVersion) {
                    updated = true;
                    if (this._shouldTrackDependencyChange(rushProject, updatedDependentProjectName)) {
                        this._trackDependencyChange(changes, clonedProject, projectVersionChanged, updatedDependentProject, oldDependencyVersion, newDependencyVersion);
                    }
                    dependencies[updatedDependentProjectName] = newDependencyVersion;
                }
            }
        });
        return updated;
    }
    _shouldTrackDependencyChange(rushProject, dependencyName) {
        const dependencyRushProject = this._rushConfiguration.projectsByName.get(dependencyName);
        return (!!dependencyRushProject &&
            rushProject.shouldPublish &&
            (!rushProject.versionPolicy ||
                !rushProject.versionPolicy.isLockstepped ||
                (rushProject.isMainProject &&
                    dependencyRushProject.versionPolicyName !== rushProject.versionPolicyName)));
    }
    _trackDependencyChange(changes, clonedProject, projectVersionChanged, updatedDependentProject, oldDependencyVersion, newDependencyVersion) {
        const oldSpecifier = DependencySpecifier_1.DependencySpecifier.parseWithCache(updatedDependentProject.name, oldDependencyVersion);
        if (!semver.satisfies(updatedDependentProject.version, oldSpecifier.versionSpecifier) &&
            !projectVersionChanged) {
            this._addChange(changes, {
                changeType: ChangeManagement_1.ChangeType.patch,
                packageName: clonedProject.name
            });
        }
        // If current version is not a prerelease version and new dependency is also not a prerelease version,
        // add change entry. Otherwise, too many changes will be created for frequent releases.
        if (!this._isPrerelease(updatedDependentProject.version) && !this._isPrerelease(clonedProject.version)) {
            this._addChange(changes, {
                changeType: ChangeManagement_1.ChangeType.dependency,
                comment: `Dependency ${updatedDependentProject.name} version bump from ${oldDependencyVersion}` +
                    ` to ${newDependencyVersion}.`,
                packageName: clonedProject.name
            });
        }
    }
    _addChange(changes, newChange) {
        const exists = changes.some((changeInfo) => {
            return (changeInfo.author === newChange.author &&
                changeInfo.changeType === newChange.changeType &&
                changeInfo.comment === newChange.comment &&
                changeInfo.commit === newChange.commit &&
                changeInfo.packageName === newChange.packageName &&
                changeInfo.type === newChange.type);
        });
        if (!exists) {
            changes.push(newChange);
        }
    }
    _updatePackageJsonFiles() {
        this.updatedProjects.forEach((newPackageJson, packageName) => {
            const rushProject = this._rushConfiguration.getProjectByName(packageName);
            // Update package.json
            if (rushProject) {
                const packagePath = path.join(rushProject.projectFolder, node_core_library_1.FileConstants.PackageJson);
                node_core_library_1.JsonFile.save(newPackageJson, packagePath, { updateExistingFile: true });
            }
        });
    }
    _createChangeInfo(newPackageJson, rushProject) {
        return {
            changeType: ChangeManagement_1.ChangeType.none,
            newVersion: newPackageJson.version,
            packageName: newPackageJson.name,
            comment: ''
        };
    }
}
exports.VersionManager = VersionManager;
//# sourceMappingURL=VersionManager.js.map