1. #!/usr/bin/env node
    
  2. 
    
  3. 'use strict';
    
  4. 
    
  5. const clear = require('clear');
    
  6. const {readFileSync, writeFileSync} = require('fs');
    
  7. const {readJson, writeJson} = require('fs-extra');
    
  8. const {join, relative} = require('path');
    
  9. const {confirm, execRead, printDiff} = require('../utils');
    
  10. const theme = require('../theme');
    
  11. 
    
  12. const run = async ({cwd, packages, version}, versionsMap) => {
    
  13.   const nodeModulesPath = join(cwd, 'build/node_modules');
    
  14. 
    
  15.   // Cache all package JSONs for easy lookup below.
    
  16.   const sourcePackageJSONs = new Map();
    
  17.   for (let i = 0; i < packages.length; i++) {
    
  18.     const packageName = packages[i];
    
  19.     const sourcePackageJSON = await readJson(
    
  20.       join(cwd, 'packages', packageName, 'package.json')
    
  21.     );
    
  22.     sourcePackageJSONs.set(packageName, sourcePackageJSON);
    
  23.   }
    
  24. 
    
  25.   const updateDependencies = async (targetPackageJSON, key) => {
    
  26.     const targetDependencies = targetPackageJSON[key];
    
  27.     if (targetDependencies) {
    
  28.       const sourceDependencies = sourcePackageJSONs.get(targetPackageJSON.name)[
    
  29.         key
    
  30.       ];
    
  31. 
    
  32.       for (let i = 0; i < packages.length; i++) {
    
  33.         const dependencyName = packages[i];
    
  34.         const targetDependency = targetDependencies[dependencyName];
    
  35. 
    
  36.         if (targetDependency) {
    
  37.           // For example, say we're updating react-dom's dependency on scheduler.
    
  38.           // We compare source packages to determine what the new scheduler dependency constraint should be.
    
  39.           // To do this, we look at both the local version of the scheduler (e.g. 0.11.0),
    
  40.           // and the dependency constraint in the local version of react-dom (e.g. scheduler@^0.11.0).
    
  41.           const sourceDependencyVersion =
    
  42.             sourcePackageJSONs.get(dependencyName).version;
    
  43.           const sourceDependencyConstraint = sourceDependencies[dependencyName];
    
  44. 
    
  45.           // If the source dependency's version and the constraint match,
    
  46.           // we will need to update the constraint to point at the dependency's new release version,
    
  47.           // (e.g. scheduler@^0.11.0 becomes scheduler@^0.12.0 when we release scheduler 0.12.0).
    
  48.           // Otherwise we leave the constraint alone (e.g. react@^16.0.0 doesn't change between releases).
    
  49.           // Note that in both cases, we must update the target package JSON,
    
  50.           // since "next" releases are all locked to the version (e.g. 0.0.0-0e526bcec-20210202).
    
  51.           if (
    
  52.             sourceDependencyVersion ===
    
  53.             sourceDependencyConstraint.replace(/^[\^\~]/, '')
    
  54.           ) {
    
  55.             targetDependencies[dependencyName] =
    
  56.               sourceDependencyConstraint.replace(
    
  57.                 sourceDependencyVersion,
    
  58.                 versionsMap.get(dependencyName)
    
  59.               );
    
  60.           } else {
    
  61.             targetDependencies[dependencyName] = sourceDependencyConstraint;
    
  62.           }
    
  63.         }
    
  64.       }
    
  65.     }
    
  66.   };
    
  67. 
    
  68.   // Update all package JSON versions and their dependencies/peerDependencies.
    
  69.   // This must be done in a way that respects semver constraints (e.g. 16.7.0, ^16.7.0, ^16.0.0).
    
  70.   // To do this, we use the dependencies defined in the source package JSONs,
    
  71.   // because the "next" dependencies have already been flattened to an exact match (e.g. 0.0.0-0e526bcec-20210202).
    
  72.   for (let i = 0; i < packages.length; i++) {
    
  73.     const packageName = packages[i];
    
  74.     const packageJSONPath = join(nodeModulesPath, packageName, 'package.json');
    
  75.     const packageJSON = await readJson(packageJSONPath);
    
  76.     packageJSON.version = versionsMap.get(packageName);
    
  77. 
    
  78.     await updateDependencies(packageJSON, 'dependencies');
    
  79.     await updateDependencies(packageJSON, 'peerDependencies');
    
  80. 
    
  81.     await writeJson(packageJSONPath, packageJSON, {spaces: 2});
    
  82.   }
    
  83. 
    
  84.   clear();
    
  85. 
    
  86.   // Print the map of versions and their dependencies for confirmation.
    
  87.   const printDependencies = (maybeDependency, label) => {
    
  88.     if (maybeDependency) {
    
  89.       for (let dependencyName in maybeDependency) {
    
  90.         if (packages.includes(dependencyName)) {
    
  91.           console.log(
    
  92.             theme`• {package ${dependencyName}} {version ${maybeDependency[dependencyName]}} {dimmed ${label}}`
    
  93.           );
    
  94.         }
    
  95.       }
    
  96.     }
    
  97.   };
    
  98.   for (let i = 0; i < packages.length; i++) {
    
  99.     const packageName = packages[i];
    
  100.     const packageJSONPath = join(nodeModulesPath, packageName, 'package.json');
    
  101.     const packageJSON = await readJson(packageJSONPath);
    
  102.     console.log(
    
  103.       theme`\n{package ${packageName}} {version ${versionsMap.get(
    
  104.         packageName
    
  105.       )}}`
    
  106.     );
    
  107.     printDependencies(packageJSON.dependencies, 'dependency');
    
  108.     printDependencies(packageJSON.peerDependencies, 'peer');
    
  109.   }
    
  110.   await confirm('Do the versions above look correct?');
    
  111. 
    
  112.   clear();
    
  113. 
    
  114.   // A separate "React version" is used for the embedded renderer version to support DevTools,
    
  115.   // since it needs to distinguish between different version ranges of React.
    
  116.   // We need to replace it as well as the "next" version number.
    
  117.   const buildInfoPath = join(nodeModulesPath, 'react', 'build-info.json');
    
  118.   const {reactVersion} = await readJson(buildInfoPath);
    
  119. 
    
  120.   if (!reactVersion) {
    
  121.     console.error(
    
  122.       theme`{error Unsupported or invalid build metadata in} {path build/node_modules/react/build-info.json}` +
    
  123.         theme`{error . This could indicate that you have specified an outdated "next" version.}`
    
  124.     );
    
  125.     process.exit(1);
    
  126.   }
    
  127. 
    
  128.   // We print the diff to the console for review,
    
  129.   // but it can be large so let's also write it to disk.
    
  130.   const diffPath = join(cwd, 'build', 'temp.diff');
    
  131.   let diff = '';
    
  132.   let numFilesModified = 0;
    
  133. 
    
  134.   // Find-and-replace hardcoded version (in built JS) for renderers.
    
  135.   for (let i = 0; i < packages.length; i++) {
    
  136.     const packageName = packages[i];
    
  137.     const packagePath = join(nodeModulesPath, packageName);
    
  138. 
    
  139.     let files = await execRead(
    
  140.       `find ${packagePath} -name '*.js' -exec echo {} \\;`,
    
  141.       {cwd}
    
  142.     );
    
  143.     files = files.split('\n');
    
  144.     files.forEach(path => {
    
  145.       const newStableVersion = versionsMap.get(packageName);
    
  146.       const beforeContents = readFileSync(path, 'utf8', {cwd});
    
  147.       let afterContents = beforeContents;
    
  148.       // Replace all "next" version numbers (e.g. header @license).
    
  149.       while (afterContents.indexOf(version) >= 0) {
    
  150.         afterContents = afterContents.replace(version, newStableVersion);
    
  151.       }
    
  152.       // Replace inline renderer version numbers (e.g. shared/ReactVersion).
    
  153.       while (afterContents.indexOf(reactVersion) >= 0) {
    
  154.         afterContents = afterContents.replace(reactVersion, newStableVersion);
    
  155.       }
    
  156.       if (beforeContents !== afterContents) {
    
  157.         numFilesModified++;
    
  158.         // Using a relative path for diff helps with the snapshot test
    
  159.         diff += printDiff(relative(cwd, path), beforeContents, afterContents);
    
  160.         writeFileSync(path, afterContents, {cwd});
    
  161.       }
    
  162.     });
    
  163.   }
    
  164.   writeFileSync(diffPath, diff, {cwd});
    
  165.   console.log(theme.header(`\n${numFilesModified} files have been updated.`));
    
  166.   console.log(
    
  167.     theme`A full diff is available at {path ${relative(cwd, diffPath)}}.`
    
  168.   );
    
  169.   await confirm('Do the changes above look correct?');
    
  170. 
    
  171.   clear();
    
  172. };
    
  173. 
    
  174. // Run this directly because logPromise would interfere with printing package dependencies.
    
  175. module.exports = run;