|
| 1 | +import * as path from "path"; |
| 2 | +import * as semver from "semver"; |
| 3 | +import * as constants from "../constants"; |
| 4 | +import { BaseUpdateController } from "./base-update-controller"; |
| 5 | + |
| 6 | +interface IDependency { |
| 7 | + packageName: string; |
| 8 | + isDev?: boolean; |
| 9 | +} |
| 10 | + |
| 11 | +interface IMigrationDependency extends IDependency { |
| 12 | + mustRemove?: boolean; |
| 13 | + replaceWith?: string; |
| 14 | + verifiedVersion?: string; |
| 15 | + isRequirement?: boolean; |
| 16 | + shouldAdd?: boolean; |
| 17 | +} |
| 18 | + |
| 19 | +export class MigrateController extends BaseUpdateController implements IMigrateController { |
| 20 | + constructor( |
| 21 | + protected $fs: IFileSystem, |
| 22 | + private $logger: ILogger, |
| 23 | + private $platformCommandHelper: IPlatformCommandHelper, |
| 24 | + private $platformsDataService: IPlatformsDataService, |
| 25 | + private $addPlatformService: IAddPlatformService, |
| 26 | + private $pluginsService: IPluginsService, |
| 27 | + private $projectDataService: IProjectDataService, |
| 28 | + private $packageManager: INodePackageManager, |
| 29 | + private $packageInstallationManager: IPackageInstallationManager) { |
| 30 | + super($fs); |
| 31 | + } |
| 32 | + |
| 33 | + static readonly folders: string[] = [ |
| 34 | + constants.LIB_DIR_NAME, |
| 35 | + constants.HOOKS_DIR_NAME, |
| 36 | + constants.WEBPACK_CONFIG_NAME, |
| 37 | + constants.PACKAGE_JSON_FILE_NAME, |
| 38 | + constants.PACKAGE_LOCK_JSON_FILE_NAME, |
| 39 | + constants.TSCCONFIG_TNS_JSON_NAME |
| 40 | + ]; |
| 41 | + |
| 42 | + static readonly migrationDependencies : IMigrationDependency[] = [ |
| 43 | + { packageName: constants.TNS_CORE_MODULES_NAME, isDev: false, isRequirement: true, verifiedVersion: "6.0.0-next-2019-06-10-092158-01"}, |
| 44 | + { packageName: constants.TNS_CORE_MODULES_WIDGETS_NAME, isDev: false, isRequirement: true, verifiedVersion: "6.0.0-next-2019-06-10-092158-01"}, |
| 45 | + { packageName: "node-sass", isDev: true, verifiedVersion: "4.12.0"}, |
| 46 | + { packageName: "typescript", isDev: true, verifiedVersion: "3.4.1"}, |
| 47 | + { packageName: "less", isDev: true, verifiedVersion: "3.9.0"}, |
| 48 | + { packageName: "nativescript-dev-sass", isDev: true, replaceWith: "node-sass", isRequirement: true}, |
| 49 | + { packageName: "nativescript-dev-typescript", isDev: true, replaceWith: "typescript", isRequirement: true}, |
| 50 | + { packageName: "nativescript-dev-less", isDev: true, replaceWith: "less", isRequirement: true}, |
| 51 | + { packageName: constants.WEBPACK_PLUGIN_NAME, isDev: true, isRequirement: true, shouldAdd: true, verifiedVersion: "0.25.0-webpack-2019-06-11-105349-01"}, |
| 52 | + { packageName: "nativescript-camera", verifiedVersion: "4.5.0"}, |
| 53 | + { packageName: "nativescript-geolocation", verifiedVersion: "5.1.0"}, |
| 54 | + { packageName: "nativescript-imagepicker", verifiedVersion: "6.2.0"}, |
| 55 | + { packageName: "nativescript-social-share", verifiedVersion: "1.5.2"}, |
| 56 | + { packageName: "nativescript-ui-chart", verifiedVersion: "5.0.0-androidx-110619"}, |
| 57 | + { packageName: "nativescript-ui-dataform", verifiedVersion: "5.0.0-androidx-110619"}, |
| 58 | + { packageName: "nativescript-ui-gauge", verifiedVersion: "5.0.0-androidx"}, |
| 59 | + { packageName: "nativescript-ui-listview", verifiedVersion: "7.0.0-androidx-110619"}, |
| 60 | + { packageName: "nativescript-ui-sidedrawer", verifiedVersion: "7.0.0-androidx-110619"}, |
| 61 | + { packageName: "nativescript-ui-calendar", verifiedVersion: "5.0.0-androidx-110619-2"}, |
| 62 | + { packageName: "nativescript-ui-autocomplete", verifiedVersion: "5.0.0-androidx-110619"}, |
| 63 | + { packageName: "nativescript-datetimepicker", verifiedVersion: "1.1.0"}, |
| 64 | + //TODO update with compatible with webpack only hooks |
| 65 | + { packageName: "kinvey-nativescript-sdk", verifiedVersion: "4.1.0"}, |
| 66 | + //TODO update with compatible with webpack only hooks |
| 67 | + { packageName: "nativescript-plugin-firebase", verifiedVersion: "8.3.2"}, |
| 68 | + //TODO update with no prerelease version compatible with webpack only hooks |
| 69 | + { packageName: "nativescript-vue", verifiedVersion: "2.3.0-rc.0"}, |
| 70 | + { packageName: "nativescript-permissions", verifiedVersion: "1.3.0"}, |
| 71 | + { packageName: "nativescript-cardview", verifiedVersion: "3.2.0"} |
| 72 | + ]; |
| 73 | + |
| 74 | + static readonly verifiedPlatformVersions: IDictionary<string> = { |
| 75 | + [constants.DEVICE_PLATFORMS.Android.toLowerCase()]: "6.0.0-2019-06-11-172137-01", |
| 76 | + [constants.DEVICE_PLATFORMS.iOS.toLowerCase()]: "6.0.0-2019-06-10-154118-03" |
| 77 | + }; |
| 78 | + |
| 79 | + static readonly tempFolder: string = ".tmp_backup"; |
| 80 | + static readonly updateFailMessage: string = "Could not update the project!"; |
| 81 | + static readonly backupFailMessage: string = "Could not backup project folders!"; |
| 82 | + |
| 83 | + public async migrate({projectDir}: {projectDir: string}): Promise<void> { |
| 84 | + const projectData = this.$projectDataService.getProjectData(projectDir); |
| 85 | + const tmpDir = path.join(projectDir, MigrateController.tempFolder); |
| 86 | + |
| 87 | + try { |
| 88 | + this.backup(MigrateController.folders, tmpDir, projectData); |
| 89 | + } catch (error) { |
| 90 | + this.$logger.error(MigrateController.backupFailMessage); |
| 91 | + this.$fs.deleteDirectory(tmpDir); |
| 92 | + return; |
| 93 | + } |
| 94 | + |
| 95 | + try { |
| 96 | + await this.cleanUpProject(projectData); |
| 97 | + await this.migrateDependencies(projectData); |
| 98 | + } catch (error) { |
| 99 | + this.restoreBackup(MigrateController.folders, tmpDir, projectData); |
| 100 | + this.$logger.error(MigrateController.updateFailMessage); |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | + public async shouldMigrate({projectDir}: IProjectDir): Promise<boolean> { |
| 105 | + const projectData = this.$projectDataService.getProjectData(projectDir); |
| 106 | + for (let i = 0; i < MigrateController.migrationDependencies.length; i++) { |
| 107 | + const dependency = MigrateController.migrationDependencies[i]; |
| 108 | + if (dependency.isRequirement) { |
| 109 | + const collection = dependency.isDev ? projectData.devDependencies : projectData.dependencies; |
| 110 | + if (dependency.replaceWith && collection && collection[dependency.packageName]) { |
| 111 | + return true; |
| 112 | + } |
| 113 | + |
| 114 | + if (!this.shouldSkipDependency(dependency, projectData) && await this.shouldMigrateDependencyVersion(dependency, projectData)) { |
| 115 | + return true; |
| 116 | + } |
| 117 | + } |
| 118 | + } |
| 119 | + } |
| 120 | + |
| 121 | + private async cleanUpProject(projectData: IProjectData) { |
| 122 | + this.$fs.deleteDirectory(path.join(projectData.projectDir, constants.HOOKS_DIR_NAME)); |
| 123 | + this.$fs.deleteDirectory(path.join(projectData.projectDir, constants.PLATFORMS_DIR_NAME)); |
| 124 | + this.$fs.deleteDirectory(path.join(projectData.projectDir, constants.NODE_MODULES_FOLDER_NAME)); |
| 125 | + this.$fs.deleteFile(path.join(projectData.projectDir, constants.WEBPACK_CONFIG_NAME)); |
| 126 | + this.$fs.deleteFile(path.join(projectData.projectDir, constants.PACKAGE_LOCK_JSON_FILE_NAME)); |
| 127 | + this.$fs.deleteFile(path.join(projectData.projectDir, constants.TSCCONFIG_TNS_JSON_NAME)); |
| 128 | + } |
| 129 | + |
| 130 | + private async migrateDependencies(projectData: IProjectData): Promise<void> { |
| 131 | + for (let i = 0; i < MigrateController.migrationDependencies.length; i++) { |
| 132 | + const dependency = MigrateController.migrationDependencies[i]; |
| 133 | + if (this.shouldSkipDependency(dependency, projectData)) { |
| 134 | + continue; |
| 135 | + } |
| 136 | + |
| 137 | + if (dependency.replaceWith) { |
| 138 | + this.$pluginsService.removeFromPackageJson(dependency.packageName, dependency.isDev, projectData.projectDir); |
| 139 | + const replacementDep = _.find(MigrateController.migrationDependencies, migrationPackage => migrationPackage.packageName === dependency.replaceWith); |
| 140 | + this.$pluginsService.addToPackageJson(replacementDep.packageName, replacementDep.verifiedVersion, replacementDep.isDev, projectData.projectDir); |
| 141 | + } else if (await this.shouldMigrateDependencyVersion(dependency, projectData)) { |
| 142 | + this.$pluginsService.addToPackageJson(dependency.packageName, dependency.verifiedVersion, dependency.isDev, projectData.projectDir); |
| 143 | + } |
| 144 | + } |
| 145 | + |
| 146 | + for (let platform in constants.DEVICE_PLATFORMS) { |
| 147 | + platform = platform.toLowerCase(); |
| 148 | + const currentPlatformVersion = this.$platformCommandHelper.getCurrentPlatformVersion(platform, projectData); |
| 149 | + const verifiedPlatformVersion = MigrateController.verifiedPlatformVersions[platform]; |
| 150 | + const platformData = this.$platformsDataService.getPlatformData(platform, projectData); |
| 151 | + if (currentPlatformVersion) { |
| 152 | + const maxPlatformSatisfyingVersion = await this.$packageInstallationManager.maxSatisfyingVersion(platformData.frameworkPackageName, currentPlatformVersion) || currentPlatformVersion; |
| 153 | + if (semver.gte(maxPlatformSatisfyingVersion, verifiedPlatformVersion)) { |
| 154 | + continue; |
| 155 | + } |
| 156 | + } |
| 157 | + |
| 158 | + await this.$addPlatformService.setPlatformVersion(platformData, projectData, verifiedPlatformVersion); |
| 159 | + } |
| 160 | + |
| 161 | + await this.$packageManager.install(projectData.projectDir, projectData.projectDir, { |
| 162 | + disableNpmInstall: false, |
| 163 | + frameworkPath: null, |
| 164 | + ignoreScripts: false, |
| 165 | + path: projectData.projectDir |
| 166 | + }); |
| 167 | + } |
| 168 | + |
| 169 | + private async shouldMigrateDependencyVersion(dependency: IMigrationDependency, projectData: IProjectData): Promise<boolean> { |
| 170 | + const collection = dependency.isDev ? projectData.devDependencies : projectData.dependencies; |
| 171 | + if ( |
| 172 | + collection && |
| 173 | + collection[dependency.packageName] && |
| 174 | + (semver.valid(collection[dependency.packageName]) || semver.validRange(collection[dependency.packageName])) |
| 175 | + ) { |
| 176 | + const maxSatisfyingVersion = await this.$packageInstallationManager.maxSatisfyingVersion(dependency.packageName, collection[dependency.packageName]) || collection[dependency.packageName]; |
| 177 | + const isPrereleaseVersion = semver.prerelease(maxSatisfyingVersion); |
| 178 | + const coerceMaxSatisfying = semver.coerce(maxSatisfyingVersion).version; |
| 179 | + const coerceVerifiedVersion = semver.coerce(dependency.verifiedVersion).version; |
| 180 | + //This makes sure that if the user has a prerelease 6.0.0-next-2019-06-10-092158-01 version we will update it to 6.0.0 |
| 181 | + if (isPrereleaseVersion) { |
| 182 | + if (semver.gt(coerceMaxSatisfying, coerceVerifiedVersion)) { |
| 183 | + return false; |
| 184 | + } |
| 185 | + |
| 186 | + //TODO This should be removed once we update the verified versions to no prerelease versions |
| 187 | + if (isPrereleaseVersion && semver.eq(maxSatisfyingVersion, dependency.verifiedVersion)) { |
| 188 | + return false; |
| 189 | + } |
| 190 | + } else if (semver.gte(coerceMaxSatisfying, coerceVerifiedVersion)) { |
| 191 | + return false; |
| 192 | + } |
| 193 | + } |
| 194 | + |
| 195 | + return true; |
| 196 | + } |
| 197 | + |
| 198 | + private shouldSkipDependency(dependency: IMigrationDependency, projectData: IProjectData): boolean { |
| 199 | + if (!dependency.shouldAdd) { |
| 200 | + const collection = dependency.isDev ? projectData.devDependencies : projectData.dependencies; |
| 201 | + if (!collection[dependency.packageName]) { |
| 202 | + return true; |
| 203 | + } |
| 204 | + } |
| 205 | + } |
| 206 | +} |
| 207 | + |
| 208 | +$injector.register("migrateController", MigrateController); |
0 commit comments