1. 'use strict';
    
  2. 
    
  3. const {
    
  4.   existsSync,
    
  5.   readdirSync,
    
  6.   unlinkSync,
    
  7.   readFileSync,
    
  8.   writeFileSync,
    
  9. } = require('fs');
    
  10. const path = require('path');
    
  11. const Bundles = require('./bundles');
    
  12. const {
    
  13.   asyncCopyTo,
    
  14.   asyncExecuteCommand,
    
  15.   asyncExtractTar,
    
  16.   asyncRimRaf,
    
  17. } = require('./utils');
    
  18. const {getSigningToken, signFile} = require('signedsource');
    
  19. 
    
  20. const {
    
  21.   NODE_ES2015,
    
  22.   ESM_DEV,
    
  23.   ESM_PROD,
    
  24.   UMD_DEV,
    
  25.   UMD_PROD,
    
  26.   UMD_PROFILING,
    
  27.   NODE_DEV,
    
  28.   NODE_PROD,
    
  29.   NODE_PROFILING,
    
  30.   BUN_DEV,
    
  31.   BUN_PROD,
    
  32.   FB_WWW_DEV,
    
  33.   FB_WWW_PROD,
    
  34.   FB_WWW_PROFILING,
    
  35.   RN_OSS_DEV,
    
  36.   RN_OSS_PROD,
    
  37.   RN_OSS_PROFILING,
    
  38.   RN_FB_DEV,
    
  39.   RN_FB_PROD,
    
  40.   RN_FB_PROFILING,
    
  41.   BROWSER_SCRIPT,
    
  42. } = Bundles.bundleTypes;
    
  43. 
    
  44. function getPackageName(name) {
    
  45.   if (name.indexOf('/') !== -1) {
    
  46.     return name.split('/')[0];
    
  47.   }
    
  48.   return name;
    
  49. }
    
  50. 
    
  51. function getBundleOutputPath(bundle, bundleType, filename, packageName) {
    
  52.   switch (bundleType) {
    
  53.     case NODE_ES2015:
    
  54.       return `build/node_modules/${packageName}/cjs/${filename}`;
    
  55.     case ESM_DEV:
    
  56.     case ESM_PROD:
    
  57.       return `build/node_modules/${packageName}/esm/${filename}`;
    
  58.     case BUN_DEV:
    
  59.     case BUN_PROD:
    
  60.       return `build/node_modules/${packageName}/cjs/${filename}`;
    
  61.     case NODE_DEV:
    
  62.     case NODE_PROD:
    
  63.     case NODE_PROFILING:
    
  64.       return `build/node_modules/${packageName}/cjs/${filename}`;
    
  65.     case UMD_DEV:
    
  66.     case UMD_PROD:
    
  67.     case UMD_PROFILING:
    
  68.       return `build/node_modules/${packageName}/umd/${filename}`;
    
  69.     case FB_WWW_DEV:
    
  70.     case FB_WWW_PROD:
    
  71.     case FB_WWW_PROFILING:
    
  72.       return `build/facebook-www/${filename}`;
    
  73.     case RN_OSS_DEV:
    
  74.     case RN_OSS_PROD:
    
  75.     case RN_OSS_PROFILING:
    
  76.       switch (packageName) {
    
  77.         case 'react-native-renderer':
    
  78.           return `build/react-native/implementations/${filename}`;
    
  79.         default:
    
  80.           throw new Error('Unknown RN package.');
    
  81.       }
    
  82.     case RN_FB_DEV:
    
  83.     case RN_FB_PROD:
    
  84.     case RN_FB_PROFILING:
    
  85.       switch (packageName) {
    
  86.         case 'scheduler':
    
  87.         case 'react':
    
  88.         case 'react-is':
    
  89.         case 'react-test-renderer':
    
  90.           return `build/facebook-react-native/${packageName}/cjs/${filename}`;
    
  91.         case 'react-native-renderer':
    
  92.           return `build/react-native/implementations/${filename.replace(
    
  93.             /\.js$/,
    
  94.             '.fb.js'
    
  95.           )}`;
    
  96.         default:
    
  97.           throw new Error('Unknown RN package.');
    
  98.       }
    
  99.     case BROWSER_SCRIPT: {
    
  100.       // Bundles that are served as browser scripts need to be able to be sent
    
  101.       // straight to the browser with any additional bundling. We shouldn't use
    
  102.       // a module to re-export. Depending on how they are served, they also may
    
  103.       // not go through package.json module resolution, so we shouldn't rely on
    
  104.       // that either. We should consider the output path as part of the public
    
  105.       // contract, and explicitly specify its location within the package's
    
  106.       // directory structure.
    
  107.       const outputPath = bundle.outputPath;
    
  108.       if (!outputPath) {
    
  109.         throw new Error(
    
  110.           'Bundles with type BROWSER_SCRIPT must specific an explicit ' +
    
  111.             'output path.'
    
  112.         );
    
  113.       }
    
  114.       return `build/node_modules/${packageName}/${outputPath}`;
    
  115.     }
    
  116.     default:
    
  117.       throw new Error('Unknown bundle type.');
    
  118.   }
    
  119. }
    
  120. 
    
  121. async function copyWWWShims() {
    
  122.   await asyncCopyTo(
    
  123.     `${__dirname}/shims/facebook-www`,
    
  124.     'build/facebook-www/shims'
    
  125.   );
    
  126. }
    
  127. 
    
  128. async function copyRNShims() {
    
  129.   await asyncCopyTo(
    
  130.     `${__dirname}/shims/react-native`,
    
  131.     'build/react-native/shims'
    
  132.   );
    
  133.   await asyncCopyTo(
    
  134.     require.resolve('react-native-renderer/src/ReactNativeTypes.js'),
    
  135.     'build/react-native/shims/ReactNativeTypes.js'
    
  136.   );
    
  137.   processGenerated('build/react-native/shims');
    
  138. }
    
  139. 
    
  140. function processGenerated(directory) {
    
  141.   const files = readdirSync(directory)
    
  142.     .filter(dir => dir.endsWith('.js'))
    
  143.     .map(file => path.join(directory, file));
    
  144. 
    
  145.   files.forEach(file => {
    
  146.     const originalContents = readFileSync(file, 'utf8');
    
  147.     const contents = originalContents
    
  148.       // Replace {@}format with {@}noformat
    
  149.       .replace(/(\r?\n\s*\*\s*)@format\b.*(\n)/, '$1@noformat$2')
    
  150.       // Add {@}nolint and {@}generated
    
  151.       .replace(/(\r?\n\s*\*)\//, `$1 @nolint$1 ${getSigningToken()}$1/`);
    
  152.     const signedContents = signFile(contents);
    
  153.     writeFileSync(file, signedContents, 'utf8');
    
  154.   });
    
  155. }
    
  156. 
    
  157. async function copyAllShims() {
    
  158.   await Promise.all([copyWWWShims(), copyRNShims()]);
    
  159. }
    
  160. 
    
  161. function getTarOptions(tgzName, packageName) {
    
  162.   // Files inside the `npm pack`ed archive start
    
  163.   // with "package/" in their paths. We'll undo
    
  164.   // this during extraction.
    
  165.   const CONTENTS_FOLDER = 'package';
    
  166.   return {
    
  167.     src: tgzName,
    
  168.     dest: `build/node_modules/${packageName}`,
    
  169.     tar: {
    
  170.       entries: [CONTENTS_FOLDER],
    
  171.       map(header) {
    
  172.         if (header.name.indexOf(CONTENTS_FOLDER + '/') === 0) {
    
  173.           header.name = header.name.slice(CONTENTS_FOLDER.length + 1);
    
  174.         }
    
  175.       },
    
  176.     },
    
  177.   };
    
  178. }
    
  179. 
    
  180. let entryPointsToHasBundle = new Map();
    
  181. // eslint-disable-next-line no-for-of-loops/no-for-of-loops
    
  182. for (const bundle of Bundles.bundles) {
    
  183.   let hasBundle = entryPointsToHasBundle.get(bundle.entry);
    
  184.   if (!hasBundle) {
    
  185.     const hasNonFBBundleTypes = bundle.bundleTypes.some(
    
  186.       type =>
    
  187.         type !== FB_WWW_DEV && type !== FB_WWW_PROD && type !== FB_WWW_PROFILING
    
  188.     );
    
  189.     entryPointsToHasBundle.set(bundle.entry, hasNonFBBundleTypes);
    
  190.   }
    
  191. }
    
  192. 
    
  193. function filterOutEntrypoints(name) {
    
  194.   // Remove entry point files that are not built in this configuration.
    
  195.   let jsonPath = `build/node_modules/${name}/package.json`;
    
  196.   let packageJSON = JSON.parse(readFileSync(jsonPath));
    
  197.   let files = packageJSON.files;
    
  198.   let exportsJSON = packageJSON.exports;
    
  199.   let browserJSON = packageJSON.browser;
    
  200.   if (!Array.isArray(files)) {
    
  201.     throw new Error('expected all package.json files to contain a files field');
    
  202.   }
    
  203.   let changed = false;
    
  204.   for (let i = 0; i < files.length; i++) {
    
  205.     let filename = files[i];
    
  206.     let entry =
    
  207.       filename === 'index.js'
    
  208.         ? name
    
  209.         : name + '/' + filename.replace(/\.js$/, '');
    
  210.     let hasBundle = entryPointsToHasBundle.get(entry);
    
  211.     if (hasBundle === undefined) {
    
  212.       // This entry doesn't exist in the bundles. Check if something similar exists.
    
  213.       hasBundle =
    
  214.         entryPointsToHasBundle.get(entry + '.node') ||
    
  215.         entryPointsToHasBundle.get(entry + '.browser');
    
  216.     }
    
  217.     if (hasBundle === undefined) {
    
  218.       // This doesn't exist in the bundles. It's an extra file.
    
  219.     } else if (hasBundle === true) {
    
  220.       // This is built in this release channel.
    
  221.     } else {
    
  222.       // This doesn't have any bundleTypes in this release channel.
    
  223.       // Let's remove it.
    
  224.       files.splice(i, 1);
    
  225.       i--;
    
  226.       unlinkSync(`build/node_modules/${name}/${filename}`);
    
  227.       changed = true;
    
  228.       // Remove it from the exports field too if it exists.
    
  229.       if (exportsJSON) {
    
  230.         if (filename === 'index.js') {
    
  231.           delete exportsJSON['.'];
    
  232.         } else {
    
  233.           delete exportsJSON['./' + filename.replace(/\.js$/, '')];
    
  234.         }
    
  235.       }
    
  236.       if (browserJSON) {
    
  237.         delete browserJSON['./' + filename];
    
  238.       }
    
  239.     }
    
  240. 
    
  241.     // We only export the source directory so Jest and Rollup can access them
    
  242.     // during local development and at build time. The files don't exist in the
    
  243.     // public builds, so we don't need the export entry, either.
    
  244.     const sourceWildcardExport = './src/*';
    
  245.     if (exportsJSON && exportsJSON[sourceWildcardExport]) {
    
  246.       delete exportsJSON[sourceWildcardExport];
    
  247.       changed = true;
    
  248.     }
    
  249.   }
    
  250.   if (changed) {
    
  251.     let newJSON = JSON.stringify(packageJSON, null, '  ');
    
  252.     writeFileSync(jsonPath, newJSON);
    
  253.   }
    
  254. }
    
  255. 
    
  256. async function prepareNpmPackage(name) {
    
  257.   await Promise.all([
    
  258.     asyncCopyTo('LICENSE', `build/node_modules/${name}/LICENSE`),
    
  259.     asyncCopyTo(
    
  260.       `packages/${name}/package.json`,
    
  261.       `build/node_modules/${name}/package.json`
    
  262.     ),
    
  263.     asyncCopyTo(
    
  264.       `packages/${name}/README.md`,
    
  265.       `build/node_modules/${name}/README.md`
    
  266.     ),
    
  267.     asyncCopyTo(`packages/${name}/npm`, `build/node_modules/${name}`),
    
  268.   ]);
    
  269.   filterOutEntrypoints(name);
    
  270.   const tgzName = (
    
  271.     await asyncExecuteCommand(`npm pack build/node_modules/${name}`)
    
  272.   ).trim();
    
  273.   await asyncRimRaf(`build/node_modules/${name}`);
    
  274.   await asyncExtractTar(getTarOptions(tgzName, name));
    
  275.   unlinkSync(tgzName);
    
  276. }
    
  277. 
    
  278. async function prepareNpmPackages() {
    
  279.   if (!existsSync('build/node_modules')) {
    
  280.     // We didn't build any npm packages.
    
  281.     return;
    
  282.   }
    
  283.   const builtPackageFolders = readdirSync('build/node_modules').filter(
    
  284.     dir => dir.charAt(0) !== '.'
    
  285.   );
    
  286.   await Promise.all(builtPackageFolders.map(prepareNpmPackage));
    
  287. }
    
  288. 
    
  289. module.exports = {
    
  290.   copyAllShims,
    
  291.   getPackageName,
    
  292.   getBundleOutputPath,
    
  293.   prepareNpmPackages,
    
  294. };