UNPKG

react-native-integrate

Version:

Automate integration of additional code into React Native projects

399 lines (398 loc) 22 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.upgrade = upgrade; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const picocolors_1 = __importDefault(require("picocolors")); const preload_1 = __importDefault(require("semver/preload")); const options_1 = require("./options"); const progress_1 = require("./progress"); const prompter_1 = require("./prompter"); const analyzePackages_1 = require("./utils/analyzePackages"); const checkCondition_1 = require("./utils/checkCondition"); const checkForUpdate_1 = require("./utils/checkForUpdate"); const getErrMessage_1 = require("./utils/getErrMessage"); const getPackageConfig_1 = require("./utils/getPackageConfig"); const getProjectPath_1 = require("./utils/getProjectPath"); const parseConfig_1 = require("./utils/parseConfig"); const runCommand_1 = require("./utils/runCommand"); const runTask_1 = require("./utils/runTask"); const setState_1 = require("./utils/setState"); const taskManager_1 = require("./utils/taskManager"); const topologicalSort_1 = require("./utils/topologicalSort"); const updateIntegrationStatus_1 = require("./utils/updateIntegrationStatus"); const createNewProject_1 = require("./utils/upgrade/createNewProject"); const importFromOldProject_1 = require("./utils/upgrade/importFromOldProject"); const importPackageJson_1 = require("./utils/upgrade/other/importPackageJson"); const runUpgradeTasks_1 = require("./utils/upgrade/runUpgradeTasks"); const restoreBackupFiles_1 = require("./utils/upgrade/restoreBackupFiles"); const validateOldProjectPath_1 = require("./utils/upgrade/validateOldProjectPath"); const variables_1 = require("./variables"); async function upgrade() { progress_1.progress.setOptions({ stage: 'checking for updates', }); await (0, checkForUpdate_1.checkForUpdate)(); let stage = 1; progress_1.progress.setOptions({ step: stage, stage: 'creating new project', }); const isManual = options_1.options.get().manual; if (!isManual) { const { output: gitStatus } = await (0, runCommand_1.runCommand)('git status -s -uno', { silent: true, }); if (gitStatus) { throw new Error('your git status is dirty, please stash or commit changes before upgrading'); } (0, prompter_1.logInfo)(picocolors_1.default.bold(picocolors_1.default.inverse(picocolors_1.default.magenta(` stage ${stage++} `))) + picocolors_1.default.bold(picocolors_1.default.magenta(' Create new project '))); const didCreate = await (0, createNewProject_1.createNewProject)(); if (didCreate) { (0, prompter_1.logInfo)(picocolors_1.default.inverse(picocolors_1.default.bold(picocolors_1.default.green(' created '))) + picocolors_1.default.green(' new project created successfully')); } } progress_1.progress.setOptions({ step: stage, stage: 'importing old project data', }); (0, prompter_1.logInfo)(picocolors_1.default.bold(picocolors_1.default.inverse(picocolors_1.default.magenta(` stage ${stage++} `))) + picocolors_1.default.bold(picocolors_1.default.magenta(' Import old project data '))); // get old project path let oldProjectPath = variables_1.variables.get('__OLD_PROJECT_DIR__') || (await (0, prompter_1.text)('Enter old project path to import some basic data (display name, icons, etc.)', { placeholder: 'leave empty to skip', validate: validateOldProjectPath_1.validateOldProjectPath, })); if (oldProjectPath) { oldProjectPath = path_1.default.resolve(oldProjectPath); const didImport = await (0, importFromOldProject_1.importFromOldProject)(oldProjectPath); if (didImport) { (0, prompter_1.logInfo)(picocolors_1.default.inverse(picocolors_1.default.bold(picocolors_1.default.green(' imported '))) + picocolors_1.default.green(' imported project data successfully')); } } else { (0, prompter_1.logMessageGray)('skipping import from old project'); } progress_1.progress.setOptions({ step: stage, stage: 'executing upgrade.yml pre install steps', }); (0, prompter_1.logInfo)(picocolors_1.default.bold(picocolors_1.default.inverse(picocolors_1.default.magenta(` stage ${stage++} `))) + picocolors_1.default.bold(picocolors_1.default.magenta(' Execute upgrade.yml pre install steps '))); variables_1.variables.setPredefined('__UPGRADE__', true); const upgradePreInstallResult = await (0, runUpgradeTasks_1.runUpgradeTasks)(oldProjectPath, 'pre_install').catch((e) => { (0, prompter_1.logWarning)(e.message); }); if (upgradePreInstallResult && upgradePreInstallResult.didRun) { if (upgradePreInstallResult.failedTaskCount) { (0, prompter_1.logWarning)(picocolors_1.default.inverse(picocolors_1.default.bold(picocolors_1.default.yellow(' executed with errors '))) + picocolors_1.default.bold(picocolors_1.default.blue(' upgrade.yml pre_install ')) + picocolors_1.default.yellow(`failed to complete ${upgradePreInstallResult.failedTaskCount} task(s) - please complete upgrade manually`), true); } else { (0, prompter_1.logInfo)(picocolors_1.default.inverse(picocolors_1.default.bold(picocolors_1.default.green(' executed '))) + picocolors_1.default.black(picocolors_1.default.bold(picocolors_1.default.blue(' upgrade.yml pre_install '))) + picocolors_1.default.green(`completed ${upgradePreInstallResult.completedTaskCount} task(s) successfully`)); } } progress_1.progress.setOptions({ step: stage, stage: 'installing modules', }); await (0, importPackageJson_1.installModules)(oldProjectPath); progress_1.progress.setOptions({ step: stage, stage: 're-integrating packages', }); (0, prompter_1.logInfo)(picocolors_1.default.bold(picocolors_1.default.inverse(picocolors_1.default.magenta(` stage ${stage++} `))) + picocolors_1.default.bold(picocolors_1.default.magenta(' Re-integrate packages '))); let skipStage3 = false; (0, prompter_1.startSpinner)('analyzing packages'); const analyzedPackages = (0, analyzePackages_1.analyzePackages)(); const { installedPackages, integratedPackages } = analyzedPackages; if (analyzedPackages.justCreatedLockFile) { (0, prompter_1.stopSpinner)(picocolors_1.default.gray(picocolors_1.default.italic('integrate-lock.json not found. Nothing to re-integrate.'))); skipStage3 = true; } if (!skipStage3) { (0, prompter_1.stopSpinner)(`analyzed ${installedPackages.length} packages`); const packageLockUpdates = []; let packagesToIntegrate = []; progress_1.progress.setOptions({ stage: 'checking package configuration', }); (0, prompter_1.startSpinner)('checking package configuration'); for (let i = 0; i < integratedPackages.length; i++) { const [packageName] = integratedPackages[i]; const installedPackage = installedPackages.find(([installedPackageName]) => installedPackageName === packageName); if (!installedPackage) { (0, prompter_1.logWarning)(`Skipping integration of ${picocolors_1.default.bold(picocolors_1.default.blue(packageName))} because it is not installed.`); continue; } const version = installedPackage[1]; const configPath = await (0, getPackageConfig_1.getPackageConfig)(packageName, { index: i, count: integratedPackages.length, }); if (!configPath) { (0, prompter_1.logWarning)(`Skipping integration of ${picocolors_1.default.bold(picocolors_1.default.blue(packageName))} because currently it has no configuration.`); continue; } let config; try { config = (0, parseConfig_1.parseConfig)(configPath); } catch (e) { (0, prompter_1.logError)(picocolors_1.default.bold(picocolors_1.default.bgRed(' error ')) + picocolors_1.default.bold(picocolors_1.default.blue(` ${packageName} `)) + picocolors_1.default.red('could not parse package configuration\n') + picocolors_1.default.gray((0, getErrMessage_1.getErrMessage)(e, 'validation')), true); continue; } const rnVersionEntry = installedPackages.find(entry => entry[0] === 'react-native'); if (!rnVersionEntry) { (0, prompter_1.stopSpinner)('checked package configuration'); (0, prompter_1.logWarning)('React Native not installed!?'); return; } const rnVersion = rnVersionEntry[1]; const semRnVersion = preload_1.default.coerce(rnVersion); if (!semRnVersion) { (0, prompter_1.stopSpinner)('checked package configuration'); (0, prompter_1.logWarning)(`React Native version (${rnVersion}) is invalid!?`); return; } variables_1.variables.setPredefined('RN_VERSION', { major: semRnVersion.major, minor: semRnVersion.minor, patch: semRnVersion.patch, }); if (config.minRNVersion) { if (preload_1.default.lt(semRnVersion, preload_1.default.coerce(config.minRNVersion) || '0.0.0')) { (0, prompter_1.stopSpinner)('checked package configuration'); (0, prompter_1.logWarning)(`${picocolors_1.default.bold(picocolors_1.default.blue(packageName))} requires React Native version ${picocolors_1.default.bold(picocolors_1.default.blue(config.minRNVersion))}`); return; } } if (config.minVersion) { if (preload_1.default.lt(preload_1.default.coerce(version) || '0.0.0', preload_1.default.coerce(config.minVersion) || '0.0.0')) { (0, prompter_1.stopSpinner)('checked package configuration'); (0, prompter_1.logWarning)(`${picocolors_1.default.bold(picocolors_1.default.blue(packageName))} requires version ${picocolors_1.default.bold(picocolors_1.default.blue(config.minVersion))} - please update it first and try again`); return; } } if (config.dependencies?.length) { for (const dependentPackageName of config.dependencies) { // check if dependency is not integrated const isNotIntegrated = integratedPackages.every(([packageName]) => packageName != dependentPackageName); if (isNotIntegrated) { (0, prompter_1.stopSpinner)('checked package configuration'); (0, prompter_1.logWarning)(`Skipping integration of ${picocolors_1.default.bold(picocolors_1.default.blue(packageName))} because the dependant package ${picocolors_1.default.bold(picocolors_1.default.blue(dependentPackageName))} is not integrated.`); return; } } } packagesToIntegrate.push({ packageName, version, configPath, config, }); } let msg = 'checked package configuration'; if (packagesToIntegrate.length > 0) { msg += picocolors_1.default.green(` | ${packagesToIntegrate.length} package will be re-integrated`); } else { msg += ' | no packages to integrate'; } (0, prompter_1.stopSpinner)(msg); if (packagesToIntegrate.length) { packagesToIntegrate = (0, topologicalSort_1.topologicalSort)(packagesToIntegrate); for (let i = 0; i < packagesToIntegrate.length; i++) { const { packageName, version, configPath, config } = packagesToIntegrate[i]; progress_1.progress.setOptions({ step: i + 1, total: packagesToIntegrate.length, stage: `integrating ${picocolors_1.default.blue(packageName)}`, }); (0, prompter_1.logSuccess)(picocolors_1.default.bold(picocolors_1.default.bgBlue(' integration ')) + picocolors_1.default.bold(picocolors_1.default.blue(` ${packageName} `))); variables_1.variables.clear(); // reset variables if (config.env) { Object.entries(config.env).forEach(([name, value]) => variables_1.variables.set(name, (0, variables_1.transformTextInObject)(value))); } let failedTaskCount = 0, completedTaskCount = 0; for (const task of config.steps) { if (task.when && !(0, checkCondition_1.checkCondition)(task.when)) { (0, setState_1.setState)(task.name, { state: 'skipped', }); continue; } (0, setState_1.setState)(task.name, { state: 'progress', }); const isNonSystemTask = !taskManager_1.taskManager.isSystemTask(task.task); if (isNonSystemTask) { if (task.label) task.label = (0, variables_1.getText)(task.label); else task.label = taskManager_1.taskManager.task[task.task].summary; (0, prompter_1.logInfo)(picocolors_1.default.bold(picocolors_1.default.inverse(picocolors_1.default.cyan(' task '))) + picocolors_1.default.bold(picocolors_1.default.cyan(` ${task.label} `))); } try { await (0, runTask_1.runTask)({ configPath, packageName, task, }); completedTaskCount++; (0, setState_1.setState)(task.name, { state: 'done', }); } catch (e) { failedTaskCount++; const errMessage = (0, getErrMessage_1.getErrMessage)(e); (0, prompter_1.logError)(errMessage); (0, setState_1.setState)(task.name, { state: 'error', reason: errMessage, }); } } if (failedTaskCount) { (0, prompter_1.logWarning)(picocolors_1.default.inverse(picocolors_1.default.bold(picocolors_1.default.yellow(' done with errors '))) + picocolors_1.default.bold(picocolors_1.default.blue(` ${packageName} `)) + picocolors_1.default.yellow(`failed to complete ${failedTaskCount} task(s) - please complete this integration manually`), true); } else { (0, prompter_1.logSuccess)(picocolors_1.default.inverse(picocolors_1.default.bold(picocolors_1.default.green(' done '))) + picocolors_1.default.green(` completed ${completedTaskCount} task(s) successfully`)); } packageLockUpdates.push({ packageName, lockProjectData: { version, integrated: true, }, }); } } (0, updateIntegrationStatus_1.updateIntegrationStatus)(packageLockUpdates); } progress_1.progress.setOptions({ step: stage, stage: 'importing files from .upgrade/imports', }); (0, prompter_1.logInfo)(picocolors_1.default.bold(picocolors_1.default.inverse(picocolors_1.default.magenta(` stage ${stage++} `))) + picocolors_1.default.bold(picocolors_1.default.magenta(' Import files from .upgrade/imports '))); const didRestore = await (0, restoreBackupFiles_1.restoreBackupFiles)().catch((e) => { (0, prompter_1.logWarning)(e.message); }); if (didRestore) { (0, prompter_1.logInfo)(picocolors_1.default.inverse(picocolors_1.default.bold(picocolors_1.default.green(' imported '))) + picocolors_1.default.green(' files were imported successfully')); } progress_1.progress.setOptions({ step: stage, stage: 'executing upgrade.yml steps', }); (0, prompter_1.logInfo)(picocolors_1.default.bold(picocolors_1.default.inverse(picocolors_1.default.magenta(` stage ${stage++} `))) + picocolors_1.default.bold(picocolors_1.default.magenta(' Execute upgrade.yml steps '))); const upgradeResult = await (0, runUpgradeTasks_1.runUpgradeTasks)(oldProjectPath).catch((e) => { (0, prompter_1.logWarning)(e.message); }); if (upgradeResult && upgradeResult.didRun) { if (upgradeResult.failedTaskCount) { (0, prompter_1.logWarning)(picocolors_1.default.inverse(picocolors_1.default.bold(picocolors_1.default.yellow(' executed with errors '))) + picocolors_1.default.bold(picocolors_1.default.blue(' upgrade.yml ')) + picocolors_1.default.yellow(`failed to complete ${upgradeResult.failedTaskCount} task(s) - please complete upgrade manually`), true); } else { (0, prompter_1.logInfo)(picocolors_1.default.inverse(picocolors_1.default.bold(picocolors_1.default.green(' executed '))) + picocolors_1.default.black(picocolors_1.default.bold(picocolors_1.default.blue(' upgrade.yml '))) + picocolors_1.default.green(`completed ${upgradeResult.completedTaskCount} task(s) successfully`)); } } if (!isManual) { progress_1.progress.setOptions({ step: stage, stage: 'committing changes to branch', }); (0, prompter_1.logInfo)(picocolors_1.default.bold(picocolors_1.default.inverse(picocolors_1.default.magenta(` stage ${stage++} `))) + picocolors_1.default.bold(picocolors_1.default.magenta(' Commit changes to branch '))); const rnVersionVar = variables_1.variables.get('RN_VERSION'); const rnVersion = `${rnVersionVar.major}.${rnVersionVar.minor}.${rnVersionVar.patch}`; const branchName = `upgrade/react-native-${rnVersion}`; // checkout into new branch const { exitCode: didCheckoutFail } = await (0, runCommand_1.runCommand)(`git checkout -B ${branchName}`, { silent: false, progressText: 'switching branch', completeText: `switched branch to ${picocolors_1.default.yellow(branchName)}`, }); if (didCheckoutFail) { (0, prompter_1.logWarning)('please commit changes manually in ' + (0, getProjectPath_1.getProjectPath)()); return; } // add changes const { exitCode: didAddFail } = await (0, runCommand_1.runCommand)('git add .', { silent: false, progressText: 'staging changes', completeText: 'staged all changes', }); if (didAddFail) { (0, prompter_1.logWarning)('please commit changes manually in ' + (0, getProjectPath_1.getProjectPath)()); return; } // commit changes const { exitCode: didCommitFail } = await (0, runCommand_1.runCommand)('git', ['commit', '-m', `build(deps): bump react-native to ${rnVersion}`], { silent: false, progressText: 'committing changes', completeText: 'committed changes', }); if (didCommitFail) { (0, prompter_1.logWarning)('please commit changes manually in ' + (0, getProjectPath_1.getProjectPath)()); return; } // push changes const { exitCode: didPushFail } = await (0, runCommand_1.runCommand)(`git push -u origin ${branchName}`, { silent: false, progressText: 'pushing changes to origin', completeText: 'pushed changes to origin', }); if (didPushFail) { (0, prompter_1.logWarning)('please push changes manually in ' + (0, getProjectPath_1.getProjectPath)()); return; } (0, prompter_1.logSuccess)(picocolors_1.default.inverse(picocolors_1.default.bold(picocolors_1.default.green(' committed '))) + picocolors_1.default.green(` saved changes to branch ${picocolors_1.default.bold(branchName)}`)); // fetch changes const { exitCode: didFetchFail } = await (0, runCommand_1.runCommand)('git fetch', { silent: false, progressText: 'fetching changes to current project', completeText: 'fetched changes to current project', cwd: variables_1.variables.get('__OLD_PROJECT_DIR__'), }); if (didFetchFail) { (0, prompter_1.logWarning)('please fetch changes manually in ' + variables_1.variables.get('__OLD_PROJECT_DIR__')); return; } (0, prompter_1.startSpinner)('cleaning up'); await new Promise(r => fs_1.default.rm((0, getProjectPath_1.getProjectPath)(), { recursive: true, force: true, }, r)); (0, prompter_1.stopSpinner)('cleaned up'); } }