nativescript
Version:
Command-line interface for building NativeScript projects
1,126 lines • 49.4 kB
JavaScript
"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 = ` /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 /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