UNPKG

nativescript

Version:

Command-line interface for building NativeScript projects

1,126 lines • 49.4 kB
"use strict"; 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"); // const wait: (ms: number) => Promise<void> = (ms: number = 1000) => // new Promise((resolve) => setTimeout(resolve, ms)); class MigrateController extends update_controller_base_1.UpdateControllerBase { constructor($fs, $platformCommandHelper, $platformsDataService, $packageInstallationManager, $packageManager, $pacoteService, // private $androidResourcesMigrationService: IAndroidResourcesMigrationService, $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.9.0", shouldAddIfMissing: true, }, { packageName: "tns-core-modules", shouldRemove: true, }, { packageName: "@nativescript/types", minVersion: "7.0.0", desiredVersion: "~8.9.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, async shouldMigrateAction() { 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", async shouldMigrateAction(dependency, projectData, loose) { if (!this.hasDependency(dependency, projectData)) { return false; } return await 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: "^19.0.0", async shouldMigrateAction(dependency, projectData, loose) { if (!this.hasDependency(dependency, projectData)) { return false; } return await this.shouldMigrateDependencyVersion(dependency, projectData, loose); }, migrateAction: this.migrateNativeScriptAngular.bind(this), }, { packageName: "svelte-native", minVersion: "0.9.0", desiredVersion: "~0.9.4", async shouldMigrateAction(dependency, projectData, loose) { if (!this.hasDependency(dependency, projectData)) { return false; } return await this.shouldMigrateDependencyVersion(dependency, projectData, loose); }, migrateAction: this.migrateNativeScriptSvelte.bind(this), }, { packageName: "nativescript-unit-test-runner", replaceWith: "@nativescript/unit-test-runner", shouldRemove: true, isDev: true, async shouldMigrateAction() { return true; }, migrateAction: this.migrateUnitTestRunner.bind(this), }, { packageName: "@nativescript/unit-test-runner", minVersion: "1.0.0", desiredVersion: "~3.0.0", async shouldMigrateAction(dependency, projectData, loose) { if (!this.hasDependency(dependency, projectData)) { return false; } return await this.shouldMigrateDependencyVersion(dependency, projectData, loose); }, migrateAction: this.migrateUnitTestRunner.bind(this), }, { packageName: "typescript", isDev: true, minVersion: "3.7.0", desiredVersion: "~5.7.0", }, { packageName: "node-sass", replaceWith: "sass", minVersion: "0.0.0", // ignore isDev: true, // shouldRemove: true, }, { packageName: "sass", minVersion: "0.0.0", // ignore desiredVersion: "^1.49.9", isDev: true, // shouldRemove: true, }, // runtimes { 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.9.0", isDev: true, }, { packageName: "@nativescript/android", minVersion: "7.0.0", desiredVersion: "~8.9.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, }); } async shouldMigrate({ projectDir, platforms, loose = false, }) { const remainingPlatforms = []; let shouldMigrate = false; for (const platform of platforms) { if (!loose) { remainingPlatforms.push(platform); continue; } // should only run in loose mode... const cachedResult = await this.getCachedShouldMigrate(projectDir, platform); this.$logger.trace(`Got cached result for shouldMigrate for platform: ${platform}: ${cachedResult}`); // the cached result is only used if it's false, otherwise we need to check again if (cachedResult !== false) { remainingPlatforms.push(platform); } } if (remainingPlatforms.length > 0) { shouldMigrate = await this._shouldMigrate({ projectDir, platforms: remainingPlatforms, loose, }); this.$logger.trace(`Executed shouldMigrate for platforms: ${remainingPlatforms}. Result is: ${shouldMigrate}`); // only cache results if running in loose mode if (!shouldMigrate && loose) { for (const remainingPlatform of remainingPlatforms) { await this.setCachedShouldMigrate(projectDir, remainingPlatform); } } } return shouldMigrate; } async validate({ projectDir, platforms, loose = true, }) { const shouldMigrate = await 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.`); } } async migrate({ projectDir, platforms, loose = false, }) { this.spinner = this.$terminalSpinnerService.createSpinner(); const projectData = this.$projectDataService.getProjectData(projectDir); this.$logger.trace("MigrationController.migrate called with", { projectDir, platforms, loose: loose, }); // ensure in git repo and require --force if not (for safety) // ensure git branch is clean const canMigrate = await this.ensureGitCleanOrForce(projectDir); if (!canMigrate) { this.spinner.fail("Pre-Migration verification failed"); return; } this.spinner.succeed("Pre-Migration verification complete"); // back up project files and folders this.spinner.info("Backing up project files before migration"); const backup = await this.backupProject(projectDir); this.spinner.succeed("Project files have been backed up"); // clean up project files this.spinner.info("Cleaning up project files before migration"); await this.cleanUpProject(projectData); this.spinner.succeed("Project files have been cleaned up"); // clean up artifacts this.spinner.info("Cleaning up old artifacts"); await this.handleAutoGeneratedFiles(backup, projectData); this.spinner.succeed("Cleaned old artifacts"); const newConfigPath = path.resolve(projectDir, "nativescript.config.ts"); if (!this.$fs.exists(newConfigPath)) { // migrate configs this.spinner.info(`Migrating project to use ${color_1.color.green("nativescript.config.ts")}`); await this.migrateConfigs(projectDir); this.spinner.succeed(`Project has been migrated to use ${color_1.color.green("nativescript.config.ts")}`); } // update dependencies this.spinner.info("Updating project dependencies"); await this.migrateDependencies(projectData, platforms, loose); this.spinner.succeed("Project dependencies have been updated"); const isAngular = this.hasDependency({ packageName: "@nativescript/angular", }, projectData); // ensure polyfills.ts exists in angular projects let polyfillsPath; if (isAngular) { polyfillsPath = await this.checkOrCreatePolyfillsTS(projectData); } // update tsconfig const tsConfigPath = path.resolve(projectDir, "tsconfig.json"); if (this.$fs.exists(tsConfigPath)) { this.spinner.info(`Updating ${color_1.color.yellow("tsconfig.json")}`); await this.migrateTSConfig({ tsConfigPath, isAngular, polyfillsPath, }); this.spinner.succeed(`Updated ${color_1.color.yellow("tsconfig.json")}`); } await this.migrateWebpack5(projectDir, projectData); // run @nativescript/eslint over codebase await 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."); // print markdown for next steps: // if no runtime has been added, print a message that it will be added when they run ns run <platform> // if all is good, run ns migrate clean to clean up backup folders // in case of failure, print diagnostic data: what failed and why // restore all files - or perhaps let the user sort it out // or ns migrate restore - to restore from pre-migration backup // for some known cases, print suggestions perhaps } async _shouldMigrate({ projectDir, platforms, loose, }) { 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) { // in loose mode we ignore missing dependencies continue; } return true; } continue; } if (dependency.shouldMigrateAction) { const shouldMigrate = await 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.`); // in loose mode we ignore deprecated dependencies if (loose) { continue; } return true; } const shouldUpdate = await this.shouldMigrateDependencyVersion(dependency, projectData, loose); if (shouldUpdate) { this.$logger.trace(`${shouldMigrateCommonMessage}'${dependency.packageName}' should be updated.`); return true; } } return false; } async shouldMigrateDependencyVersion(dependency, projectData, loose) { var _a, _b; const installedVersion = await 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); } async getCachedShouldMigrate(projectDir, platform) { let cachedShouldMigrateValue = null; const cachedHash = await this.$jsonFileSettingsService.getSettingValue((0, helpers_1.getHash)(`${projectDir}${platform.toLowerCase()}`)); const packageJsonHash = await this.getPackageJsonHash(projectDir); if (cachedHash === packageJsonHash) { cachedShouldMigrateValue = false; } return cachedShouldMigrateValue; } async setCachedShouldMigrate(projectDir, platform) { this.$logger.trace(`Caching shouldMigrate result for platform ${platform}.`); const packageJsonHash = await this.getPackageJsonHash(projectDir); await this.$jsonFileSettingsService.saveSetting((0, helpers_1.getHash)(`${projectDir}${platform.toLowerCase()}`), packageJsonHash); } async getPackageJsonHash(projectDir) { const projectPackageJsonFilePath = path.join(projectDir, constants.PACKAGE_JSON_FILE_NAME); return await this.$fs.getFileShasum(projectPackageJsonFilePath); } // private async migrateOldAndroidAppResources( // projectData: IProjectData, // backupDir: string // ) { // const appResourcesPath = projectData.getAppResourcesDirectoryPath(); // if (!this.$androidResourcesMigrationService.hasMigrated(appResourcesPath)) { // this.spinner.info("Migrate old Android App_Resources structure."); // try { // await this.$androidResourcesMigrationService.migrate( // appResourcesPath, // backupDir // ); // } catch (error) { // this.$logger.warn( // "Migrate old Android App_Resources structure failed: ", // error.message // ); // } // } // } async ensureGitCleanOrForce(projectDir) { const git = (0, simple_git_1.default)(projectDir); const isGit = await git.checkIsRepo(); const isForce = this.$options.force; if (!isGit) { // not a git repo and no --force 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 = (await 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; } async backupProject(projectDir) { 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}`); } } async cleanUpProject(projectData) { await 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 } = await this.$pluginsService.getDependenciesFromPackageJson(projectData.projectDir); const hasSchematics = [...dependencies, ...devDependencies].find((p) => p.name === "@nativescript/schematics"); if (!hasSchematics) { // clean tsconfig.tns.json if not in a shared project await this.$projectCleanupService.clean([ constants.TSCCONFIG_TNS_JSON_NAME, ]); } } async handleAutoGeneratedFiles(backup, projectData) { 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()) { await 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) { // in loose mode, a falsy version is not considered outdated if (!current && loose) { return false; } const installed = semver.coerce(current); const min = semver.coerce(target.minVersion); const desired = semver.coerce(target.desiredVersion); // in loose mode we check if we satisfy the min version if (loose) { if (!installed || !min) { return false; } return semver.lt(installed, min); } if (!installed || !desired) { return true; } // otherwise we compare with the desired version 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(); } } async runMigrateActionIfAny(dependency, projectData, loose, force = false) { if (dependency.migrateAction) { const shouldMigrate = force || (await dependency.shouldMigrateAction.bind(this)(dependency, projectData, loose)); if (shouldMigrate) { const newDependencies = await dependency.migrateAction(projectData, path.join(projectData.projectDir, MigrateController.backupFolderName)); for (const newDependency of newDependencies) { await this.migrateDependency(newDependency, projectData, loose); } } } } async migrateDependencies(projectData, platforms, loose) { 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; } await this.runMigrateActionIfAny(dependency, projectData, loose); await this.migrateDependency(dependency, projectData, loose); } } async migrateDependency(dependency, projectData, loose) { var _a, _b, _c, _d, _e; const hasDependency = this.hasDependency(dependency, projectData); // show warning if needed 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) { // remove this.$pluginsService.removeFromPackageJson(dependency.packageName, projectData.projectDir); // no replacement required - we're done 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; // add replacement dependency 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(); await this.runMigrateActionIfAny(replacementDep, projectData, loose, true); return; } const shouldMigrateVersion = await 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(); } async migrateConfigs(projectDir) { const projectData = this.$projectDataService.getProjectData(projectDir); // package.json const rootPackageJsonPath = path.resolve(projectDir, constants.PACKAGE_JSON_FILE_NAME); // nested package.json const embeddedPackageJsonPath = path.resolve(projectData.projectDir, projectData.getAppDirectoryRelativePath(), constants.PACKAGE_JSON_FILE_NAME); // nsconfig.json const legacyNsConfigPath = path.resolve(projectData.projectDir, constants.CONFIG_NS_FILE_NAME); let rootPackageJsonData = {}; if (this.$fs.exists(rootPackageJsonPath)) { rootPackageJsonData = this.$fs.readJson(rootPackageJsonPath); } // write the default config unless it already exists const newConfigPath = this.$projectConfigService.writeDefaultConfig(projectData.projectDir); // force legacy config mode this.$projectConfigService.setForceUsingLegacyConfig(true); // all different sources are combined into configData (nested package.json, nsconfig and root package.json[nativescript]) const configData = this.$projectConfigService.readConfig(projectData.projectDir); // we no longer want to force legacy config mode this.$projectConfigService.setForceUsingLegacyConfig(false); // move main key into root package.json if (configData.main) { rootPackageJsonData.main = configData.main; delete configData.main; } // detect appPath and App_Resources path configData.appPath = this.detectAppPath(projectDir, configData); configData.appResourcesPath = this.detectAppResourcesPath(projectDir, configData); // delete nativescript key from root package.json if (rootPackageJsonData.nativescript) { delete rootPackageJsonData.nativescript; } // force the config service to use nativescript.config.ts this.$projectConfigService.setForceUsingNewConfig(true); // migrate data into nativescript.config.ts const hasUpdatedConfigSuccessfully = await this.$projectConfigService.setValue("", // root configData); if (!hasUpdatedConfigSuccessfully) { if (typeof newConfigPath === "string") { // only clean the config if it was created by the migration script await 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.`); } // save root package.json this.$fs.writeJson(rootPackageJsonPath, rootPackageJsonData); // delete migrated files await this.$projectCleanupService.cleanPath(embeddedPackageJsonPath); await this.$projectCleanupService.cleanPath(legacyNsConfigPath); return true; } async migrateUnitTestRunner(projectData, migrationBackupDirPath) { // Migrate karma.conf.js 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); } // Dependencies to migrate 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; } async migrateTSConfig({ tsConfigPath, isAngular, polyfillsPath, }) { try { const configContents = this.$fs.readJson(tsConfigPath); // update configContents.compilerOptions = configContents.compilerOptions || {}; configContents.compilerOptions.target = "es2020"; configContents.compilerOptions.module = "esnext"; configContents.compilerOptions.moduleResolution = "node"; configContents.compilerOptions.experimentalDecorators = true; configContents.compilerOptions.removeComments = false; configContents.compilerOptions.lib = [ ...new Set([...(configContents.compilerOptions.lib || []), "ESNext"]), ]; if (isAngular) { // make sure polyfills.ts is in files 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; } } async checkOrCreatePolyfillsTS(projectData) { 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", }); // get from default angular template await this.$pacoteService.extractPackage(constants.RESERVED_TEMPLATE_NAMES["angular"], tempDir); this.$fs.copyFile(path.resolve(tempDir, "src/polyfills.ts"), possiblePaths[0]); // clean up temp project this.$fs.deleteDirectory(tempDir); this.spinner.succeed(`Created fresh ${color_1.color.cyan("polyfills.ts")}`); return "./" + path.relative(projectDir, possiblePaths[0]); } async migrateNativeScriptAngular() { const minVersion = "10.0.0"; const desiredVersion = "~19.1.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.15.0", shouldAddIfMissing: true, }, // devDependencies { 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; } async migrateNativeScriptVue() { 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, }, // remove any version of vue { packageName: "vue", shouldRemove: true, }, // add latest { packageName: "vue", desiredVersion: "2.6.12", isDev: true, }, ]; return dependencies; } async migrateNativeScriptSvelte() { 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; } async migrateWebpack() { 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, }; }); } async migrateWebpack5(projectDir, projectData) { var _a; 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; } } // clean old config before generating new one await 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 = await this.$packageInstallationManager.getMaxSatisfyingVersion(scopedWebpackPackage, webpackVersion); await 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`); } } async runESLint(projectDir) { this.spinner.start(`Running ESLint fixes`); try { await 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); } } async runNPX(args = []) { const npxVersion = await this.$childProcess.exec("npx -v"); const npxFlags = []; if (semver.gt(semver.coerce(npxVersion), "7.0.0")) { npxFlags.push("-y"); } const args_ = ["npx", ...npxFlags, ...args]; await this.$childProcess.exec(args_.join(" ")); } } exports.MigrateController = MigrateController; // static readonly typescriptPackageName: string = "typescript"; 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); //# sourceMappingURL=migrate-controller.js.map