Skip to content

Commit 0ddb420

Browse files
committed
feat: introduce migrate command
1 parent 314e745 commit 0ddb420

14 files changed

+337
-27
lines changed

lib/bootstrap.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ $injector.requirePublicClass("buildController", "./controllers/build-controller"
5050
$injector.requirePublicClass("runController", "./controllers/run-controller");
5151
$injector.requirePublicClass("debugController", "./controllers/debug-controller");
5252
$injector.requirePublicClass("previewAppController", "./controllers/preview-app-controller");
53+
$injector.requirePublicClass("migrateController", "./controllers/migrate-controller");
5354

5455
$injector.require("prepareDataService", "./services/prepare-data-service");
5556
$injector.require("buildDataService", "./services/build-data-service");
@@ -172,6 +173,7 @@ $injector.require("messages", "./common/messages/messages");
172173
$injector.require("xmlValidator", "./xml-validator");
173174

174175
$injector.requireCommand("post-install-cli", "./commands/post-install");
176+
$injector.requireCommand("migrate", "./commands/migrate");
175177
$injector.requireCommand("update", "./commands/update");
176178

177179
$injector.require("iOSLogFilter", "./services/ios-log-filter");

lib/commands/migrate.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export class MigrateCommand implements ICommand {
2+
public allowedParameters: ICommandParameter[] = [];
3+
4+
constructor(
5+
private $migrateController: IMigrateController,
6+
private $projectData: IProjectData) {
7+
this.$projectData.initializeProjectData();
8+
}
9+
10+
public async execute(args: string[]): Promise<void> {
11+
await this.$migrateController.migrate({projectDir: this.$projectData.projectDir});
12+
}
13+
14+
public async canExecute(args: string[]): Promise<boolean> {
15+
if (!await this.$migrateController.shouldMigrate({ projectDir: this.$projectData.projectDir })) {
16+
throw new Error('Project is already migrated. To get the latest NativesScript packages execute "tns update".');
17+
}
18+
19+
return true;
20+
}
21+
}
22+
23+
$injector.registerCommand("migrate", MigrateCommand);

lib/constants.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ export const NATIVESCRIPT_KEY_NAME = "nativescript";
1010
export const NODE_MODULES_FOLDER_NAME = "node_modules";
1111
export const TNS_MODULES_FOLDER_NAME = "tns_modules";
1212
export const TNS_CORE_MODULES_NAME = "tns-core-modules";
13+
export const WEBPACK_PLUGIN_NAME = "nativescript-dev-webpack";
1314
export const TNS_CORE_MODULES_WIDGETS_NAME = "tns-core-modules-widgets";
1415
export const TNS_ANDROID_RUNTIME_NAME = "tns-android";
1516
export const TNS_IOS_RUNTIME_NAME = "tns-ios";
1617
export const PACKAGE_JSON_FILE_NAME = "package.json";
18+
export const PACKAGE_LOCK_JSON_FILE_NAME = "package-lock.json";
1719
export const ANDROID_DEVICE_APP_ROOT_TEMPLATE = `/data/data/%s/files`;
1820
export const NODE_MODULE_CACHE_PATH_KEY_NAME = "node-modules-cache-path";
1921
export const DEFAULT_APP_IDENTIFIER_PREFIX = "org.nativescript";
@@ -24,6 +26,8 @@ export const LIVESYNC_EXCLUDED_FILE_PATTERNS = ["**/*.js.map", "**/*.ts"];
2426
export const XML_FILE_EXTENSION = ".xml";
2527
export const PLATFORMS_DIR_NAME = "platforms";
2628
export const HOOKS_DIR_NAME = "hooks";
29+
export const WEBPACK_CONFIG_NAME = "webpack.config.js";
30+
export const TSCCONFIG_TNS_JSON_NAME = "tsconfig.tns.json";
2731
export const LIB_DIR_NAME = "lib";
2832
export const CODE_SIGN_ENTITLEMENTS = "CODE_SIGN_ENTITLEMENTS";
2933
export const AWAIT_NOTIFICATION_TIMEOUT_SECONDS = 9;
@@ -261,6 +265,11 @@ export class PluginNativeDirNames {
261265
public static Android = "android";
262266
}
263267

268+
export const DEVICE_PLATFORMS: IDictionary<string> = {
269+
iOS: "iOS",
270+
Android: "Android"
271+
};
272+
264273
export const PODFILE_NAME = "Podfile";
265274

266275
export class IosProjectConstants {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import * as path from "path";
2+
3+
export class BaseUpdateController {
4+
constructor(protected $fs: IFileSystem) {
5+
}
6+
7+
protected restoreBackup(folders: string[], tmpDir: string, projectData: IProjectData): void {
8+
for (const folder of folders) {
9+
this.$fs.deleteDirectory(path.join(projectData.projectDir, folder));
10+
11+
const folderToCopy = path.join(tmpDir, folder);
12+
13+
if (this.$fs.exists(folderToCopy)) {
14+
this.$fs.copyFile(folderToCopy, projectData.projectDir);
15+
}
16+
}
17+
}
18+
19+
protected backup(folders: string[], tmpDir: string, projectData: IProjectData): void {
20+
this.$fs.deleteDirectory(tmpDir);
21+
this.$fs.createDirectory(tmpDir);
22+
for (const folder of folders) {
23+
const folderToCopy = path.join(projectData.projectDir, folder);
24+
if (this.$fs.exists(folderToCopy)) {
25+
this.$fs.copyFile(folderToCopy, tmpDir);
26+
}
27+
}
28+
}
29+
}

lib/controllers/migrate-controller.ts

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
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);

lib/declarations.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ interface IPackageInstallationManager {
8888
getLatestVersion(packageName: string): Promise<string>;
8989
getNextVersion(packageName: string): Promise<string>;
9090
getLatestCompatibleVersion(packageName: string, referenceVersion?: string): Promise<string>;
91+
maxSatisfyingVersion(packageName: string, versionRange: string): Promise<string>;
9192
getLatestCompatibleVersionSafe(packageName: string, referenceVersion?: string): Promise<string>;
9293
getInspectorFromCache(inspectorNpmPackageName: string, projectDir: string): Promise<string>;
9394
clearInspectorCache(): void;
@@ -1030,7 +1031,7 @@ interface IPlatformValidationService {
10301031
}
10311032

10321033
interface IPlatformCommandHelper {
1033-
addPlatforms(platforms: string[], projectData: IProjectData, frameworkPath: string): Promise<void>;
1034+
addPlatforms(platforms: string[], projectData: IProjectData, frameworkPath?: string): Promise<void>;
10341035
cleanPlatforms(platforms: string[], projectData: IProjectData, framworkPath: string): Promise<void>;
10351036
removePlatforms(platforms: string[], projectData: IProjectData): Promise<void>;
10361037
updatePlatforms(platforms: string[], projectData: IProjectData): Promise<void>;

lib/definitions/migrate.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
interface IMigrateController {
2+
migrate(migrateData: IProjectDir): Promise<void>;
3+
shouldMigrate(data: IProjectDir): Promise<boolean>;
4+
}

lib/definitions/platform.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,5 @@ interface IPlatformController {
9494

9595
interface IAddPlatformService {
9696
addPlatformSafe(projectData: IProjectData, platformData: IPlatformData, packageToInstall: string, nativePrepare: INativePrepare): Promise<string>;
97+
setPlatformVersion(platformData: IPlatformData, projectData: IProjectData, frameworkVersion: string): Promise<void>
9798
}

lib/definitions/plugins.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
interface IPluginsService {
22
add(plugin: string, projectData: IProjectData): Promise<void>; // adds plugin by name, github url, local path and et.
33
remove(pluginName: string, projectData: IProjectData): Promise<void>; // removes plugin only by name
4+
addToPackageJson(plugin: string, version: string, dev: boolean, projectDir: string): void;
5+
removeFromPackageJson(plugin: string, dev: boolean, projectDir: string): void;
46
getAllInstalledPlugins(projectData: IProjectData): Promise<IPluginData[]>;
57
ensureAllDependenciesAreInstalled(projectData: IProjectData): Promise<void>;
68

lib/helpers/bundle-validator-helper.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import * as util from "util";
22
import { BundleValidatorMessages } from "../constants";
33
import { VersionValidatorHelper } from "./version-validator-helper";
4+
import { WEBPACK_PLUGIN_NAME } from "../constants";
45

56
export class BundleValidatorHelper extends VersionValidatorHelper implements IBundleValidatorHelper {
67
private bundlersMap: IStringDictionary = {
7-
webpack: "nativescript-dev-webpack"
8+
webpack: WEBPACK_PLUGIN_NAME
89
};
910

1011
constructor(protected $errors: IErrors,

0 commit comments

Comments
 (0)