1*753557f6STomasz Sapeta/**
2*753557f6STomasz Sapeta * Copyright (c) Meta Platforms, Inc. and affiliates.
3*753557f6STomasz Sapeta *
4*753557f6STomasz Sapeta * This source code is licensed under the MIT license found in the
5*753557f6STomasz Sapeta * LICENSE file in the root directory of this source tree.
6*753557f6STomasz Sapeta *
7*753557f6STomasz Sapeta * @format
8*753557f6STomasz Sapeta */
9*753557f6STomasz Sapeta
10*753557f6STomasz Sapeta'use strict';
11*753557f6STomasz Sapeta
12*753557f6STomasz Sapeta/**
13*753557f6STomasz Sapeta * This script crawls through a React Native application's dependencies and invokes the codegen
14*753557f6STomasz Sapeta * for any libraries that require it.
15*753557f6STomasz Sapeta * To enable codegen support, the library should include a config in the codegenConfigKey key
16*753557f6STomasz Sapeta * in a codegenConfigFilename file.
17*753557f6STomasz Sapeta */
18*753557f6STomasz Sapeta
19*753557f6STomasz Sapetaconst {execSync} = require('child_process');
20*753557f6STomasz Sapetaconst fs = require('fs');
21*753557f6STomasz Sapetaconst os = require('os');
22*753557f6STomasz Sapetaconst path = require('path');
23*753557f6STomasz Sapeta
24*753557f6STomasz Sapetaconst RN_ROOT = path.join(__dirname, '../..');
25*753557f6STomasz Sapeta
26*753557f6STomasz Sapetaconst CODEGEN_REPO_PATH = `${RN_ROOT}/packages/react-native-codegen`;
27*753557f6STomasz Sapetaconst CODEGEN_NPM_PATH = `${RN_ROOT}/../react-native-codegen`;
28*753557f6STomasz Sapetaconst CORE_LIBRARIES = new Set(['rncore', 'FBReactNativeSpec']);
29*753557f6STomasz Sapetaconst REACT_NATIVE_DEPENDENCY_NAME = 'react-native';
30*753557f6STomasz Sapeta
31*753557f6STomasz Sapeta// HELPERS
32*753557f6STomasz Sapeta
33*753557f6STomasz Sapetafunction isReactNativeCoreLibrary(libraryName) {
34*753557f6STomasz Sapeta  return CORE_LIBRARIES.has(libraryName);
35*753557f6STomasz Sapeta}
36*753557f6STomasz Sapeta
37*753557f6STomasz Sapetafunction executeNodeScript(node, script) {
38*753557f6STomasz Sapeta  execSync(`${node} ${script}`);
39*753557f6STomasz Sapeta}
40*753557f6STomasz Sapeta
41*753557f6STomasz Sapetafunction isAppRootValid(appRootDir) {
42*753557f6STomasz Sapeta  if (appRootDir == null) {
43*753557f6STomasz Sapeta    console.error('Missing path to React Native application');
44*753557f6STomasz Sapeta    process.exitCode = 1;
45*753557f6STomasz Sapeta    return false;
46*753557f6STomasz Sapeta  }
47*753557f6STomasz Sapeta  return true;
48*753557f6STomasz Sapeta}
49*753557f6STomasz Sapeta
50*753557f6STomasz Sapetafunction readPackageJSON(appRootDir) {
51*753557f6STomasz Sapeta  return JSON.parse(fs.readFileSync(path.join(appRootDir, 'package.json')));
52*753557f6STomasz Sapeta}
53*753557f6STomasz Sapeta
54*753557f6STomasz Sapetafunction printDeprecationWarningIfNeeded(dependency) {
55*753557f6STomasz Sapeta  if (dependency === REACT_NATIVE_DEPENDENCY_NAME) {
56*753557f6STomasz Sapeta    return;
57*753557f6STomasz Sapeta  }
58*753557f6STomasz Sapeta  console.log(`[Codegen] CodegenConfig Deprecated Setup for ${dependency}.
59*753557f6STomasz Sapeta    The configuration file still contains the codegen in the libraries array.
60*753557f6STomasz Sapeta    If possible, replace it with a single object.
61*753557f6STomasz Sapeta  `);
62*753557f6STomasz Sapeta  console.debug(`BEFORE:
63*753557f6STomasz Sapeta    {
64*753557f6STomasz Sapeta      // ...
65*753557f6STomasz Sapeta      "codegenConfig": {
66*753557f6STomasz Sapeta        "libraries": [
67*753557f6STomasz Sapeta          {
68*753557f6STomasz Sapeta            "name": "libName1",
69*753557f6STomasz Sapeta            "type": "all|components|modules",
70*753557f6STomasz Sapeta            "jsSrcsRoot": "libName1/js"
71*753557f6STomasz Sapeta          },
72*753557f6STomasz Sapeta          {
73*753557f6STomasz Sapeta            "name": "libName2",
74*753557f6STomasz Sapeta            "type": "all|components|modules",
75*753557f6STomasz Sapeta            "jsSrcsRoot": "libName2/src"
76*753557f6STomasz Sapeta          }
77*753557f6STomasz Sapeta        ]
78*753557f6STomasz Sapeta      }
79*753557f6STomasz Sapeta    }
80*753557f6STomasz Sapeta
81*753557f6STomasz Sapeta    AFTER:
82*753557f6STomasz Sapeta    {
83*753557f6STomasz Sapeta      "codegenConfig": {
84*753557f6STomasz Sapeta        "name": "libraries",
85*753557f6STomasz Sapeta        "type": "all",
86*753557f6STomasz Sapeta        "jsSrcsRoot": "."
87*753557f6STomasz Sapeta      }
88*753557f6STomasz Sapeta    }
89*753557f6STomasz Sapeta  `);
90*753557f6STomasz Sapeta}
91*753557f6STomasz Sapeta
92*753557f6STomasz Sapeta// Reading Libraries
93*753557f6STomasz Sapetafunction extractLibrariesFromConfigurationArray(
94*753557f6STomasz Sapeta  configFile,
95*753557f6STomasz Sapeta  codegenConfigKey,
96*753557f6STomasz Sapeta  libraries,
97*753557f6STomasz Sapeta  dependency,
98*753557f6STomasz Sapeta  dependencyPath,
99*753557f6STomasz Sapeta) {
100*753557f6STomasz Sapeta  console.log(`[Codegen] Found ${dependency}`);
101*753557f6STomasz Sapeta  configFile[codegenConfigKey].libraries.forEach(config => {
102*753557f6STomasz Sapeta    const libraryConfig = {
103*753557f6STomasz Sapeta      library: dependency,
104*753557f6STomasz Sapeta      config,
105*753557f6STomasz Sapeta      libraryPath: dependencyPath,
106*753557f6STomasz Sapeta    };
107*753557f6STomasz Sapeta    libraries.push(libraryConfig);
108*753557f6STomasz Sapeta  });
109*753557f6STomasz Sapeta}
110*753557f6STomasz Sapeta
111*753557f6STomasz Sapetafunction extractLibrariesFromJSON(
112*753557f6STomasz Sapeta  configFile,
113*753557f6STomasz Sapeta  libraries,
114*753557f6STomasz Sapeta  codegenConfigKey,
115*753557f6STomasz Sapeta  dependency,
116*753557f6STomasz Sapeta  dependencyPath,
117*753557f6STomasz Sapeta) {
118*753557f6STomasz Sapeta  var isBlocking = false;
119*753557f6STomasz Sapeta  if (dependency == null) {
120*753557f6STomasz Sapeta    dependency = REACT_NATIVE_DEPENDENCY_NAME;
121*753557f6STomasz Sapeta    dependencyPath = RN_ROOT;
122*753557f6STomasz Sapeta    // If we are exploring the ReactNative libraries, we want to raise an error
123*753557f6STomasz Sapeta    // if the codegen is not properly configured.
124*753557f6STomasz Sapeta    isBlocking = true;
125*753557f6STomasz Sapeta  }
126*753557f6STomasz Sapeta
127*753557f6STomasz Sapeta  if (configFile[codegenConfigKey] == null) {
128*753557f6STomasz Sapeta    if (isBlocking) {
129*753557f6STomasz Sapeta      throw `[Codegen] Error: Could not find codegen config for ${dependency} .`;
130*753557f6STomasz Sapeta    }
131*753557f6STomasz Sapeta    return;
132*753557f6STomasz Sapeta  }
133*753557f6STomasz Sapeta
134*753557f6STomasz Sapeta  if (configFile[codegenConfigKey].libraries == null) {
135*753557f6STomasz Sapeta    console.log(`[Codegen] Found ${dependency}`);
136*753557f6STomasz Sapeta    var config = configFile[codegenConfigKey];
137*753557f6STomasz Sapeta    libraries.push({
138*753557f6STomasz Sapeta      library: dependency,
139*753557f6STomasz Sapeta      config,
140*753557f6STomasz Sapeta      libraryPath: dependencyPath,
141*753557f6STomasz Sapeta    });
142*753557f6STomasz Sapeta  } else {
143*753557f6STomasz Sapeta    printDeprecationWarningIfNeeded(dependency);
144*753557f6STomasz Sapeta    extractLibrariesFromConfigurationArray(
145*753557f6STomasz Sapeta      configFile,
146*753557f6STomasz Sapeta      codegenConfigKey,
147*753557f6STomasz Sapeta      libraries,
148*753557f6STomasz Sapeta      dependency,
149*753557f6STomasz Sapeta      dependencyPath,
150*753557f6STomasz Sapeta    );
151*753557f6STomasz Sapeta  }
152*753557f6STomasz Sapeta}
153*753557f6STomasz Sapeta
154*753557f6STomasz Sapetafunction handleReactNativeCodeLibraries(
155*753557f6STomasz Sapeta  libraries,
156*753557f6STomasz Sapeta  codegenConfigFilename,
157*753557f6STomasz Sapeta  codegenConfigKey,
158*753557f6STomasz Sapeta) {
159*753557f6STomasz Sapeta  // Handle react-native core libraries.
160*753557f6STomasz Sapeta  // This is required when react-native is outside of node_modules.
161*753557f6STomasz Sapeta  console.log('[Codegen] Processing react-native core libraries');
162*753557f6STomasz Sapeta  const reactNativePkgJson = path.join(RN_ROOT, codegenConfigFilename);
163*753557f6STomasz Sapeta  if (!fs.existsSync(reactNativePkgJson)) {
164*753557f6STomasz Sapeta    throw '[Codegen] Error: Could not find config file for react-native.';
165*753557f6STomasz Sapeta  }
166*753557f6STomasz Sapeta  const reactNativeConfigFile = JSON.parse(fs.readFileSync(reactNativePkgJson));
167*753557f6STomasz Sapeta  extractLibrariesFromJSON(reactNativeConfigFile, libraries, codegenConfigKey);
168*753557f6STomasz Sapeta}
169*753557f6STomasz Sapeta
170*753557f6STomasz Sapetafunction handleThirdPartyLibraries(
171*753557f6STomasz Sapeta  libraries,
172*753557f6STomasz Sapeta  baseCodegenConfigFileDir,
173*753557f6STomasz Sapeta  dependencies,
174*753557f6STomasz Sapeta  codegenConfigFilename,
175*753557f6STomasz Sapeta  codegenConfigKey,
176*753557f6STomasz Sapeta) {
177*753557f6STomasz Sapeta  // Determine which of these are codegen-enabled libraries
178*753557f6STomasz Sapeta  const configDir = baseCodegenConfigFileDir || path.join(RN_ROOT, '..');
179*753557f6STomasz Sapeta  console.log(
180*753557f6STomasz Sapeta    `\n\n[Codegen] >>>>> Searching for codegen-enabled libraries in ${configDir}`,
181*753557f6STomasz Sapeta  );
182*753557f6STomasz Sapeta
183*753557f6STomasz Sapeta  // Handle third-party libraries
184*753557f6STomasz Sapeta  Object.keys(dependencies).forEach(dependency => {
185*753557f6STomasz Sapeta    if (dependency === REACT_NATIVE_DEPENDENCY_NAME) {
186*753557f6STomasz Sapeta      // react-native should already be added.
187*753557f6STomasz Sapeta      return;
188*753557f6STomasz Sapeta    }
189*753557f6STomasz Sapeta    const codegenConfigFileDir = path.join(configDir, dependency);
190*753557f6STomasz Sapeta    const configFilePath = path.join(
191*753557f6STomasz Sapeta      codegenConfigFileDir,
192*753557f6STomasz Sapeta      codegenConfigFilename,
193*753557f6STomasz Sapeta    );
194*753557f6STomasz Sapeta    if (fs.existsSync(configFilePath)) {
195*753557f6STomasz Sapeta      const configFile = JSON.parse(fs.readFileSync(configFilePath));
196*753557f6STomasz Sapeta      extractLibrariesFromJSON(
197*753557f6STomasz Sapeta        configFile,
198*753557f6STomasz Sapeta        libraries,
199*753557f6STomasz Sapeta        codegenConfigKey,
200*753557f6STomasz Sapeta        dependency,
201*753557f6STomasz Sapeta        codegenConfigFileDir,
202*753557f6STomasz Sapeta      );
203*753557f6STomasz Sapeta    }
204*753557f6STomasz Sapeta  });
205*753557f6STomasz Sapeta}
206*753557f6STomasz Sapeta
207*753557f6STomasz Sapetafunction handleLibrariesFromReactNativeConfig(
208*753557f6STomasz Sapeta  libraries,
209*753557f6STomasz Sapeta  codegenConfigKey,
210*753557f6STomasz Sapeta  codegenConfigFilename,
211*753557f6STomasz Sapeta  appRootDir,
212*753557f6STomasz Sapeta) {
213*753557f6STomasz Sapeta  const rnConfigFileName = 'react-native.config.js';
214*753557f6STomasz Sapeta
215*753557f6STomasz Sapeta  console.log(
216*753557f6STomasz Sapeta    `\n\n[Codegen] >>>>> Searching for codegen-enabled libraries in ${rnConfigFileName}`,
217*753557f6STomasz Sapeta  );
218*753557f6STomasz Sapeta
219*753557f6STomasz Sapeta  const rnConfigFilePath = path.join(appRootDir, rnConfigFileName);
220*753557f6STomasz Sapeta
221*753557f6STomasz Sapeta  if (fs.existsSync(rnConfigFilePath)) {
222*753557f6STomasz Sapeta    const rnConfig = require(rnConfigFilePath);
223*753557f6STomasz Sapeta
224*753557f6STomasz Sapeta    if (rnConfig.dependencies != null) {
225*753557f6STomasz Sapeta      Object.keys(rnConfig.dependencies).forEach(name => {
226*753557f6STomasz Sapeta        const dependencyConfig = rnConfig.dependencies[name];
227*753557f6STomasz Sapeta
228*753557f6STomasz Sapeta        if (dependencyConfig.root) {
229*753557f6STomasz Sapeta          const codegenConfigFileDir = path.resolve(
230*753557f6STomasz Sapeta            appRootDir,
231*753557f6STomasz Sapeta            dependencyConfig.root,
232*753557f6STomasz Sapeta          );
233*753557f6STomasz Sapeta          const configFilePath = path.join(
234*753557f6STomasz Sapeta            codegenConfigFileDir,
235*753557f6STomasz Sapeta            codegenConfigFilename,
236*753557f6STomasz Sapeta          );
237*753557f6STomasz Sapeta          const pkgJsonPath = path.join(codegenConfigFileDir, 'package.json');
238*753557f6STomasz Sapeta
239*753557f6STomasz Sapeta          if (fs.existsSync(configFilePath)) {
240*753557f6STomasz Sapeta            const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath));
241*753557f6STomasz Sapeta            const configFile = JSON.parse(fs.readFileSync(configFilePath));
242*753557f6STomasz Sapeta            extractLibrariesFromJSON(
243*753557f6STomasz Sapeta              configFile,
244*753557f6STomasz Sapeta              libraries,
245*753557f6STomasz Sapeta              codegenConfigKey,
246*753557f6STomasz Sapeta              pkgJson.name,
247*753557f6STomasz Sapeta              codegenConfigFileDir,
248*753557f6STomasz Sapeta            );
249*753557f6STomasz Sapeta          }
250*753557f6STomasz Sapeta        }
251*753557f6STomasz Sapeta      });
252*753557f6STomasz Sapeta    }
253*753557f6STomasz Sapeta  }
254*753557f6STomasz Sapeta}
255*753557f6STomasz Sapeta
256*753557f6STomasz Sapetafunction handleInAppLibraries(
257*753557f6STomasz Sapeta  libraries,
258*753557f6STomasz Sapeta  pkgJson,
259*753557f6STomasz Sapeta  codegenConfigKey,
260*753557f6STomasz Sapeta  appRootDir,
261*753557f6STomasz Sapeta) {
262*753557f6STomasz Sapeta  console.log(
263*753557f6STomasz Sapeta    '\n\n[Codegen] >>>>> Searching for codegen-enabled libraries in the app',
264*753557f6STomasz Sapeta  );
265*753557f6STomasz Sapeta
266*753557f6STomasz Sapeta  extractLibrariesFromJSON(
267*753557f6STomasz Sapeta    pkgJson,
268*753557f6STomasz Sapeta    libraries,
269*753557f6STomasz Sapeta    codegenConfigKey,
270*753557f6STomasz Sapeta    pkgJson.name,
271*753557f6STomasz Sapeta    appRootDir,
272*753557f6STomasz Sapeta  );
273*753557f6STomasz Sapeta}
274*753557f6STomasz Sapeta
275*753557f6STomasz Sapeta// CodeGen
276*753557f6STomasz Sapeta
277*753557f6STomasz Sapetafunction getCodeGenCliPath() {
278*753557f6STomasz Sapeta  let codegenCliPath;
279*753557f6STomasz Sapeta  if (fs.existsSync(CODEGEN_REPO_PATH)) {
280*753557f6STomasz Sapeta    codegenCliPath = CODEGEN_REPO_PATH;
281*753557f6STomasz Sapeta
282*753557f6STomasz Sapeta    if (!fs.existsSync(path.join(CODEGEN_REPO_PATH, 'lib'))) {
283*753557f6STomasz Sapeta      console.log('\n\n[Codegen] >>>>> Building react-native-codegen package');
284*753557f6STomasz Sapeta      execSync('yarn install', {
285*753557f6STomasz Sapeta        cwd: codegenCliPath,
286*753557f6STomasz Sapeta        stdio: 'inherit',
287*753557f6STomasz Sapeta      });
288*753557f6STomasz Sapeta      execSync('yarn build', {
289*753557f6STomasz Sapeta        cwd: codegenCliPath,
290*753557f6STomasz Sapeta        stdio: 'inherit',
291*753557f6STomasz Sapeta      });
292*753557f6STomasz Sapeta    }
293*753557f6STomasz Sapeta  } else if (fs.existsSync(CODEGEN_NPM_PATH)) {
294*753557f6STomasz Sapeta    codegenCliPath = CODEGEN_NPM_PATH;
295*753557f6STomasz Sapeta  } else {
296*753557f6STomasz Sapeta    throw "error: Could not determine react-native-codegen location. Try running 'yarn install' or 'npm install' in your project root.";
297*753557f6STomasz Sapeta  }
298*753557f6STomasz Sapeta  return codegenCliPath;
299*753557f6STomasz Sapeta}
300*753557f6STomasz Sapeta
301*753557f6STomasz Sapetafunction computeIOSOutputDir(outputPath, appRootDir) {
302*753557f6STomasz Sapeta  return path.join(outputPath ? outputPath : appRootDir, 'build/generated/ios');
303*753557f6STomasz Sapeta}
304*753557f6STomasz Sapeta
305*753557f6STomasz Sapetafunction generateSchema(tmpDir, library, node, codegenCliPath) {
306*753557f6STomasz Sapeta  const pathToSchema = path.join(tmpDir, 'schema.json');
307*753557f6STomasz Sapeta  const pathToJavaScriptSources = path.join(
308*753557f6STomasz Sapeta    library.libraryPath,
309*753557f6STomasz Sapeta    library.config.jsSrcsDir,
310*753557f6STomasz Sapeta  );
311*753557f6STomasz Sapeta
312*753557f6STomasz Sapeta  console.log(`\n\n[Codegen] >>>>> Processing ${library.config.name}`);
313*753557f6STomasz Sapeta  // Generate one schema for the entire library...
314*753557f6STomasz Sapeta  executeNodeScript(
315*753557f6STomasz Sapeta    node,
316*753557f6STomasz Sapeta    `${path.join(
317*753557f6STomasz Sapeta      codegenCliPath,
318*753557f6STomasz Sapeta      'lib',
319*753557f6STomasz Sapeta      'cli',
320*753557f6STomasz Sapeta      'combine',
321*753557f6STomasz Sapeta      'combine-js-to-schema-cli.js',
322*753557f6STomasz Sapeta    )} ${pathToSchema} ${pathToJavaScriptSources}`,
323*753557f6STomasz Sapeta  );
324*753557f6STomasz Sapeta  console.log(`[Codegen] Generated schema: ${pathToSchema}`);
325*753557f6STomasz Sapeta  return pathToSchema;
326*753557f6STomasz Sapeta}
327*753557f6STomasz Sapeta
328*753557f6STomasz Sapetafunction generateCode(iosOutputDir, library, tmpDir, node, pathToSchema) {
329*753557f6STomasz Sapeta  // ...then generate native code artifacts.
330*753557f6STomasz Sapeta  const libraryTypeArg = library.config.type
331*753557f6STomasz Sapeta    ? `--libraryType ${library.config.type}`
332*753557f6STomasz Sapeta    : '';
333*753557f6STomasz Sapeta
334*753557f6STomasz Sapeta  const tmpOutputDir = path.join(tmpDir, 'out');
335*753557f6STomasz Sapeta  fs.mkdirSync(tmpOutputDir, {recursive: true});
336*753557f6STomasz Sapeta
337*753557f6STomasz Sapeta  executeNodeScript(
338*753557f6STomasz Sapeta    node,
339*753557f6STomasz Sapeta    `${path.join(RN_ROOT, 'scripts', 'generate-specs-cli.js')} \
340*753557f6STomasz Sapeta        --platform ios \
341*753557f6STomasz Sapeta        --schemaPath ${pathToSchema} \
342*753557f6STomasz Sapeta        --outputDir ${tmpOutputDir} \
343*753557f6STomasz Sapeta        --libraryName ${library.config.name} \
344*753557f6STomasz Sapeta        ${libraryTypeArg}`,
345*753557f6STomasz Sapeta  );
346*753557f6STomasz Sapeta
347*753557f6STomasz Sapeta  // Finally, copy artifacts to the final output directory.
348*753557f6STomasz Sapeta  fs.mkdirSync(iosOutputDir, {recursive: true});
349*753557f6STomasz Sapeta  execSync(`cp -R ${tmpOutputDir}/* ${iosOutputDir}`);
350*753557f6STomasz Sapeta  console.log(`[Codegen] Generated artifacts: ${iosOutputDir}`);
351*753557f6STomasz Sapeta}
352*753557f6STomasz Sapeta
353*753557f6STomasz Sapetafunction generateNativeCodegenFiles(
354*753557f6STomasz Sapeta  libraries,
355*753557f6STomasz Sapeta  fabricEnabled,
356*753557f6STomasz Sapeta  iosOutputDir,
357*753557f6STomasz Sapeta  node,
358*753557f6STomasz Sapeta  codegenCliPath,
359*753557f6STomasz Sapeta  schemaPaths,
360*753557f6STomasz Sapeta) {
361*753557f6STomasz Sapeta  let fabricEnabledTypes = ['components', 'all'];
362*753557f6STomasz Sapeta  libraries.forEach(library => {
363*753557f6STomasz Sapeta    if (
364*753557f6STomasz Sapeta      !fabricEnabled &&
365*753557f6STomasz Sapeta      fabricEnabledTypes.indexOf(library.config.type) >= 0
366*753557f6STomasz Sapeta    ) {
367*753557f6STomasz Sapeta      console.log(
368*753557f6STomasz Sapeta        `[Codegen] ${library.config.name} skipped because fabric is not enabled.`,
369*753557f6STomasz Sapeta      );
370*753557f6STomasz Sapeta      return;
371*753557f6STomasz Sapeta    }
372*753557f6STomasz Sapeta    const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), library.config.name));
373*753557f6STomasz Sapeta    const pathToSchema = generateSchema(tmpDir, library, node, codegenCliPath);
374*753557f6STomasz Sapeta    generateCode(iosOutputDir, library, tmpDir, node, pathToSchema);
375*753557f6STomasz Sapeta
376*753557f6STomasz Sapeta    // Filter the react native core library out.
377*753557f6STomasz Sapeta    // In the future, core library and third party library should
378*753557f6STomasz Sapeta    // use the same way to generate/register the fabric components.
379*753557f6STomasz Sapeta    if (!isReactNativeCoreLibrary(library.config.name)) {
380*753557f6STomasz Sapeta      schemaPaths[library.config.name] = pathToSchema;
381*753557f6STomasz Sapeta    }
382*753557f6STomasz Sapeta  });
383*753557f6STomasz Sapeta}
384*753557f6STomasz Sapeta
385*753557f6STomasz Sapetafunction createComponentProvider(
386*753557f6STomasz Sapeta  fabricEnabled,
387*753557f6STomasz Sapeta  schemaPaths,
388*753557f6STomasz Sapeta  node,
389*753557f6STomasz Sapeta  iosOutputDir,
390*753557f6STomasz Sapeta) {
391*753557f6STomasz Sapeta  if (fabricEnabled) {
392*753557f6STomasz Sapeta    console.log('\n\n>>>>> Creating component provider');
393*753557f6STomasz Sapeta    // Save the list of spec paths to a temp file.
394*753557f6STomasz Sapeta    const schemaListTmpPath = `${os.tmpdir()}/rn-tmp-schema-list.json`;
395*753557f6STomasz Sapeta    const fd = fs.openSync(schemaListTmpPath, 'w');
396*753557f6STomasz Sapeta    fs.writeSync(fd, JSON.stringify(schemaPaths));
397*753557f6STomasz Sapeta    fs.closeSync(fd);
398*753557f6STomasz Sapeta    console.log(`Generated schema list: ${schemaListTmpPath}`);
399*753557f6STomasz Sapeta
400*753557f6STomasz Sapeta    // Generate FabricComponentProvider.
401*753557f6STomasz Sapeta    // Only for iOS at this moment.
402*753557f6STomasz Sapeta    executeNodeScript(
403*753557f6STomasz Sapeta      node,
404*753557f6STomasz Sapeta      `${path.join(
405*753557f6STomasz Sapeta        RN_ROOT,
406*753557f6STomasz Sapeta        'scripts',
407*753557f6STomasz Sapeta        'generate-provider-cli.js',
408*753557f6STomasz Sapeta      )} --platform ios --schemaListPath "${schemaListTmpPath}" --outputDir ${iosOutputDir}`,
409*753557f6STomasz Sapeta    );
410*753557f6STomasz Sapeta    console.log(`Generated provider in: ${iosOutputDir}`);
411*753557f6STomasz Sapeta  }
412*753557f6STomasz Sapeta}
413*753557f6STomasz Sapeta
414*753557f6STomasz Sapetafunction findCodegenEnabledLibraries(
415*753557f6STomasz Sapeta  appRootDir,
416*753557f6STomasz Sapeta  baseCodegenConfigFileDir,
417*753557f6STomasz Sapeta  codegenConfigFilename,
418*753557f6STomasz Sapeta  codegenConfigKey,
419*753557f6STomasz Sapeta) {
420*753557f6STomasz Sapeta  const pkgJson = readPackageJSON(appRootDir);
421*753557f6STomasz Sapeta  const dependencies = {...pkgJson.dependencies, ...pkgJson.devDependencies};
422*753557f6STomasz Sapeta  const libraries = [];
423*753557f6STomasz Sapeta
424*753557f6STomasz Sapeta  handleReactNativeCodeLibraries(
425*753557f6STomasz Sapeta    libraries,
426*753557f6STomasz Sapeta    codegenConfigFilename,
427*753557f6STomasz Sapeta    codegenConfigKey,
428*753557f6STomasz Sapeta  );
429*753557f6STomasz Sapeta  handleThirdPartyLibraries(
430*753557f6STomasz Sapeta    libraries,
431*753557f6STomasz Sapeta    baseCodegenConfigFileDir,
432*753557f6STomasz Sapeta    dependencies,
433*753557f6STomasz Sapeta    codegenConfigFilename,
434*753557f6STomasz Sapeta    codegenConfigKey,
435*753557f6STomasz Sapeta  );
436*753557f6STomasz Sapeta  handleLibrariesFromReactNativeConfig(
437*753557f6STomasz Sapeta    libraries,
438*753557f6STomasz Sapeta    codegenConfigKey,
439*753557f6STomasz Sapeta    codegenConfigFilename,
440*753557f6STomasz Sapeta    appRootDir,
441*753557f6STomasz Sapeta  );
442*753557f6STomasz Sapeta  handleInAppLibraries(libraries, pkgJson, codegenConfigKey, appRootDir);
443*753557f6STomasz Sapeta
444*753557f6STomasz Sapeta  return libraries;
445*753557f6STomasz Sapeta}
446*753557f6STomasz Sapeta
447*753557f6STomasz Sapeta// It removes all the empty files and empty folders
448*753557f6STomasz Sapeta// it finds, starting from `filepath`, recursively.
449*753557f6STomasz Sapeta//
450*753557f6STomasz Sapeta// This function is needed since, after aligning the codegen between
451*753557f6STomasz Sapeta// iOS and Android, we have to create empty folders in advance and
452*753557f6STomasz Sapeta// we don't know wheter they will be populated up until the end of the process.
453*753557f6STomasz Sapeta//
454*753557f6STomasz Sapeta// @parameter filepath: the root path from which we want to remove the empty files and folders.
455*753557f6STomasz Sapetafunction cleanupEmptyFilesAndFolders(filepath) {
456*753557f6STomasz Sapeta  const stats = fs.statSync(filepath);
457*753557f6STomasz Sapeta
458*753557f6STomasz Sapeta  if (stats.isFile() && stats.size === 0) {
459*753557f6STomasz Sapeta    fs.rmSync(filepath);
460*753557f6STomasz Sapeta    return;
461*753557f6STomasz Sapeta  } else if (stats.isFile()) {
462*753557f6STomasz Sapeta    return;
463*753557f6STomasz Sapeta  }
464*753557f6STomasz Sapeta
465*753557f6STomasz Sapeta  const dirContent = fs.readdirSync(filepath);
466*753557f6STomasz Sapeta  dirContent.forEach(contentPath =>
467*753557f6STomasz Sapeta    cleanupEmptyFilesAndFolders(path.join(filepath, contentPath)),
468*753557f6STomasz Sapeta  );
469*753557f6STomasz Sapeta
470*753557f6STomasz Sapeta  // The original folder may be filled with empty folders
471*753557f6STomasz Sapeta  // if that the case, we would also like to remove the parent.
472*753557f6STomasz Sapeta  // Hence, we need to read the folder again.
473*753557f6STomasz Sapeta  const newContent = fs.readdirSync(filepath);
474*753557f6STomasz Sapeta  if (newContent.length === 0) {
475*753557f6STomasz Sapeta    fs.rmdirSync(filepath);
476*753557f6STomasz Sapeta    return;
477*753557f6STomasz Sapeta  }
478*753557f6STomasz Sapeta}
479*753557f6STomasz Sapeta
480*753557f6STomasz Sapeta// Execute
481*753557f6STomasz Sapeta
482*753557f6STomasz Sapeta/**
483*753557f6STomasz Sapeta * This function is the entry point for the codegen. It:
484*753557f6STomasz Sapeta * - reads the package json
485*753557f6STomasz Sapeta * - extracts the libraries
486*753557f6STomasz Sapeta * - setups the CLI to generate the code
487*753557f6STomasz Sapeta * - generate the code
488*753557f6STomasz Sapeta *
489*753557f6STomasz Sapeta * @parameter appRootDir: the directory with the app source code, where the `codegenConfigFilename` lives.
490*753557f6STomasz Sapeta * @parameter outputPath: the base output path for the CodeGen.
491*753557f6STomasz Sapeta * @parameter node: the path to the node executable, used to run the codegen scripts.
492*753557f6STomasz Sapeta * @parameter codegenConfigFilename: the file that contains the codeGen configuration. The default is `package.json`.
493*753557f6STomasz Sapeta * @parameter codegenConfigKey: the key in the codegenConfigFile that controls the codegen.
494*753557f6STomasz Sapeta * @parameter baseCodegenConfigFileDir: the directory of the codeGenConfigFile.
495*753557f6STomasz Sapeta * @parameter fabricEnabled: whether fabric is enabled or not.
496*753557f6STomasz Sapeta * @throws If it can't find a config file for react-native.
497*753557f6STomasz Sapeta * @throws If it can't find a CodeGen configuration in the file.
498*753557f6STomasz Sapeta * @throws If it can't find a cli for the CodeGen.
499*753557f6STomasz Sapeta */
500*753557f6STomasz Sapetafunction execute(
501*753557f6STomasz Sapeta  appRootDir,
502*753557f6STomasz Sapeta  outputPath,
503*753557f6STomasz Sapeta  node,
504*753557f6STomasz Sapeta  codegenConfigFilename,
505*753557f6STomasz Sapeta  codegenConfigKey,
506*753557f6STomasz Sapeta  baseCodegenConfigFileDir,
507*753557f6STomasz Sapeta  fabricEnabled,
508*753557f6STomasz Sapeta) {
509*753557f6STomasz Sapeta  if (!isAppRootValid(appRootDir)) {
510*753557f6STomasz Sapeta    return;
511*753557f6STomasz Sapeta  }
512*753557f6STomasz Sapeta
513*753557f6STomasz Sapeta  try {
514*753557f6STomasz Sapeta    const libraries = findCodegenEnabledLibraries(
515*753557f6STomasz Sapeta      appRootDir,
516*753557f6STomasz Sapeta      baseCodegenConfigFileDir,
517*753557f6STomasz Sapeta      codegenConfigFilename,
518*753557f6STomasz Sapeta      codegenConfigKey,
519*753557f6STomasz Sapeta    );
520*753557f6STomasz Sapeta
521*753557f6STomasz Sapeta    if (libraries.length === 0) {
522*753557f6STomasz Sapeta      console.log('[Codegen] No codegen-enabled libraries found.');
523*753557f6STomasz Sapeta      return;
524*753557f6STomasz Sapeta    }
525*753557f6STomasz Sapeta
526*753557f6STomasz Sapeta    const codegenCliPath = getCodeGenCliPath();
527*753557f6STomasz Sapeta
528*753557f6STomasz Sapeta    const schemaPaths = {};
529*753557f6STomasz Sapeta
530*753557f6STomasz Sapeta    const iosOutputDir = computeIOSOutputDir(outputPath, appRootDir);
531*753557f6STomasz Sapeta
532*753557f6STomasz Sapeta    generateNativeCodegenFiles(
533*753557f6STomasz Sapeta      libraries,
534*753557f6STomasz Sapeta      fabricEnabled,
535*753557f6STomasz Sapeta      iosOutputDir,
536*753557f6STomasz Sapeta      node,
537*753557f6STomasz Sapeta      codegenCliPath,
538*753557f6STomasz Sapeta      schemaPaths,
539*753557f6STomasz Sapeta    );
540*753557f6STomasz Sapeta
541*753557f6STomasz Sapeta    createComponentProvider(fabricEnabled, schemaPaths, node, iosOutputDir);
542*753557f6STomasz Sapeta    cleanupEmptyFilesAndFolders(iosOutputDir);
543*753557f6STomasz Sapeta  } catch (err) {
544*753557f6STomasz Sapeta    console.error(err);
545*753557f6STomasz Sapeta    process.exitCode = 1;
546*753557f6STomasz Sapeta  }
547*753557f6STomasz Sapeta
548*753557f6STomasz Sapeta  console.log('\n\n[Codegen] Done.');
549*753557f6STomasz Sapeta  return;
550*753557f6STomasz Sapeta}
551*753557f6STomasz Sapeta
552*753557f6STomasz Sapetamodule.exports = {
553*753557f6STomasz Sapeta  execute: execute,
554*753557f6STomasz Sapeta  // exported for testing purposes only:
555*753557f6STomasz Sapeta  _extractLibrariesFromJSON: extractLibrariesFromJSON,
556*753557f6STomasz Sapeta  _findCodegenEnabledLibraries: findCodegenEnabledLibraries,
557*753557f6STomasz Sapeta  _executeNodeScript: executeNodeScript,
558*753557f6STomasz Sapeta  _generateCode: generateCode,
559*753557f6STomasz Sapeta  _cleanupEmptyFilesAndFolders: cleanupEmptyFilesAndFolders,
560*753557f6STomasz Sapeta};
561