"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UpdateController = void 0;
const semver = require("semver");
const constants = require("../constants");
const update_controller_base_1 = require("./update-controller-base");
const yok_1 = require("../common/yok");
const constants_1 = require("../constants");
const color_1 = require("../color");
class UpdateController extends update_controller_base_1.UpdateControllerBase {
    constructor($fs, $platformsDataService, $platformCommandHelper, $packageInstallationManager, $packageManager, $pluginsService, $pacoteService, $logger, $errors, $projectDataService, $projectBackupService, $projectCleanupService, $terminalSpinnerService) {
        super($fs, $platformCommandHelper, $platformsDataService, $packageInstallationManager, $packageManager, $pacoteService);
        this.$fs = $fs;
        this.$platformsDataService = $platformsDataService;
        this.$platformCommandHelper = $platformCommandHelper;
        this.$packageInstallationManager = $packageInstallationManager;
        this.$packageManager = $packageManager;
        this.$pluginsService = $pluginsService;
        this.$pacoteService = $pacoteService;
        this.$logger = $logger;
        this.$errors = $errors;
        this.$projectDataService = $projectDataService;
        this.$projectBackupService = $projectBackupService;
        this.$projectCleanupService = $projectCleanupService;
        this.$terminalSpinnerService = $terminalSpinnerService;
    }
    update(updateOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            this.spinner = this.$terminalSpinnerService.createSpinner();
            const projectData = this.$projectDataService.getProjectData(updateOptions.projectDir);
            updateOptions.version = updateOptions.version || constants_1.PackageVersion.LATEST;
            // back up project files and folders
            this.spinner.info("Backing up project files before update");
            yield this.backupProject();
            this.spinner.succeed("Project files have been backed up");
            // clean up project files
            this.spinner.info("Cleaning up project files before update");
            yield this.cleanUpProject();
            this.spinner.succeed("Project files have been cleaned up");
            // update dependencies
            this.spinner.info("Updating project dependencies");
            yield this.updateDependencies(projectData, updateOptions.version);
            this.spinner.succeed("Project dependencies have been updated");
            this.spinner.succeed("Update complete.");
            this.$logger.info("");
            this.$logger.printMarkdown("Project has been successfully updated. The next step is to run `ns run <platform>` to ensure everything is working properly." +
                "\n\nPlease note that you may need additional changes to complete the update.");
        });
    }
    shouldUpdate(updateOptions) {
        return __awaiter(this, void 0, void 0, function* () {
            const projectData = this.$projectDataService.getProjectData(updateOptions.projectDir);
            updateOptions.version = updateOptions.version || constants_1.PackageVersion.LATEST;
            for (const dependency of UpdateController.updatableDependencies) {
                this.$logger.trace(`Checking if ${dependency.packageName} needs to be updated...`);
                const desiredVersion = yield this.getVersionFromTagOrVersion(dependency.packageName, updateOptions.version);
                if (typeof desiredVersion === "boolean") {
                    this.$logger.trace(`Package ${dependency.packageName} does not have version/tag ${updateOptions.version}. Skipping.`);
                    continue;
                }
                const shouldUpdate = yield this.shouldUpdateDependency(projectData, dependency, desiredVersion);
                if (shouldUpdate) {
                    this.$logger.trace(`shouldUpdate is true because '${dependency.packageName} needs to be updated.'`);
                    return true;
                }
            }
            return false;
        });
    }
    updateDependencies(projectData, version) {
        return __awaiter(this, void 0, void 0, function* () {
            for (const dependency of UpdateController.updatableDependencies) {
                yield this.updateDependency(projectData, dependency, version);
            }
        });
    }
    updateDependency(projectData, dependency, version) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.hasDependency(dependency, projectData)) {
                return;
            }
            const desiredVersion = yield this.getVersionFromTagOrVersion(dependency.packageName, version);
            if (typeof desiredVersion === "boolean") {
                this.$logger.info(`  - ${color_1.color.yellow(dependency.packageName)} does not have version/tag ${color_1.color.green(version)}. ` +
                    color_1.color.yellow("Skipping."));
                return;
            }
            const shouldUpdate = yield this.shouldUpdateDependency(projectData, dependency, desiredVersion);
            if (!shouldUpdate) {
                return;
            }
            // check if the coerced version is the same as desired and prefix it with a ~
            // for example:
            // 8.0.0 -> ~8.0.0
            // 8.0.8-next-XXX -> 8.0.8-next-XXX
            const updatedVersion = (() => {
                if (desiredVersion === version) {
                    return desiredVersion;
                }
                if (semver.coerce(desiredVersion).version === desiredVersion) {
                    return `~${desiredVersion}`;
                }
                return desiredVersion;
            })();
            this.$pluginsService.addToPackageJson(dependency.packageName, updatedVersion, dependency.isDev, projectData.projectDir);
            this.$logger.info(`  - ${color_1.color.yellow(dependency.packageName)} has been updated to ${color_1.color.green(updatedVersion)}`);
        });
    }
    shouldUpdateDependency(projectData, dependency, desiredVersion) {
        return __awaiter(this, void 0, void 0, function* () {
            const installedVersion = yield this.$packageInstallationManager.getInstalledDependencyVersion(dependency.packageName, projectData.projectDir);
            if (!installedVersion) {
                return false;
            }
            return installedVersion != desiredVersion;
        });
    }
    getVersionFromTagOrVersion(packageName, versionOrTag) {
        return __awaiter(this, void 0, void 0, function* () {
            if (semver.valid(versionOrTag) || semver.validRange(versionOrTag)) {
                return versionOrTag;
            }
            const version = yield this.$packageManager.getTagVersion(packageName, versionOrTag);
            if (!version) {
                return false;
            }
            return version;
        });
    }
    backupProject() {
        return __awaiter(this, void 0, void 0, function* () {
            const backup = this.$projectBackupService.getBackup("migration");
            backup.addPaths([...UpdateController.pathsToBackup]);
            try {
                return backup.create();
            }
            catch (error) {
                this.spinner.fail(`Project backup failed.`);
                backup.remove();
                this.$errors.fail(`Project backup failed. Error is: ${error.message}`);
            }
        });
    }
    cleanUpProject() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.$projectCleanupService.clean([
                constants.HOOKS_DIR_NAME,
                constants.PLATFORMS_DIR_NAME,
                constants.NODE_MODULES_FOLDER_NAME,
                constants.PACKAGE_LOCK_JSON_FILE_NAME,
            ]);
        });
    }
}
UpdateController.updatableDependencies = [
    // dependencies
    {
        packageName: "@nativescript/core",
    },
    // devDependencies
    {
        packageName: "@nativescript/webpack",
        isDev: true,
    },
    {
        packageName: "@nativescript/types",
        isDev: true,
    },
    // runtimes
    {
        packageName: "@nativescript/ios",
        isDev: true,
    },
    {
        packageName: "@nativescript/android",
        isDev: true,
    },
];
UpdateController.backupFolderName = ".migration_backup";
UpdateController.pathsToBackup = [
    constants.LIB_DIR_NAME,
    constants.HOOKS_DIR_NAME,
    constants.WEBPACK_CONFIG_NAME,
    constants.PACKAGE_JSON_FILE_NAME,
    constants.PACKAGE_LOCK_JSON_FILE_NAME,
    constants.CONFIG_NS_FILE_NAME,
];
exports.UpdateController = UpdateController;
yok_1.injector.register("updateController", UpdateController);
//# sourceMappingURL=update-controller.js.map