• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

electrode-io / electrode-native / 7453

01 Aug 2025 08:28PM UTC coverage: 56.454% (-3.6%) from 60.024%
7453

push

Azure Pipelines

web-flow
Merge pull request #1915 from r0h0gg6/container-android-publishing-fix

Declare variants for publishing for AGP 8

3549 of 7723 branches covered (45.95%)

Branch coverage included in aggregate %.

9388 of 15193 relevant lines covered (61.79%)

524.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

9.41
/ern-api-impl-gen/src/generators/android/ApiImplAndroidGenerator.ts
1
import {
2✔
2
  android,
3
  fileUtils,
4
  injectReactNativeVersionKeysInObject,
5
  log,
6
  manifest,
7
  mustacheUtils,
8
  PackagePath,
9
  Platform,
10
  PluginConfig,
11
  shell,
12
} from 'ern-core';
13
import fs from 'fs-extra';
2✔
14
import path from 'path';
2✔
15
import readDir from 'fs-readdir-recursive';
2✔
16
import { ApiImplGeneratable } from '../../ApiImplGeneratable';
17

18
export const ROOT_DIR = shell.pwd();
2✔
19
const SRC_MAIN_JAVA_DIR = path.normalize('src/main/java');
2✔
20
const API_IMPL_PACKAGE = path.normalize('com/ern/api/impl');
2✔
21

22
export default class ApiImplAndroidGenerator implements ApiImplGeneratable {
2✔
23
  public static getMustacheFileNamesMap(resourceDir: string, apiName: string) {
24
    const files = readDir(resourceDir, (f) => f.endsWith('.mustache'));
×
25
    const classNames: { [k: string]: string } = {
×
26
      'RequestHandlerConfig.java.mustache': 'RequestHandlerConfig.java',
27
      'RequestHandlerProvider.java.mustache': 'RequestHandlerProvider.java',
28
      'apiController.mustache': `${apiName}ApiController.java`,
29
      'requestHandlerProvider.mustache': `${apiName}ApiRequestHandlerProvider.java`,
30
      'requestHandlers.mustache': `${apiName}ApiRequestHandler.java`,
31
    };
32
    return { files, classNames };
×
33
  }
34

35
  private regenerateApiImpl: boolean;
36

37
  get name(): string {
38
    return 'ApiImplAndroidGenerator';
×
39
  }
40

41
  get platform(): string {
42
    return 'android';
×
43
  }
44

45
  public async generate(
46
    apiDependency: PackagePath,
47
    paths: any,
48
    reactNativeVersion: string,
49
    plugins: PackagePath[],
50
    apis: any[],
51
    regen: boolean,
52
  ) {
53
    log.debug(`Starting project generation for ${this.platform}`);
×
54
    this.regenerateApiImpl = regen;
×
55
    await this.fillHull(
×
56
      apiDependency,
57
      paths,
58
      reactNativeVersion,
59
      plugins,
60
      apis,
61
    );
62
  }
63

64
  public async fillHull(
65
    apiDependency: PackagePath,
66
    paths: any,
67
    reactNativeVersion: string,
68
    pluginsPaths: PackagePath[],
69
    apis: any[],
70
  ) {
71
    shell.pushd(ROOT_DIR);
×
72

73
    try {
×
74
      log.debug(
×
75
        `[=== Starting hull filling for api impl gen for ${this.platform} ===]`,
76
      );
77

78
      const outputDirectory = path.join(paths.outDirectory, 'android');
×
79
      log.debug(
×
80
        `Creating out directory(${outputDirectory}) for android and copying container hull to it.`,
81
      );
82

83
      fs.ensureDirSync(outputDirectory);
×
84

85
      fileUtils.chmodr('755', outputDirectory);
×
86
      shell.cp(
×
87
        '-Rf',
88
        path.join(paths.apiImplHull, 'android/{.*,*}'),
89
        outputDirectory,
90
      );
91

92
      const srcOutputDirectory = path.join(
×
93
        outputDirectory,
94
        'lib',
95
        SRC_MAIN_JAVA_DIR,
96
      );
97
      let pluginPath: PackagePath;
98
      for (pluginPath of pluginsPaths) {
×
99
        const pluginConfig = await manifest.getPluginConfig(
×
100
          pluginPath,
101
          'android',
102
        );
103
        if (pluginConfig) {
×
104
          log.debug(`Copying ${pluginPath.name} to ${outputDirectory}`);
×
105
          this.copyPluginToOutput(
×
106
            paths,
107
            srcOutputDirectory,
108
            pluginPath,
109
            pluginConfig,
110
          );
111
        }
112
      }
113
      const editableFiles = await this.generateRequestHandlerClasses(
×
114
        apiDependency,
115
        paths,
116
        apis,
117
      );
118
      await this.updateGradleProperties(
×
119
        paths,
120
        reactNativeVersion,
121
        outputDirectory,
122
      );
123
      await this.updateBuildGradle(
×
124
        apiDependency,
125
        paths,
126
        reactNativeVersion,
127
        outputDirectory,
128
      );
129
    } finally {
130
      shell.popd();
×
131
    }
132
  }
133

134
  public copyPluginToOutput(
135
    paths: any,
136
    pluginOutputDirectory: string,
137
    pluginPath: PackagePath,
138
    pluginConfig: PluginConfig<'android'>,
139
  ) {
140
    if (pluginPath.name === 'react-native') {
×
141
      return;
×
142
    }
143
    log.debug(`injecting ${pluginPath.name} code.`);
×
144
    const pluginSrcDirectory = path.join(
×
145
      paths.outDirectory,
146
      'node_modules',
147
      pluginPath.name!,
148
      'android',
149
      pluginConfig.moduleName,
150
      SRC_MAIN_JAVA_DIR,
151
      '*',
152
    );
153

154
    fs.ensureDirSync(pluginOutputDirectory);
×
155

156
    log.debug(
×
157
      `Copying code from ${pluginSrcDirectory} to ${pluginOutputDirectory}`,
158
    );
159
    shell.cp('-Rf', pluginSrcDirectory, pluginOutputDirectory);
×
160
  }
161

162
  public updateBuildGradle(
163
    apiDependency: PackagePath,
164
    paths: any,
165
    reactNativeVersion: string,
166
    outputDirectory: string,
167
  ): Promise<any> {
168
    let mustacheView: any = {};
×
169
    const versions = android.resolveAndroidVersions({
×
170
      androidGradlePlugin: '3.2.1',
171
      reactNativeVersion,
172
    });
173
    mustacheView.reactNativeVersion = reactNativeVersion;
×
174
    mustacheView = Object.assign(mustacheView, versions);
×
175
    injectReactNativeVersionKeysInObject(mustacheView, reactNativeVersion);
×
176
    mustacheUtils.mustacheRenderToOutputFileUsingTemplateFile(
×
177
      path.join(paths.apiImplHull, 'android/build.gradle'),
178
      mustacheView,
179
      path.join(outputDirectory, 'build.gradle'),
180
    );
181
    return mustacheUtils.mustacheRenderToOutputFileUsingTemplateFile(
×
182
      path.join(paths.apiImplHull, 'android/lib/build.gradle'),
183
      mustacheView,
184
      path.join(outputDirectory, 'lib/build.gradle'),
185
    );
186
  }
187

188
  public updateGradleProperties(
189
    paths: any,
190
    reactNativeVersion: string,
191
    outputDirectory: string,
192
  ): Promise<any> {
193
    let mustacheView: any = {};
×
194
    const versions = android.resolveAndroidVersions({ reactNativeVersion });
×
195
    mustacheView = Object.assign(mustacheView, versions);
×
196
    injectReactNativeVersionKeysInObject(mustacheView, reactNativeVersion);
×
197
    return mustacheUtils.mustacheRenderToOutputFileUsingTemplateFile(
×
198
      path.join(
199
        paths.apiImplHull,
200
        'android/gradle/wrapper/gradle-wrapper.properties',
201
      ),
202
      mustacheView,
203
      path.join(outputDirectory, 'gradle/wrapper/gradle-wrapper.properties'),
204
    );
205
  }
206

207
  public async generateRequestHandlerClasses(
208
    apiDependency: PackagePath,
209
    paths: any,
210
    apis: any[],
211
  ) {
212
    log.debug('=== updating request handler implementation class ===');
×
213
    try {
×
214
      const editableFiles: string[] = [];
×
215
      const resourceDir = path.join(
×
216
        Platform.currentPlatformVersionPath,
217
        'ern-api-impl-gen/resources/android',
218
      );
219
      const outputDir = path.join(
×
220
        paths.outDirectory,
221
        'android/lib',
222
        SRC_MAIN_JAVA_DIR,
223
        API_IMPL_PACKAGE,
224
      );
225
      fs.ensureDirSync(outputDir);
×
226

227
      for (const api of apis) {
×
228
        const { files, classNames } =
229
          ApiImplAndroidGenerator.getMustacheFileNamesMap(
×
230
            resourceDir,
231
            api.apiName,
232
          );
233
        for (const file of files) {
×
234
          if (file === 'requestHandlerProvider.mustache') {
×
235
            editableFiles.push(path.join(outputDir, classNames[file]));
×
236
            if (this.regenerateApiImpl) {
×
237
              log.debug(`Skipping regeneration of ${classNames[file]}`);
×
238
              continue;
×
239
            }
240
          }
241
          if (classNames[file]) {
×
242
            const partialProxy = (name: string) => {
×
243
              return fs.readFileSync(
×
244
                path.join(resourceDir, `${name}.mustache`),
245
                'utf8',
246
              );
247
            };
248
            await mustacheUtils.mustacheRenderToOutputFileUsingTemplateFile(
×
249
              path.join(resourceDir, file),
250
              api,
251
              path.join(outputDir, classNames[file]),
252
              partialProxy,
253
            );
254
          } else {
255
            log.info(`No mapping for for ${file}. Mustaching skipped.`);
×
256
          }
257
        }
258
        log.debug(
×
259
          `Api implementation files successfully generated for ${api.apiName}Api`,
260
        );
261
      }
262
      return editableFiles;
×
263
    } catch (e) {
264
      throw new Error(`Failed to update RequestHandlerClass: ${e}`);
×
265
    }
266
  }
267
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc