1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @format
8 */
9
10'use strict';
11
12const fs = require('fs');
13const mkdirp = require('mkdirp');
14const path = require('path');
15const utils = require('./codegen-utils');
16const RNCodegen = utils.getCodegen();
17
18const GENERATORS = {
19  all: {
20    android: ['componentsAndroid', 'modulesAndroid', 'modulesCxx'],
21    ios: ['componentsIOS', 'modulesIOS', 'modulesCxx'],
22  },
23  components: {
24    android: ['componentsAndroid'],
25    ios: ['componentsIOS'],
26  },
27  modules: {
28    android: ['modulesAndroid', 'modulesCxx'],
29    ios: ['modulesIOS', 'modulesCxx'],
30  },
31};
32
33function createOutputDirectoryIfNeeded(outputDirectory, libraryName) {
34  if (!outputDirectory) {
35    outputDirectory = path.resolve(__dirname, '..', 'Libraries', libraryName);
36  }
37  mkdirp.sync(outputDirectory);
38}
39
40function createFolderIfDefined(folder) {
41  if (folder) {
42    mkdirp.sync(folder);
43  }
44}
45
46/**
47 * This function read a JSON schema from a path and parses it.
48 * It throws if the schema don't exists or it can't be parsed.
49 *
50 * @parameter schemaPath: the path to the schema
51 * @return a valid schema
52 * @throw an Error if the schema doesn't exists in a given path or if it can't be parsed.
53 */
54function readAndParseSchema(schemaPath) {
55  const schemaText = fs.readFileSync(schemaPath, 'utf-8');
56
57  if (schemaText == null) {
58    throw new Error(`Can't find schema at ${schemaPath}`);
59  }
60
61  try {
62    return JSON.parse(schemaText);
63  } catch (err) {
64    throw new Error(`Can't parse schema to JSON. ${schemaPath}`);
65  }
66}
67
68function validateLibraryType(libraryType) {
69  if (GENERATORS[libraryType] == null) {
70    throw new Error(`Invalid library type. ${libraryType}`);
71  }
72}
73
74function generateSpec(
75  platform,
76  schemaPath,
77  outputDirectory,
78  libraryName,
79  packageName,
80  libraryType,
81) {
82  validateLibraryType(libraryType);
83
84  let schema = readAndParseSchema(schemaPath);
85
86  createOutputDirectoryIfNeeded(outputDirectory, libraryName);
87  function composePath(intermediate) {
88    return path.join(outputDirectory, intermediate, libraryName);
89  }
90
91  // These are hardcoded and should not be changed.
92  // The codegen creates some C++ code with #include directive
93  // which uses these paths. Those directive are not customizable yet.
94  createFolderIfDefined(composePath('react/renderer/components/'));
95  createFolderIfDefined(composePath('./'));
96
97  RNCodegen.generate(
98    {
99      libraryName,
100      schema,
101      outputDirectory,
102      packageName,
103    },
104    {
105      generators: GENERATORS[libraryType][platform],
106    },
107  );
108
109  if (platform === 'android') {
110    // Move all components C++ files to a structured jni folder for now.
111    // Note: this should've been done by RNCodegen's generators, but:
112    // * the generators don't support platform option yet
113    // * this subdir structure is Android-only, not applicable to iOS
114    const files = fs.readdirSync(outputDirectory);
115    const jniOutputDirectory = `${outputDirectory}/jni/react/renderer/components/${libraryName}`;
116    mkdirp.sync(jniOutputDirectory);
117    files
118      .filter(f => f.endsWith('.h') || f.endsWith('.cpp'))
119      .forEach(f => {
120        fs.renameSync(`${outputDirectory}/${f}`, `${jniOutputDirectory}/${f}`);
121      });
122  }
123}
124
125module.exports = {
126  execute: generateSpec,
127};
128