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