react-native-integrate
Version:
Automate integration of additional code into React Native projects
399 lines (398 loc) • 22 kB
JavaScript
;
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');
}
}