"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_1 = 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");
const temp = require("temp");
const color_1 = require("../color");
class MigrateController extends update_controller_base_1.UpdateControllerBase {
    constructor($fs, $platformCommandHelper, $platformsDataService, $packageInstallationManager, $packageManager, $pacoteService, $logger, $errors, $pluginsService, $projectDataService, $projectConfigService, $options, $resources, $injector, $settingsService, $staticConfig, $terminalSpinnerService, $projectCleanupService, $projectBackupService, $childProcess) {
        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.$logger = $logger;
        this.$errors = $errors;
        this.$pluginsService = $pluginsService;
        this.$projectDataService = $projectDataService;
        this.$projectConfigService = $projectConfigService;
        this.$options = $options;
        this.$resources = $resources;
        this.$injector = $injector;
        this.$settingsService = $settingsService;
        this.$staticConfig = $staticConfig;
        this.$terminalSpinnerService = $terminalSpinnerService;
        this.$projectCleanupService = $projectCleanupService;
        this.$projectBackupService = $projectBackupService;
        this.$childProcess = $childProcess;
        this.migrationDependencies = [
            {
                packageName: "@nativescript/core",
                minVersion: "6.5.0",
                desiredVersion: "~8.5.0",
                shouldAddIfMissing: true,
            },
            {
                packageName: "tns-core-modules",
                shouldRemove: true,
            },
            {
                packageName: "@nativescript/types",
                minVersion: "7.0.0",
                desiredVersion: "~8.5.0",
                isDev: true,
            },
            {
                packageName: "tns-platform-declarations",
                replaceWith: "@nativescript/types",
                minVersion: "6.5.0",
                isDev: true,
            },
            {
                packageName: "tns-core-modules-widgets",
                shouldRemove: true,
            },
            {
                packageName: "nativescript-dev-webpack",
                replaceWith: "@nativescript/webpack",
                shouldRemove: true,
                isDev: true,
                shouldMigrateAction() {
                    return __awaiter(this, void 0, void 0, function* () {
                        return true;
                    });
                },
                migrateAction: this.migrateWebpack.bind(this),
            },
            {
                packageName: "@nativescript/webpack",
                minVersion: "3.0.0",
                desiredVersion: "~5.0.0",
                shouldAddIfMissing: true,
                isDev: true,
            },
            {
                packageName: "nativescript-vue",
                minVersion: "2.7.0",
                desiredVersion: "~2.9.3",
                shouldMigrateAction(dependency, projectData, loose) {
                    return __awaiter(this, void 0, void 0, function* () {
                        if (!this.hasDependency(dependency, projectData)) {
                            return false;
                        }
                        return yield this.shouldMigrateDependencyVersion(dependency, projectData, loose);
                    });
                },
                migrateAction: this.migrateNativeScriptVue.bind(this),
            },
            {
                packageName: "nativescript-angular",
                replaceWith: "@nativescript/angular",
                minVersion: "10.0.0",
            },
            {
                packageName: "@nativescript/angular",
                minVersion: "10.0.0",
                desiredVersion: "^16.0.0",
                shouldMigrateAction(dependency, projectData, loose) {
                    return __awaiter(this, void 0, void 0, function* () {
                        if (!this.hasDependency(dependency, projectData)) {
                            return false;
                        }
                        return yield this.shouldMigrateDependencyVersion(dependency, projectData, loose);
                    });
                },
                migrateAction: this.migrateNativeScriptAngular.bind(this),
            },
            {
                packageName: "svelte-native",
                minVersion: "0.9.0",
                desiredVersion: "~0.9.4",
                shouldMigrateAction(dependency, projectData, loose) {
                    return __awaiter(this, void 0, void 0, function* () {
                        if (!this.hasDependency(dependency, projectData)) {
                            return false;
                        }
                        return yield this.shouldMigrateDependencyVersion(dependency, projectData, loose);
                    });
                },
                migrateAction: this.migrateNativeScriptSvelte.bind(this),
            },
            {
                packageName: "nativescript-unit-test-runner",
                replaceWith: "@nativescript/unit-test-runner",
                shouldRemove: true,
                isDev: true,
                shouldMigrateAction() {
                    return __awaiter(this, void 0, void 0, function* () {
                        return true;
                    });
                },
                migrateAction: this.migrateUnitTestRunner.bind(this),
            },
            {
                packageName: "@nativescript/unit-test-runner",
                minVersion: "1.0.0",
                desiredVersion: "~3.0.0",
                shouldMigrateAction(dependency, projectData, loose) {
                    return __awaiter(this, void 0, void 0, function* () {
                        if (!this.hasDependency(dependency, projectData)) {
                            return false;
                        }
                        return yield this.shouldMigrateDependencyVersion(dependency, projectData, loose);
                    });
                },
                migrateAction: this.migrateUnitTestRunner.bind(this),
            },
            {
                packageName: "typescript",
                isDev: true,
                minVersion: "3.7.0",
                desiredVersion: "~4.8.4",
            },
            {
                packageName: "node-sass",
                replaceWith: "sass",
                minVersion: "0.0.0",
                isDev: true,
            },
            {
                packageName: "sass",
                minVersion: "0.0.0",
                desiredVersion: "~1.49.9",
                isDev: true,
            },
            {
                packageName: "tns-ios",
                minVersion: "6.5.3",
                replaceWith: "@nativescript/ios",
                isDev: true,
            },
            {
                packageName: "tns-android",
                minVersion: "6.5.4",
                replaceWith: "@nativescript/android",
                isDev: true,
            },
            {
                packageName: "@nativescript/ios",
                minVersion: "6.5.3",
                desiredVersion: "~8.5.0",
                isDev: true,
            },
            {
                packageName: "@nativescript/android",
                minVersion: "7.0.0",
                desiredVersion: "~8.5.0",
                isDev: true,
            },
        ];
    }
    get $jsonFileSettingsService() {
        const cliVersion = semver.coerce(this.$staticConfig.version);
        const shouldMigrateCacheFilePath = path.join(this.$settingsService.getProfileDir(), `should-migrate-cache-${cliVersion}.json`);
        this.$logger.trace(`Migration cache path is: ${shouldMigrateCacheFilePath}`);
        return this.$injector.resolve("jsonFileSettingsService", {
            jsonFileSettingsPath: shouldMigrateCacheFilePath,
        });
    }
    shouldMigrate({ projectDir, platforms, loose = false, }) {
        return __awaiter(this, void 0, void 0, function* () {
            const remainingPlatforms = [];
            let shouldMigrate = false;
            for (const platform of platforms) {
                if (!loose) {
                    remainingPlatforms.push(platform);
                    continue;
                }
                const cachedResult = yield this.getCachedShouldMigrate(projectDir, platform);
                this.$logger.trace(`Got cached result for shouldMigrate for platform: ${platform}: ${cachedResult}`);
                if (cachedResult !== false) {
                    remainingPlatforms.push(platform);
                }
            }
            if (remainingPlatforms.length > 0) {
                shouldMigrate = yield this._shouldMigrate({
                    projectDir,
                    platforms: remainingPlatforms,
                    loose,
                });
                this.$logger.trace(`Executed shouldMigrate for platforms: ${remainingPlatforms}. Result is: ${shouldMigrate}`);
                if (!shouldMigrate && loose) {
                    for (const remainingPlatform of remainingPlatforms) {
                        yield this.setCachedShouldMigrate(projectDir, remainingPlatform);
                    }
                }
            }
            return shouldMigrate;
        });
    }
    validate({ projectDir, platforms, loose = true, }) {
        return __awaiter(this, void 0, void 0, function* () {
            const shouldMigrate = yield this.shouldMigrate({
                projectDir,
                platforms,
                loose,
            });
            if (shouldMigrate) {
                this.$errors.fail(`The current application is not compatible with NativeScript CLI ${this.$staticConfig.version}.\n\nRun 'ns migrate' to migrate your project to the latest NativeScript version.\n\nAlternatively you may try running it with '--force' to skip this check.`);
            }
        });
    }
    migrate({ projectDir, platforms, loose = 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,
                loose: loose,
            });
            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.info("Backing up project files before migration");
            const backup = yield this.backupProject(projectDir);
            this.spinner.succeed("Project files have been backed up");
            this.spinner.info("Cleaning up project files before migration");
            yield this.cleanUpProject(projectData);
            this.spinner.succeed("Project files have been cleaned up");
            this.spinner.info("Cleaning up old artifacts");
            yield this.handleAutoGeneratedFiles(backup, projectData);
            this.spinner.succeed("Cleaned old artifacts");
            const newConfigPath = path.resolve(projectDir, "nativescript.config.ts");
            if (!this.$fs.exists(newConfigPath)) {
                this.spinner.info(`Migrating project to use ${color_1.color.green("nativescript.config.ts")}`);
                yield this.migrateConfigs(projectDir);
                this.spinner.succeed(`Project has been migrated to use ${color_1.color.green("nativescript.config.ts")}`);
            }
            this.spinner.info("Updating project dependencies");
            yield this.migrateDependencies(projectData, platforms, loose);
            this.spinner.succeed("Project dependencies have been updated");
            const isAngular = this.hasDependency({
                packageName: "@nativescript/angular",
            }, projectData);
            let polyfillsPath;
            if (isAngular) {
                polyfillsPath = yield this.checkOrCreatePolyfillsTS(projectData);
            }
            const tsConfigPath = path.resolve(projectDir, "tsconfig.json");
            if (this.$fs.exists(tsConfigPath)) {
                this.spinner.info(`Updating ${color_1.color.yellow("tsconfig.json")}`);
                yield this.migrateTSConfig({
                    tsConfigPath,
                    isAngular,
                    polyfillsPath,
                });
                this.spinner.succeed(`Updated ${color_1.color.yellow("tsconfig.json")}`);
            }
            yield this.migrateWebpack5(projectDir, projectData);
            yield this.runESLint(projectDir);
            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 you may need additional changes to complete the migration.");
        });
    }
    _shouldMigrate({ projectDir, platforms, loose, }) {
        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) {
                    if (dependency.shouldAddIfMissing) {
                        this.$logger.trace(`${shouldMigrateCommonMessage}'${dependency.packageName}' is missing.`);
                        if (loose) {
                            continue;
                        }
                        return true;
                    }
                    continue;
                }
                if (dependency.shouldMigrateAction) {
                    const shouldMigrate = yield dependency.shouldMigrateAction.bind(this)(dependency, projectData, loose);
                    if (shouldMigrate) {
                        this.$logger.trace(`${shouldMigrateCommonMessage}'${dependency.packageName}' requires an update.`);
                        return true;
                    }
                }
                if (dependency.replaceWith || dependency.shouldRemove) {
                    this.$logger.trace(`${shouldMigrateCommonMessage}'${dependency.packageName}' is deprecated.`);
                    if (loose) {
                        continue;
                    }
                    return true;
                }
                const shouldUpdate = yield this.shouldMigrateDependencyVersion(dependency, projectData, loose);
                if (shouldUpdate) {
                    this.$logger.trace(`${shouldMigrateCommonMessage}'${dependency.packageName}' should be updated.`);
                    return true;
                }
            }
            return false;
        });
    }
    shouldMigrateDependencyVersion(dependency, projectData, loose) {
        var _a, _b;
        return __awaiter(this, void 0, void 0, function* () {
            const installedVersion = yield this.$packageInstallationManager.getInstalledDependencyVersion(dependency.packageName, projectData.projectDir);
            const desiredVersion = (_a = dependency.desiredVersion) !== null && _a !== void 0 ? _a : dependency.minVersion;
            const minVersion = (_b = dependency.minVersion) !== null && _b !== void 0 ? _b : dependency.desiredVersion;
            if (dependency.shouldUseExactVersion &&
                installedVersion !== desiredVersion) {
                return true;
            }
            return this.isOutdatedVersion(installedVersion, { minVersion, desiredVersion }, loose);
        });
    }
    getCachedShouldMigrate(projectDir, platform) {
        return __awaiter(this, void 0, void 0, function* () {
            let cachedShouldMigrateValue = null;
            const cachedHash = yield this.$jsonFileSettingsService.getSettingValue((0, 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* () {
            this.$logger.trace(`Caching shouldMigrate result for platform ${platform}.`);
            const packageJsonHash = yield this.getPackageJsonHash(projectDir);
            yield this.$jsonFileSettingsService.saveSetting((0, 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);
        });
    }
    ensureGitCleanOrForce(projectDir) {
        return __awaiter(this, void 0, void 0, function* () {
            const git = (0, 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 ${color_1.color.red("--force")}`);
                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 ${color_1.color.red("--force")}`);
                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}`);
            }
        });
    }
    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.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 = {
                nocase: true,
                matchBase: true,
                nodir: true,
                absolute: false,
                cwd: projectData.appDirectoryPath,
                withFileTypes: false,
            };
            const jsFiles = (0, glob_1.globSync)("*.@(js|ts|js.map)", globOptions);
            const autoGeneratedJsFiles = this.getGeneratedFiles(jsFiles, [".js"], [".ts"]);
            const autoGeneratedJsMapFiles = this.getGeneratedFiles(jsFiles, [".map"], [""]);
            const cssFiles = (0, glob_1.globSync)("*.@(less|sass|scss|css)", 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) {
        return 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;
        });
    }
    isOutdatedVersion(current, target, loose) {
        if (!current && loose) {
            return false;
        }
        const installed = semver.coerce(current);
        const min = semver.coerce(target.minVersion);
        const desired = semver.coerce(target.desiredVersion);
        if (loose) {
            if (!installed || !min) {
                return false;
            }
            return semver.lt(installed, min);
        }
        if (!installed || !desired) {
            return true;
        }
        return semver.lt(installed, desired);
    }
    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();
        }
    }
    runMigrateActionIfAny(dependency, projectData, loose, force = false) {
        return __awaiter(this, void 0, void 0, function* () {
            if (dependency.migrateAction) {
                const shouldMigrate = force ||
                    (yield dependency.shouldMigrateAction.bind(this)(dependency, projectData, loose));
                if (shouldMigrate) {
                    const newDependencies = yield dependency.migrateAction(projectData, path.join(projectData.projectDir, MigrateController.backupFolderName));
                    for (const newDependency of newDependencies) {
                        yield this.migrateDependency(newDependency, projectData, loose);
                    }
                }
            }
        });
    }
    migrateDependencies(projectData, platforms, loose) {
        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.shouldAddIfMissing) {
                    continue;
                }
                yield this.runMigrateActionIfAny(dependency, projectData, loose);
                yield this.migrateDependency(dependency, projectData, loose);
            }
        });
    }
    migrateDependency(dependency, projectData, loose) {
        var _a, _b, _c, _d, _e;
        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) {
                if (!dependency.shouldAddIfMissing) {
                    return;
                }
                const version = (_a = dependency.desiredVersion) !== null && _a !== void 0 ? _a : dependency.minVersion;
                this.$pluginsService.addToPackageJson(dependency.packageName, version, dependency.isDev, projectData.projectDir);
                this.spinner.clear();
                this.$logger.info(`  - ${color_1.color.yellow(dependency.packageName)} ${color_1.color.green(version)} has been added`);
                this.spinner.render();
                return;
            }
            if (dependency.replaceWith || dependency.shouldRemove) {
                this.$pluginsService.removeFromPackageJson(dependency.packageName, projectData.projectDir);
                if (!dependency.replaceWith) {
                    return;
                }
                const replacementDep = _.find(this.migrationDependencies, (migrationPackage) => migrationPackage.packageName === dependency.replaceWith);
                if (!replacementDep) {
                    this.$errors.fail("Failed to find replacement dependency.");
                }
                const version = (_d = (_c = (_b = replacementDep.desiredVersion) !== null && _b !== void 0 ? _b : replacementDep.minVersion) !== null && _c !== void 0 ? _c : dependency.desiredVersion) !== null && _d !== void 0 ? _d : dependency.minVersion;
                this.$pluginsService.addToPackageJson(replacementDep.packageName, version, replacementDep.isDev, projectData.projectDir);
                this.spinner.clear();
                this.$logger.info(`  - ${color_1.color.yellow(dependency.packageName)} has been replaced with ${color_1.color.cyan(replacementDep.packageName)} ${color_1.color.green(version)}`);
                this.spinner.render();
                yield this.runMigrateActionIfAny(replacementDep, projectData, loose, true);
                return;
            }
            const shouldMigrateVersion = yield this.shouldMigrateDependencyVersion(dependency, projectData, loose);
            if (!shouldMigrateVersion) {
                return;
            }
            const version = (_e = dependency.desiredVersion) !== null && _e !== void 0 ? _e : dependency.minVersion;
            this.$pluginsService.addToPackageJson(dependency.packageName, version, dependency.isDev, projectData.projectDir);
            this.spinner.clear();
            this.$logger.info(`  - ${color_1.color.yellow(dependency.packageName)} has been updated to ${color_1.color.green(version)}`);
            this.spinner.render();
        });
    }
    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;
        });
    }
    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 = `'${(0, helpers_1.fromWindowsRelativePathToUnix)(relativeTestsDir)}/**/*.*'`;
                const karmaConfTemplate = this.$resources.readText("test/karma.conf.js");
                const karmaConf = _.template(karmaConfTemplate)({
                    frameworks,
                    testFiles,
                    basePath: projectData.getAppDirectoryRelativePath(),
                });
                this.$fs.writeFile(path.join(projectData.projectDir, constants.KARMA_CONFIG_NAME), karmaConf);
            }
            const dependencies = [
                {
                    packageName: "karma-webpack",
                    shouldRemove: true,
                },
                {
                    packageName: "karma-jasmine",
                    minVersion: "2.0.1",
                    desiredVersion: "~4.0.1",
                    isDev: true,
                },
                {
                    packageName: "karma-mocha",
                    minVersion: "1.3.0",
                    desiredVersion: "~2.0.1",
                    isDev: true,
                },
                {
                    packageName: "karma-chai",
                    minVersion: "0.1.0",
                    desiredVersion: "~0.1.0",
                    isDev: true,
                },
                {
                    packageName: "karma-qunit",
                    minVersion: "3.1.2",
                    desiredVersion: "~4.1.2",
                    isDev: true,
                },
                {
                    packageName: "karma",
                    minVersion: "4.1.0",
                    desiredVersion: "~6.3.4",
                    isDev: true,
                },
            ];
            return dependencies;
        });
    }
    migrateTSConfig({ tsConfigPath, isAngular, polyfillsPath, }) {
        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"]),
                ];
                if (isAngular) {
                    if (configContents.files) {
                        configContents.files = [
                            ...new Set([
                                ...(configContents.files || []),
                                polyfillsPath !== null && polyfillsPath !== void 0 ? polyfillsPath : "./src/polyfills.ts",
                            ]),
                        ];
                    }
                }
                this.$fs.writeJson(tsConfigPath, configContents);
                return true;
            }
            catch (error) {
                this.$logger.trace("Failed to migrate tsconfig.json. Error is: ", error);
                return false;
            }
        });
    }
    checkOrCreatePolyfillsTS(projectData) {
        return __awaiter(this, void 0, void 0, function* () {
            const { projectDir, appDirectoryPath } = projectData;
            const possiblePaths = [
                `${appDirectoryPath}/polyfills.ts`,
                `./src/polyfills.ts`,
                `./app/polyfills.ts`,
            ].map((possiblePath) => path.resolve(projectDir, possiblePath));
            let polyfillsPath = possiblePaths.find((possiblePath) => {
                return this.$fs.exists(possiblePath);
            });
            if (polyfillsPath) {
                return "./" + path.relative(projectDir, polyfillsPath);
            }
            const tempDir = temp.mkdirSync({
                prefix: "migrate-angular-polyfills",
            });
            yield this.$pacoteService.extractPackage(constants.RESERVED_TEMPLATE_NAMES["angular"], tempDir);
            this.$fs.copyFile(path.resolve(tempDir, "src/polyfills.ts"), possiblePaths[0]);
            this.$fs.deleteDirectory(tempDir);
            this.spinner.succeed(`Created fresh ${color_1.color.cyan("polyfills.ts")}`);
            return "./" + path.relative(projectDir, possiblePaths[0]);
        });
    }
    migrateNativeScriptAngular() {
        return __awaiter(this, void 0, void 0, function* () {
            const minVersion = "10.0.0";
            const desiredVersion = "~16.2.0";
            const dependencies = [
                {
                    packageName: "@angular/animations",
                    minVersion,
                    desiredVersion,
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "@angular/common",
                    minVersion,
                    desiredVersion,
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "@angular/compiler",
                    minVersion,
                    desiredVersion,
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "@angular/core",
                    minVersion,
                    desiredVersion,
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "@angular/forms",
                    minVersion,
                    desiredVersion,
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "@angular/platform-browser",
                    minVersion,
                    desiredVersion,
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "@angular/platform-browser-dynamic",
                    minVersion,
                    desiredVersion,
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "@angular/router",
                    minVersion,
                    desiredVersion,
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "rxjs",
                    minVersion: "6.6.0",
                    desiredVersion: "~7.8.0",
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "zone.js",
                    minVersion: "0.11.1",
                    desiredVersion: "~0.14.0",
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "@angular/cli",
                    minVersion,
                    desiredVersion,
                    isDev: true,
                },
                {
                    packageName: "@angular/compiler-cli",
                    minVersion,
                    desiredVersion,
                    isDev: true,
                },
                {
                    packageName: "@ngtools/webpack",
                    minVersion,
                    desiredVersion,
                    isDev: true,
                },
                {
                    packageName: "@angular-devkit/build-angular",
                    minVersion,
                    desiredVersion,
                    isDev: true,
                },
            ];
            return dependencies;
        });
    }
    migrateNativeScriptVue() {
        return __awaiter(this, void 0, void 0, function* () {
            const dependencies = [
                {
                    packageName: "nativescript-vue-template-compiler",
                    minVersion: "2.7.0",
                    desiredVersion: "~2.9.3",
                    isDev: true,
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "nativescript-vue-devtools",
                    minVersion: "1.4.0",
                    desiredVersion: "~1.5.1",
                    isDev: true,
                },
                {
                    packageName: "vue-loader",
                    shouldRemove: true,
                },
                {
                    packageName: "babel-loader",
                    shouldRemove: true,
                },
                {
                    packageName: "babel-traverse",
                    shouldRemove: true,
                },
                {
                    packageName: "babel-types",
                    shouldRemove: true,
                },
                {
                    packageName: "babylon",
                    shouldRemove: true,
                },
                {
                    packageName: "@babel/core",
                    shouldRemove: true,
                },
                {
                    packageName: "@babel/preset-env",
                    shouldRemove: true,
                },
                {
                    packageName: "vue",
                    shouldRemove: true,
                },
                {
                    packageName: "vue",
                    desiredVersion: "2.6.12",
                    isDev: true,
                },
            ];
            return dependencies;
        });
    }
    migrateNativeScriptSvelte() {
        return __awaiter(this, void 0, void 0, function* () {
            const dependencies = [
                {
                    packageName: "svelte-native-nativescript-ui",
                    minVersion: "0.9.0",
                    desiredVersion: "~0.9.0",
                    isDev: true,
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "svelte-native-preprocessor",
                    minVersion: "0.2.0",
                    desiredVersion: "~0.2.0",
                    isDev: true,
                    shouldAddIfMissing: true,
                },
                {
                    packageName: "svelte-loader",
                    shouldRemove: true,
                },
                {
                    packageName: "svelte-loader-hot",
                    shouldRemove: true,
                },
                {
                    packageName: "svelte",
                    shouldRemove: true,
                },
                {
                    packageName: "svelte",
                    minVersion: "3.24.1",
                    desiredVersion: "3.24.1",
                    shouldUseExactVersion: true,
                    isDev: true,
                },
            ];
            return dependencies;
        });
    }
    migrateWebpack() {
        return __awaiter(this, void 0, void 0, function* () {
            const webpackDependencies = [
                "@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",
            ];
            return webpackDependencies.map((dep) => {
                return {
                    packageName: dep,
                    shouldRemove: true,
                };
            });
        });
    }
    migrateWebpack5(projectDir, projectData) {
        var _a;
        return __awaiter(this, void 0, void 0, function* () {
            const webpackConfigPath = path.resolve(projectDir, "webpack.config.js");
            if (this.$fs.exists(webpackConfigPath)) {
                const webpackConfigContent = this.$fs.readText(webpackConfigPath);
                if (webpackConfigContent.includes("webpack.init(")) {
                    this.spinner.succeed(`Project already using new ${color_1.color.yellow("webpack.config.js")}`);
                    return;
                }
            }
            yield this.$projectCleanupService.clean(["webpack.config.js"]);
            this.spinner.info(`Initializing new ${color_1.color.yellow("webpack.config.js")}`);
            const { desiredVersion: webpackVersion } = this.migrationDependencies.find((dep) => dep.packageName === constants.WEBPACK_PLUGIN_NAME);
            try {
                const scopedWebpackPackage = `@nativescript/webpack`;
                const resolvedVersion = yield this.$packageInstallationManager.getMaxSatisfyingVersion(scopedWebpackPackage, webpackVersion);
                yield this.runNPX([
                    "--package",
                    `${scopedWebpackPackage}@${resolvedVersion}`,
                    "nativescript-webpack",
                    "init",
                ]);
                this.spinner.succeed(`Initialized new ${color_1.color.yellow("webpack.config.js")}`);
            }
            catch (err) {
                this.spinner.fail(`Failed to initialize ${color_1.color.yellow("webpack.config.js")}`);
                this.$logger.trace("Failed to initialize webpack.config.js. Error is: ", err);
                this.$logger.printMarkdown(`You can try again by running \`npm install\` (or yarn, pnpm) and then \`npx @nativescript/webpack init\`.`);
            }
            const packageJSON = this.$fs.readJson(projectData.projectFilePath);
            const currentMain = (_a = packageJSON.main) !== null && _a !== void 0 ? _a : "app.js";
            const currentMainTS = currentMain.replace(/.js$/, ".ts");
            const appPath = projectData.appDirectoryPath;
            const possibleMains = [
                `./${appPath}/${currentMain}`,
                `./${appPath}/${currentMainTS}`,
                `./${appPath}/main.js`,
                `./${appPath}/main.ts`,
                `./app/${currentMain}`,
                `./app/${currentMainTS}`,
                `./src/${currentMain}`,
                `./src/${currentMainTS}`,
                `./app/main.js`,
                `./app/main.ts`,
                `./src/main.js`,
                `./src/main.ts`,
            ].map((possibleMain) => path.resolve(projectDir, possibleMain));
            let replacedMain = possibleMains.find((possibleMain) => {
                return this.$fs.exists(possibleMain);
            });
            if (replacedMain) {
                replacedMain = `./${path.relative(projectDir, replacedMain)}`.replace(/\\/g, "/");
                packageJSON.main = replacedMain;
                this.$fs.writeJson(projectData.projectFilePath, packageJSON);
                this.spinner.info(`Updated ${color_1.color.yellow("package.json")} main field to ${color_1.color.green(replacedMain)}`);
            }
            else {
                this.$logger.warn();
                this.$logger.warn("Note:\n-----");
                this.$logger.printMarkdown(`Could not determine the correct \`main\` field for \`package.json\`. Make sure to update it manually, pointing to the actual entry file relative to the \`package.json\`.\n`);
            }
        });
    }
    runESLint(projectDir) {
        return __awaiter(this, void 0, void 0, function* () {
            this.spinner.start(`Running ESLint fixes`);
            try {
                yield this.runNPX(["@nativescript/eslint-plugin", projectDir]);
                this.spinner.succeed(`Applied ESLint fixes`);
            }
            catch (err) {
                this.spinner.fail(`Failed to apply ESLint fixes`);
                this.$logger.trace("Failed to apply ESLint fixes. Error is:", err);
            }
        });
    }
    runNPX(args = []) {
        return __awaiter(this, void 0, void 0, function* () {
            const npxVersion = yield this.$childProcess.exec("npx -v");
            const npxFlags = [];
            if (semver.gt(semver.coerce(npxVersion), "7.0.0")) {
                npxFlags.push("-y");
            }
            const args_ = ["npx", ...npxFlags, ...args];
            yield this.$childProcess.exec(args_.join(" "));
        });
    }
}
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,
];
exports.MigrateController = MigrateController;
yok_1.injector.register("migrateController", MigrateController);
