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