"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.MigrateController = void 0;
const path = require("path");
const semver = require("semver");
const constants = require("../constants");
const glob = require("glob");
const _ = require("lodash");
const simple_git_1 = require("simple-git");
const update_controller_base_1 = require("./update-controller-base");
const helpers_1 = require("../common/helpers");
const yok_1 = require("../common/yok");
class MigrateController extends update_controller_base_1.UpdateControllerBase {
    constructor($fs, $platformCommandHelper, $platformsDataService, $packageInstallationManager, $packageManager, $pacoteService, $devicePlatformsConstants, $logger, $errors, $addPlatformService, $pluginsService, $projectDataService, $projectConfigService, $options, $platformValidationService, $resources, $injector, $settingsService, $staticConfig, $terminalSpinnerService, $projectCleanupService, $projectBackupService) {
        super($fs, $platformCommandHelper, $platformsDataService, $packageInstallationManager, $packageManager, $pacoteService);
        this.$fs = $fs;
        this.$platformCommandHelper = $platformCommandHelper;
        this.$platformsDataService = $platformsDataService;
        this.$packageInstallationManager = $packageInstallationManager;
        this.$packageManager = $packageManager;
        this.$pacoteService = $pacoteService;
        this.$devicePlatformsConstants = $devicePlatformsConstants;
        this.$logger = $logger;
        this.$errors = $errors;
        this.$addPlatformService = $addPlatformService;
        this.$pluginsService = $pluginsService;
        this.$projectDataService = $projectDataService;
        this.$projectConfigService = $projectConfigService;
        this.$options = $options;
        this.$platformValidationService = $platformValidationService;
        this.$resources = $resources;
        this.$injector = $injector;
        this.$settingsService = $settingsService;
        this.$staticConfig = $staticConfig;
        this.$terminalSpinnerService = $terminalSpinnerService;
        this.$projectCleanupService = $projectCleanupService;
        this.$projectBackupService = $projectBackupService;
        this.migrationDependencies = [
            {
                packageName: constants.SCOPED_TNS_CORE_MODULES,
                verifiedVersion: "7.0.0",
                shouldAddIfMissing: true,
            },
            {
                packageName: constants.TNS_CORE_MODULES_NAME,
                shouldRemove: true,
            },
            {
                packageName: "@nativescript/types",
                verifiedVersion: "7.0.0",
                isDev: true,
            },
            {
                packageName: "tns-platform-declarations",
                replaceWith: "@nativescript/types",
                verifiedVersion: "7.0.0",
                isDev: true,
            },
            {
                packageName: constants.TNS_CORE_MODULES_WIDGETS_NAME,
                shouldRemove: true,
            },
            {
                packageName: "nativescript-dev-webpack",
                replaceWith: constants.WEBPACK_PLUGIN_NAME,
                shouldRemove: true,
                isDev: true,
                shouldMigrateAction: () => __awaiter(this, void 0, void 0, function* () {
                    return true;
                }),
                migrateAction: this.migrateWebpack.bind(this),
            },
            {
                packageName: constants.WEBPACK_PLUGIN_NAME,
                verifiedVersion: "3.0.0",
                shouldAddIfMissing: true,
            },
            {
                packageName: "nativescript-vue",
                verifiedVersion: "2.8.0",
                shouldMigrateAction: (projectData, allowInvalidVersions) => __awaiter(this, void 0, void 0, function* () {
                    const dependency = {
                        packageName: "nativescript-vue",
                        verifiedVersion: "2.8.0",
                        isDev: false,
                    };
                    const result = this.hasDependency(dependency, projectData) &&
                        (yield this.shouldMigrateDependencyVersion(dependency, projectData, allowInvalidVersions));
                    return result;
                }),
                migrateAction: this.migrateNativeScriptVue.bind(this),
            },
            {
                packageName: "nativescript-angular",
                replaceWith: "@nativescript/angular",
                verifiedVersion: "10.0.0",
            },
            {
                packageName: "@nativescript/angular",
                verifiedVersion: "10.0.0",
                shouldMigrateAction: (projectData, allowInvalidVersions) => __awaiter(this, void 0, void 0, function* () {
                    const dependency = {
                        packageName: "@nativescript/angular",
                        verifiedVersion: "10.0.0",
                        isDev: false,
                    };
                    const result = this.hasDependency(dependency, projectData) &&
                        (yield this.shouldMigrateDependencyVersion(dependency, projectData, allowInvalidVersions));
                    return result;
                }),
                migrateAction: this.migrateNativeScriptAngular.bind(this),
            },
            {
                packageName: "@nativescript/unit-test-runner",
                verifiedVersion: "1.0.0",
                shouldMigrateAction: (projectData, allowInvalidVersions) => __awaiter(this, void 0, void 0, function* () {
                    const dependency = {
                        packageName: "@nativescript/unit-test-runner",
                        verifiedVersion: "1.0.0",
                        isDev: false,
                    };
                    const result = this.hasDependency(dependency, projectData) &&
                        (yield this.shouldMigrateDependencyVersion(dependency, projectData, allowInvalidVersions));
                    return result;
                }),
                migrateAction: this.migrateUnitTestRunner.bind(this),
            },
            {
                packageName: "typescript",
                isDev: true,
                verifiedVersion: "3.9.7",
            },
        ];
    }
    get $jsonFileSettingsService() {
        const cliVersion = semver.coerce(this.$staticConfig.version);
        const shouldMigrateCacheFilePath = path.join(this.$settingsService.getProfileDir(), `should-migrate-cache-${cliVersion}.json`);
        return this.$injector.resolve("jsonFileSettingsService", {
            jsonFileSettingsPath: shouldMigrateCacheFilePath,
        });
    }
    get verifiedPlatformVersions() {
        return {
            [this.$devicePlatformsConstants.Android.toLowerCase()]: "6.5.3",
            [this.$devicePlatformsConstants.iOS.toLowerCase()]: "6.5.2",
        };
    }
    shouldMigrate({ projectDir, platforms, allowInvalidVersions = false, }) {
        return __awaiter(this, void 0, void 0, function* () {
            const remainingPlatforms = [];
            let shouldMigrate = false;
            for (const platform of platforms) {
                const cachedResult = yield this.getCachedShouldMigrate(projectDir, platform);
                if (cachedResult !== false) {
                    remainingPlatforms.push(platform);
                }
                else {
                    this.$logger.trace(`Got cached result for shouldMigrate for platform: ${platform}`);
                }
            }
            if (remainingPlatforms.length > 0) {
                shouldMigrate = yield this._shouldMigrate({
                    projectDir,
                    platforms: remainingPlatforms,
                    allowInvalidVersions,
                });
                this.$logger.trace(`Executed shouldMigrate for platforms: ${remainingPlatforms}. Result is: ${shouldMigrate}`);
                if (!shouldMigrate) {
                    for (const remainingPlatform of remainingPlatforms) {
                        yield this.setCachedShouldMigrate(projectDir, remainingPlatform);
                    }
                }
            }
            return shouldMigrate;
        });
    }
    validate({ projectDir, platforms, allowInvalidVersions = true, }) {
        return __awaiter(this, void 0, void 0, function* () {
            const shouldMigrate = yield this.shouldMigrate({
                projectDir,
                platforms,
                allowInvalidVersions,
            });
            if (shouldMigrate) {
                this.$errors.fail(`The current application is not compatible with NativeScript CLI 7.0.\n\nRun 'ns migrate' to migrate your project to NativeScript 7.\n\nAlternatively you may try running it with '--force' to skip this check.`);
            }
        });
    }
    migrate({ projectDir, platforms, allowInvalidVersions = false, }) {
        return __awaiter(this, void 0, void 0, function* () {
            this.spinner = this.$terminalSpinnerService.createSpinner();
            const projectData = this.$projectDataService.getProjectData(projectDir);
            this.$logger.trace("MigrationController.migrate called with", {
                projectDir,
                platforms,
                allowInvalidVersions,
            });
            const canMigrate = yield this.ensureGitCleanOrForce(projectDir);
            if (!canMigrate) {
                this.spinner.fail("Pre-Migration verification failed");
                return;
            }
            this.spinner.succeed("Pre-Migration verification complete");
            this.spinner.start("Backing up project files before migration");
            const backup = yield this.backupProject(projectDir);
            this.spinner.text = "Project files have been backed up";
            this.spinner.succeed();
            this.spinner.start("Cleaning up project files before migration");
            yield this.cleanUpProject(projectData);
            this.spinner.text = "Project files have been cleaned up";
            this.spinner.succeed();
            this.spinner.start("Cleaning up old artifacts");
            yield this.handleAutoGeneratedFiles(backup, projectData);
            this.spinner.text = "Cleaned old artifacts";
            this.spinner.succeed();
            this.spinner.start(`Migrating project to use ${"nativescript.config.ts".green}`);
            yield this.migrateConfigs(projectDir);
            this.spinner.text = `Project has been migrated to use ${"nativescript.config.ts".green}`;
            this.spinner.succeed();
            this.spinner.start("Updating project dependencies");
            yield this.migrateDependencies(projectData, platforms, allowInvalidVersions);
            this.spinner.text = "Project dependencies have been updated";
            this.spinner.succeed();
            const tsConfigPath = path.resolve(projectDir, "tsconfig.json");
            if (this.$fs.exists(tsConfigPath)) {
                this.spinner.start("Updating tsconfig.json");
                yield this.migrateTSConfig(tsConfigPath);
                this.spinner.succeed("Updated tsconfig.json");
            }
            this.spinner.succeed("Migration complete.");
            this.$logger.info("");
            this.$logger.printMarkdown("Project has been successfully migrated. The next step is to run `ns run <platform>` to ensure everything is working properly." +
                "\n\nPlease note that `ns migrate` does not make changes to your source code, you may need additional changes to complete the migration.");
        });
    }
    ensureGitCleanOrForce(projectDir) {
        return __awaiter(this, void 0, void 0, function* () {
            const git = simple_git_1.default(projectDir);
            const isGit = yield git.checkIsRepo();
            const isForce = this.$options.force;
            if (!isGit) {
                if (!isForce) {
                    this.$logger.printMarkdown(`Running \`ns migrate\` in a non-git project is not recommended. If you want to skip this check run \`ns migrate --force\`.`);
                    this.$errors.fail("Not in Git repo.");
                    return false;
                }
                this.spinner.warn(`Not in Git repo, but using ${"--force".red}`);
                return true;
            }
            const isClean = (yield git.status()).isClean();
            if (!isClean) {
                if (!isForce) {
                    this.$logger.printMarkdown(`Current git branch has uncommitted changes. Please commit the changes and try again. Alternatively run \`ns migrate --force\` to skip this check.`);
                    this.$errors.fail("Git branch not clean.");
                    return false;
                }
                this.spinner.warn(`Git branch not clean, but using ${"--force".red}`);
                return true;
            }
            return true;
        });
    }
    backupProject(projectDir) {
        return __awaiter(this, void 0, void 0, function* () {
            const projectData = this.$projectDataService.getProjectData(projectDir);
            const backup = this.$projectBackupService.getBackup("migration");
            backup.addPaths([
                ...MigrateController.pathsToBackup,
                path.join(projectData.getAppDirectoryRelativePath(), "package.json"),
            ]);
            try {
                return backup.create();
            }
            catch (error) {
                this.spinner.fail(`Project backup failed.`);
                backup.remove();
                this.$errors.fail(`Project backup failed. Error is: ${error.message}`);
            }
        });
    }
    migrateConfigs(projectDir) {
        return __awaiter(this, void 0, void 0, function* () {
            const projectData = this.$projectDataService.getProjectData(projectDir);
            const rootPackageJsonPath = path.resolve(projectDir, constants.PACKAGE_JSON_FILE_NAME);
            const embeddedPackageJsonPath = path.resolve(projectData.projectDir, projectData.getAppDirectoryRelativePath(), constants.PACKAGE_JSON_FILE_NAME);
            const legacyNsConfigPath = path.resolve(projectData.projectDir, constants.CONFIG_NS_FILE_NAME);
            let rootPackageJsonData = {};
            if (this.$fs.exists(rootPackageJsonPath)) {
                rootPackageJsonData = this.$fs.readJson(rootPackageJsonPath);
            }
            const newConfigPath = this.$projectConfigService.writeDefaultConfig(projectData.projectDir);
            this.$projectConfigService.setForceUsingLegacyConfig(true);
            const configData = this.$projectConfigService.readConfig(projectData.projectDir);
            this.$projectConfigService.setForceUsingLegacyConfig(false);
            if (configData.main) {
                rootPackageJsonData.main = configData.main;
                delete configData.main;
            }
            configData.appPath = this.detectAppPath(projectDir, configData);
            configData.appResourcesPath = this.detectAppResourcesPath(projectDir, configData);
            if (rootPackageJsonData.nativescript) {
                delete rootPackageJsonData.nativescript;
            }
            this.$projectConfigService.setForceUsingNewConfig(true);
            const hasUpdatedConfigSuccessfully = yield this.$projectConfigService.setValue("", configData);
            if (!hasUpdatedConfigSuccessfully) {
                if (typeof newConfigPath === "string") {
                    yield this.$projectCleanupService.cleanPath(newConfigPath);
                }
                this.$errors.fail(`Failed to migrate project to use ${constants.CONFIG_FILE_NAME_TS}. One or more values could not be updated.`);
            }
            this.$fs.writeJson(rootPackageJsonPath, rootPackageJsonData);
            yield this.$projectCleanupService.cleanPath(embeddedPackageJsonPath);
            yield this.$projectCleanupService.cleanPath(legacyNsConfigPath);
            return true;
        });
    }
    _shouldMigrate({ projectDir, platforms, allowInvalidVersions, }) {
        return __awaiter(this, void 0, void 0, function* () {
            const isMigrate = _.get(this.$options, "argv._[0]") === "migrate";
            const projectData = this.$projectDataService.getProjectData(projectDir);
            const projectInfo = this.$projectConfigService.detectProjectConfigs(projectData.projectDir);
            if (!isMigrate && projectInfo.hasNSConfig) {
                return;
            }
            const shouldMigrateCommonMessage = "The app is not compatible with this CLI version and it should be migrated. Reason: ";
            for (let i = 0; i < this.migrationDependencies.length; i++) {
                const dependency = this.migrationDependencies[i];
                const hasDependency = this.hasDependency(dependency, projectData);
                if (hasDependency &&
                    dependency.shouldMigrateAction &&
                    (yield dependency.shouldMigrateAction(projectData, allowInvalidVersions))) {
                    this.$logger.trace(`${shouldMigrateCommonMessage}'${dependency.packageName}' requires an update.`);
                    return true;
                }
                if (hasDependency &&
                    (dependency.replaceWith || dependency.shouldRemove)) {
                    this.$logger.trace(`${shouldMigrateCommonMessage}'${dependency.packageName}' is deprecated.`);
                    return true;
                }
                if (hasDependency &&
                    (yield this.shouldMigrateDependencyVersion(dependency, projectData, allowInvalidVersions))) {
                    this.$logger.trace(`${shouldMigrateCommonMessage}'${dependency.packageName}' should be updated.`);
                    return true;
                }
                if (!hasDependency && dependency.shouldAddIfMissing) {
                    this.$logger.trace(`${shouldMigrateCommonMessage}'${dependency.packageName}' is missing.`);
                    return true;
                }
            }
            for (let platform of platforms) {
                platform = platform && platform.toLowerCase();
                if (!this.$platformValidationService.isValidPlatform(platform, projectData)) {
                    continue;
                }
                const hasRuntimeDependency = this.hasRuntimeDependency({
                    platform,
                    projectData,
                });
                if (hasRuntimeDependency &&
                    (yield this.shouldUpdateRuntimeVersion(this.verifiedPlatformVersions[platform.toLowerCase()], platform, projectData, allowInvalidVersions))) {
                    this.$logger.trace(`${shouldMigrateCommonMessage}Platform '${platform}' should be updated.`);
                    return true;
                }
            }
        });
    }
    getCachedShouldMigrate(projectDir, platform) {
        return __awaiter(this, void 0, void 0, function* () {
            let cachedShouldMigrateValue = null;
            const cachedHash = yield this.$jsonFileSettingsService.getSettingValue(helpers_1.getHash(`${projectDir}${platform.toLowerCase()}`));
            const packageJsonHash = yield this.getPackageJsonHash(projectDir);
            if (cachedHash === packageJsonHash) {
                cachedShouldMigrateValue = false;
            }
            return cachedShouldMigrateValue;
        });
    }
    setCachedShouldMigrate(projectDir, platform) {
        return __awaiter(this, void 0, void 0, function* () {
            const packageJsonHash = yield this.getPackageJsonHash(projectDir);
            yield this.$jsonFileSettingsService.saveSetting(helpers_1.getHash(`${projectDir}${platform.toLowerCase()}`), packageJsonHash);
        });
    }
    getPackageJsonHash(projectDir) {
        return __awaiter(this, void 0, void 0, function* () {
            const projectPackageJsonFilePath = path.join(projectDir, constants.PACKAGE_JSON_FILE_NAME);
            return yield this.$fs.getFileShasum(projectPackageJsonFilePath);
        });
    }
    cleanUpProject(projectData) {
        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.WEBPACK_CONFIG_NAME,
                constants.PACKAGE_LOCK_JSON_FILE_NAME,
            ]);
            const { dependencies, devDependencies, } = yield this.$pluginsService.getDependenciesFromPackageJson(projectData.projectDir);
            const hasSchematics = [...dependencies, ...devDependencies].find((p) => p.name === "@nativescript/schematics");
            if (!hasSchematics) {
                yield this.$projectCleanupService.clean([
                    constants.TSCCONFIG_TNS_JSON_NAME,
                ]);
            }
        });
    }
    handleAutoGeneratedFiles(backup, projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            const globOptions = {
                silent: true,
                nocase: true,
                matchBase: true,
                nodir: true,
                absolute: false,
                cwd: projectData.appDirectoryPath,
            };
            const jsFiles = glob.sync("*.@(js|ts|js.map)", globOptions);
            const autoGeneratedJsFiles = this.getGeneratedFiles(jsFiles, [".js"], [".ts"]);
            const autoGeneratedJsMapFiles = this.getGeneratedFiles(jsFiles, [".map"], [""]);
            const cssFiles = glob.sync("*.@(le|sa|sc|c)ss", globOptions);
            const autoGeneratedCssFiles = this.getGeneratedFiles(cssFiles, [".css"], [".scss", ".sass", ".less"]);
            const allGeneratedFiles = autoGeneratedJsFiles
                .concat(autoGeneratedJsMapFiles)
                .concat(autoGeneratedCssFiles);
            const pathsToBackup = allGeneratedFiles.map((generatedFile) => path.join(projectData.appDirectoryPath, generatedFile));
            backup.addPaths(pathsToBackup);
            backup.create();
            if (backup.isUpToDate()) {
                yield this.$projectCleanupService.clean(pathsToBackup);
            }
        });
    }
    getGeneratedFiles(allFiles, generatedFileExts, sourceFileExts) {
        const autoGeneratedFiles = allFiles.filter((file) => {
            let isGenerated = false;
            const { dir, name, ext } = path.parse(file);
            if (generatedFileExts.indexOf(ext) > -1) {
                for (const sourceExt of sourceFileExts) {
                    const possibleSourceFile = path.format({ dir, name, ext: sourceExt });
                    isGenerated = allFiles.indexOf(possibleSourceFile) > -1;
                    if (isGenerated) {
                        break;
                    }
                }
            }
            return isGenerated;
        });
        return autoGeneratedFiles;
    }
    migrateDependencies(projectData, platforms, allowInvalidVersions) {
        return __awaiter(this, void 0, void 0, function* () {
            for (let i = 0; i < this.migrationDependencies.length; i++) {
                const dependency = this.migrationDependencies[i];
                const hasDependency = this.hasDependency(dependency, projectData);
                if (hasDependency &&
                    dependency.migrateAction &&
                    (yield dependency.shouldMigrateAction(projectData, allowInvalidVersions))) {
                    const newDependencies = yield dependency.migrateAction(projectData, path.join(projectData.projectDir, MigrateController.backupFolderName));
                    for (const newDependency of newDependencies) {
                        yield this.migrateDependency(newDependency, projectData, allowInvalidVersions);
                    }
                }
                yield this.migrateDependency(dependency, projectData, allowInvalidVersions);
            }
            for (const platform of platforms) {
                const lowercasePlatform = platform.toLowerCase();
                const hasRuntimeDependency = this.hasRuntimeDependency({
                    platform,
                    projectData,
                });
                if (hasRuntimeDependency &&
                    (yield this.shouldUpdateRuntimeVersion(this.verifiedPlatformVersions[lowercasePlatform], platform, projectData, allowInvalidVersions))) {
                    const verifiedPlatformVersion = this.verifiedPlatformVersions[lowercasePlatform];
                    const platformData = this.$platformsDataService.getPlatformData(lowercasePlatform, projectData);
                    this.spinner.info(`Updating ${platform} platform to version '${verifiedPlatformVersion}'.`);
                    yield this.$addPlatformService.setPlatformVersion(platformData, projectData, verifiedPlatformVersion);
                    this.spinner.succeed();
                }
            }
        });
    }
    migrateDependency(dependency, projectData, allowInvalidVersions) {
        return __awaiter(this, void 0, void 0, function* () {
            const hasDependency = this.hasDependency(dependency, projectData);
            if (hasDependency && dependency.warning) {
                this.$logger.warn(dependency.warning);
            }
            if (hasDependency && (dependency.replaceWith || dependency.shouldRemove)) {
                this.$pluginsService.removeFromPackageJson(dependency.packageName, projectData.projectDir);
                if (dependency.replaceWith) {
                    const replacementDep = _.find(this.migrationDependencies, (migrationPackage) => migrationPackage.packageName === dependency.replaceWith);
                    if (!replacementDep) {
                        this.$errors.fail("Failed to find replacement dependency.");
                    }
                    this.$pluginsService.addToPackageJson(replacementDep.packageName, replacementDep.verifiedVersion, replacementDep.isDev, projectData.projectDir);
                    this.spinner.clear();
                    this.$logger.info(`  - ${dependency.packageName.yellow} has been replaced with ${replacementDep.packageName.cyan} ${`v${replacementDep.verifiedVersion}`.green}`);
                    this.spinner.render();
                }
                return;
            }
            if (hasDependency &&
                (yield this.shouldMigrateDependencyVersion(dependency, projectData, allowInvalidVersions))) {
                this.$pluginsService.addToPackageJson(dependency.packageName, dependency.verifiedVersion, dependency.isDev, projectData.projectDir);
                this.spinner.clear();
                this.$logger.info(`  - ${dependency.packageName.yellow} has been updated to ${`v${dependency.verifiedVersion}`.green}`);
                this.spinner.render();
                return;
            }
            if (!hasDependency && dependency.shouldAddIfMissing) {
                this.$pluginsService.addToPackageJson(dependency.packageName, dependency.verifiedVersion, dependency.isDev, projectData.projectDir);
                this.spinner.clear();
                this.$logger.info(`  - ${dependency.packageName.yellow} ${`v${dependency.verifiedVersion}`.green} has been added`);
                this.spinner.render();
            }
        });
    }
    shouldMigrateDependencyVersion(dependency, projectData, allowInvalidVersions) {
        return __awaiter(this, void 0, void 0, function* () {
            const installedVersion = yield this.$packageInstallationManager.getInstalledDependencyVersion(dependency.packageName, projectData.projectDir);
            const requiredVersion = dependency.verifiedVersion;
            if (dependency.shouldUseExactVersion &&
                installedVersion !== requiredVersion) {
                return true;
            }
            return this.isOutdatedVersion(installedVersion, requiredVersion, allowInvalidVersions);
        });
    }
    shouldUpdateRuntimeVersion(targetVersion, platform, projectData, allowInvalidVersions) {
        return __awaiter(this, void 0, void 0, function* () {
            const installedVersion = yield this.getMaxRuntimeVersion({
                platform,
                projectData,
            });
            return this.isOutdatedVersion(installedVersion, targetVersion, allowInvalidVersions);
        });
    }
    isOutdatedVersion(version, targetVersion, allowInvalidVersions) {
        return !!version
            ? semver.lt(version, targetVersion)
            : !allowInvalidVersions;
    }
    detectAppPath(projectDir, configData) {
        if (configData.appPath) {
            return configData.appPath;
        }
        const possibleAppPaths = [
            path.resolve(projectDir, constants.SRC_DIR),
            path.resolve(projectDir, constants.APP_FOLDER_NAME),
        ];
        const appPath = possibleAppPaths.find((possiblePath) => this.$fs.exists(possiblePath));
        if (appPath) {
            const relativeAppPath = path
                .relative(projectDir, appPath)
                .replace(path.sep, "/");
            this.$logger.trace(`Found app source at '${appPath}'.`);
            return relativeAppPath.toString();
        }
    }
    detectAppResourcesPath(projectDir, configData) {
        if (configData.appResourcesPath) {
            return configData.appResourcesPath;
        }
        const possibleAppResourcesPaths = [
            path.resolve(projectDir, configData.appPath, constants.APP_RESOURCES_FOLDER_NAME),
            path.resolve(projectDir, constants.APP_RESOURCES_FOLDER_NAME),
        ];
        const appResourcesPath = possibleAppResourcesPaths.find((possiblePath) => this.$fs.exists(possiblePath));
        if (appResourcesPath) {
            const relativeAppResourcesPath = path
                .relative(projectDir, appResourcesPath)
                .replace(path.sep, "/");
            this.$logger.trace(`Found App_Resources at '${appResourcesPath}'.`);
            return relativeAppResourcesPath.toString();
        }
    }
    migrateUnitTestRunner(projectData, migrationBackupDirPath) {
        return __awaiter(this, void 0, void 0, function* () {
            const pathToKarmaConfig = path.join(migrationBackupDirPath, constants.KARMA_CONFIG_NAME);
            if (this.$fs.exists(pathToKarmaConfig)) {
                const oldKarmaContent = this.$fs.readText(pathToKarmaConfig);
                const regExp = /frameworks:\s+\[([\S\s]*?)\]/g;
                const matches = regExp.exec(oldKarmaContent);
                const frameworks = (matches && matches[1] && matches[1].trim()) || '["jasmine"]';
                const testsDir = path.join(projectData.appDirectoryPath, "tests");
                const relativeTestsDir = path.relative(projectData.projectDir, testsDir);
                const testFiles = `'${helpers_1.fromWindowsRelativePathToUnix(relativeTestsDir)}/**/*.*'`;
                const karmaConfTemplate = this.$resources.readText("test/karma.conf.js");
                const karmaConf = _.template(karmaConfTemplate)({
                    frameworks,
                    testFiles,
                });
                this.$fs.writeFile(path.join(projectData.projectDir, constants.KARMA_CONFIG_NAME), karmaConf);
            }
            const dependencies = [
                {
                    packageName: "karma-webpack",
                    verifiedVersion: "3.0.5",
                    isDev: true,
                    shouldAddIfMissing: true,
                },
                { packageName: "karma-jasmine", verifiedVersion: "2.0.1", isDev: true },
                { packageName: "karma-mocha", verifiedVersion: "1.3.0", isDev: true },
                { packageName: "karma-chai", verifiedVersion: "0.1.0", isDev: true },
                { packageName: "karma-qunit", verifiedVersion: "3.1.2", isDev: true },
                { packageName: "karma", verifiedVersion: "4.1.0", isDev: true },
            ];
            return dependencies;
        });
    }
    migrateTSConfig(tsConfigPath) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                const configContents = this.$fs.readJson(tsConfigPath);
                configContents.compilerOptions = configContents.compilerOptions || {};
                configContents.compilerOptions.target = "es2017";
                configContents.compilerOptions.module = "esnext";
                configContents.compilerOptions.moduleResolution = "node";
                configContents.compilerOptions.experimentalDecorators = true;
                configContents.compilerOptions.removeComments = false;
                configContents.compilerOptions.lib = [
                    ...new Set([...(configContents.compilerOptions.lib || []), "es2017"]),
                ];
                this.$fs.writeJson(tsConfigPath, configContents);
                return true;
            }
            catch (error) {
                this.$logger.trace("Failed to migrate tsconfig.json. Error is: ", error);
                return false;
            }
        });
    }
    migrateNativeScriptAngular() {
        return __awaiter(this, void 0, void 0, function* () {
            const dependencies = [
                {
                    packageName: "@angular/platform-browser-dynamic",
                    verifiedVersion: "10.0.0",
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "@angular/common",
                    verifiedVersion: "10.0.0",
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "@angular/compiler",
                    verifiedVersion: "10.0.0",
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "@angular/core",
                    verifiedVersion: "10.0.0",
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "@angular/forms",
                    verifiedVersion: "10.0.0",
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "@angular/platform-browser",
                    verifiedVersion: "10.0.0",
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "@angular/router",
                    verifiedVersion: "10.0.0",
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "rxjs",
                    verifiedVersion: "6.6.0",
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "zone.js",
                    verifiedVersion: "0.11.1",
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "@angular/animations",
                    verifiedVersion: "10.0.0",
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "@angular/compiler-cli",
                    verifiedVersion: "10.0.0",
                    isDev: true,
                },
                {
                    packageName: "@ngtools/webpack",
                    verifiedVersion: "10.0.0",
                    isDev: true,
                },
                {
                    packageName: "@angular-devkit/build-angular",
                    verifiedVersion: "0.1000.8",
                    isDev: true,
                },
            ];
            return dependencies;
        });
    }
    migrateNativeScriptVue() {
        return __awaiter(this, void 0, void 0, function* () {
            const dependencies = [
                {
                    packageName: "nativescript-vue-template-compiler",
                    verifiedVersion: "2.8.0",
                    shouldUseExactVersion: true,
                    isDev: true,
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "vue-loader",
                    verifiedVersion: "15.9.3",
                    shouldUseExactVersion: true,
                    isDev: true,
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "vue",
                    shouldRemove: true,
                },
                {
                    packageName: "vue",
                    verifiedVersion: "2.6.12",
                    shouldUseExactVersion: true,
                    isDev: true,
                },
            ];
            return dependencies;
        });
    }
    migrateWebpack() {
        return __awaiter(this, void 0, void 0, function* () {
            const scopedWebpackDeps = [
                "@angular-devkit/core",
                "clean-webpack-plugin",
                "copy-webpack-plugin",
                "css",
                "css-loader",
                "escape-string-regexp",
                "fork-ts-checker-webpack-plugin",
                "global-modules-path",
                "loader-utils",
                "minimatch",
                "@nativescript/hook",
                "nativescript-worker-loader",
                "properties-reader",
                "proxy-lib",
                "raw-loader",
                "resolve-url-loader",
                "sass-loader",
                "sax",
                "schema-utils",
                "semver",
                "shelljs",
                "tapable",
                "terser",
                "terser-webpack-plugin",
                "ts-loader",
                "webpack",
                "webpack-bundle-analyzer",
                "webpack-cli",
                "webpack-sources",
            ];
            const dependencies = scopedWebpackDeps.map((dep) => {
                return {
                    packageName: dep,
                    shouldRemove: true,
                };
            });
            return dependencies;
        });
    }
}
exports.MigrateController = MigrateController;
MigrateController.backupFolderName = ".migration_backup";
MigrateController.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.TSCCONFIG_TNS_JSON_NAME,
    constants.KARMA_CONFIG_NAME,
    constants.CONFIG_NS_FILE_NAME,
];
yok_1.injector.register("migrateController", MigrateController);
