@wavemaker-ai/wm-reactnative-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +236 -0
  2. package/assets/CLI-EnvironmentVariable.png +0 -0
  3. package/assets/EnvironmentVariable.png +0 -0
  4. package/assets/EnvironmentVariable1.png +0 -0
  5. package/files/ui-build.js +331 -0
  6. package/index.js +381 -0
  7. package/package.json +39 -0
  8. package/src/android.js +479 -0
  9. package/src/command.js +552 -0
  10. package/src/config.js +11 -0
  11. package/src/custom-logger/progress-bar.js +97 -0
  12. package/src/custom-logger/steps.js +117 -0
  13. package/src/custom-logger/task-logger.js +147 -0
  14. package/src/exec.js +73 -0
  15. package/src/expo-launcher.js +596 -0
  16. package/src/ios.js +517 -0
  17. package/src/logger.js +104 -0
  18. package/src/mobileprovision-parse/index.js +72 -0
  19. package/src/project-sync.service.js +390 -0
  20. package/src/requirements.js +250 -0
  21. package/src/utils.js +100 -0
  22. package/src/web-preview-launcher.js +548 -0
  23. package/src/zip.js +19 -0
  24. package/templates/embed/android/ReactNativeAppFragment.java +78 -0
  25. package/templates/embed/android/SplashScreenReactActivityLifecycleListener.kt +41 -0
  26. package/templates/embed/android/fragment_react_native_app.xml +14 -0
  27. package/templates/embed/ios/ReactNativeView.h +12 -0
  28. package/templates/embed/ios/ReactNativeView.m +59 -0
  29. package/templates/embed/ios/ReactNativeView.swift +53 -0
  30. package/templates/expo-camera-patch/useWebQRScanner.js +100 -0
  31. package/templates/ios-build-patch/podFIlePostInstall.js +72 -0
  32. package/templates/package/packageLock.json +14334 -0
  33. package/templates/wm-rn-runtime/App.js +479 -0
  34. package/templates/wm-rn-runtime/App.navigator.js +109 -0
  35. package/test.js +0 -0
  36. package/tools-site/index.html.template +17 -0
  37. package/tools-site/page_background.svg +99 -0
  38. package/tools-site/qrcode.js +614 -0
  39. package/tools-site/styles.css +39 -0
package/src/android.js ADDED
@@ -0,0 +1,479 @@
1
+ const fs = require('fs-extra');
2
+ const propertiesReader = require('properties-reader');
3
+ const logger = require('./logger');
4
+ const config = require('./config');
5
+ const {
6
+ exec
7
+ } = require('./exec');
8
+
9
+ const {
10
+ validateForAndroid,
11
+ checkForAndroidStudioAvailability
12
+ } = require('./requirements');
13
+ const { readAndReplaceFileContent } = require('./utils');
14
+ const taskLogger = require('./custom-logger/task-logger').spinnerBar;
15
+ const {androidBuildSteps} = require('./custom-logger/steps');
16
+
17
+ const loggerLabel = 'android-build';
18
+
19
+ function setKeyStoreValuesInGradleProps(content, keystoreName, ksData) {
20
+ // TODO: if key pwds are changed, then just update the values.
21
+ if(content.search(/MYAPP_UPLOAD_STORE_PASSWORD/gm) == -1) {
22
+ return content.concat(` \n MYAPP_UPLOAD_STORE_FILE=${keystoreName}
23
+ MYAPP_UPLOAD_KEY_ALIAS=${ksData.keyAlias}
24
+ MYAPP_UPLOAD_STORE_PASSWORD=${ksData.storePassword}
25
+ MYAPP_UPLOAD_KEY_PASSWORD=${ksData.keyPassword}`);
26
+ }
27
+ return content;
28
+ }
29
+ // Reference: http://reactnative.dev/docs/signed-apk-android
30
+ async function generateSignedApk(keyStore, storePassword, keyAlias, keyPassword, packageType) {
31
+ const ksData = {storePassword: storePassword, keyAlias: keyAlias, keyPassword: keyPassword};
32
+ const namesArr = keyStore.split('/');
33
+ const keystoreName = namesArr[namesArr.length - 1];
34
+ const filepath = config.src + 'android/app/' + keystoreName;
35
+
36
+ fs.copyFileSync(keyStore, filepath);
37
+
38
+ // edit file android/gradle.properties
39
+ const gradlePropsPath = config.src + 'android/gradle.properties';
40
+ if (fs.existsSync(gradlePropsPath)) {
41
+ let data = fs.readFileSync(gradlePropsPath, 'utf8');
42
+ let content = await setKeyStoreValuesInGradleProps(data, keystoreName, ksData);
43
+ fs.writeFileSync(gradlePropsPath, content);
44
+ }
45
+
46
+ const appGradlePath = config.src + 'android/app/build.gradle';
47
+ let content = fs.readFileSync(appGradlePath, 'utf8');
48
+ content = await updateSigningConfig(content);
49
+ fs.writeFileSync(appGradlePath, content);
50
+ await generateAab(packageType);
51
+ }
52
+
53
+ function updateSigningConfig(content) {
54
+ // TODO: replace one of the buildTypes to signingConfigs.release
55
+ if(content.search(/if \(project.hasProperty\(\'MYAPP_UPLOAD_STORE_FILE\'\)\)/gm) == -1) {
56
+ content = content.replace(/signingConfigs\.debug/g, 'signingConfigs.release');
57
+ return content.replace(/signingConfigs \{/gm, `signingConfigs {
58
+ release {
59
+ if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) {
60
+ storeFile file(MYAPP_UPLOAD_STORE_FILE)
61
+ storePassword MYAPP_UPLOAD_STORE_PASSWORD
62
+ keyAlias MYAPP_UPLOAD_KEY_ALIAS
63
+ keyPassword MYAPP_UPLOAD_KEY_PASSWORD
64
+ }
65
+ }`);
66
+ }
67
+ return content;
68
+ }
69
+
70
+ function updateJSEnginePreference() {
71
+ const jsEngine = require(config.src + 'app.json').expo.jsEngine;
72
+ const gradlePropsPath = config.src + 'android/gradle.properties';
73
+ if (fs.existsSync(gradlePropsPath)) {
74
+ let data = fs.readFileSync(gradlePropsPath, 'utf8');
75
+ data = data.replace(/expo\.jsEngine=(jsc|hermes)/, `expo.jsEngine=${jsEngine}`)
76
+ fs.writeFileSync(gradlePropsPath, data);
77
+ logger.info({
78
+ label: loggerLabel,
79
+ message: `js engine is set as ${jsEngine}`
80
+ });
81
+ }
82
+ }
83
+
84
+ function setSigningConfigInGradle() {
85
+ const gradlePath = config.src + 'android/app/build.gradle';
86
+
87
+ let content = fs.readFileSync(gradlePath, 'utf8');
88
+ content = updateSigningConfig(content);
89
+ fs.writeFileSync(gradlePath, content);
90
+
91
+ generateAab();
92
+ }
93
+
94
+ function addKeepFileEntries() {
95
+ fs.mkdirSync(config.src + 'android/app/src/main/res/raw/', {recursive: true});
96
+ const data = `<?xml version="1.0" encoding="utf-8"?>
97
+ <resources xmlns:tools="http://schemas.android.com/tools"
98
+ tools:keep="@raw/*_fontawesome,@raw/*__streamlinelighticon, @raw/*_wavicon, @raw/*_streamlineregularicon" />`;
99
+ fs.appendFileSync(config.src + 'android/app/src/main/res/raw/keep.xml', data);
100
+ }
101
+
102
+
103
+ async function generateAab(packageType) {
104
+ try {
105
+ // addKeepFileEntries();
106
+ await exec('./gradlew', ['clean'], {
107
+ cwd: config.src + 'android'
108
+ });
109
+ logger.info('****** invoking aab build *****');
110
+ if (packageType === 'bundle') {
111
+ await exec('./gradlew', [':app:bundleRelease'], {
112
+ cwd: config.src + 'android'
113
+ });
114
+ } else {
115
+ await exec('./gradlew', ['assembleRelease'], {
116
+ cwd: config.src + 'android'
117
+ });
118
+ }
119
+ }
120
+ catch(e) {
121
+ console.error('error generating release apk. ', e);
122
+ return {
123
+ success: false,
124
+ errors: e
125
+ }
126
+ }
127
+ }
128
+
129
+ const endWith = (str, suffix) => {
130
+ if (!str.endsWith(suffix)) {
131
+ return str += suffix;
132
+ }
133
+ return str;
134
+ };
135
+
136
+ function findFile(path, nameregex) {
137
+ const files = fs.readdirSync(path);
138
+ const f = files.find(f => f.match(nameregex));
139
+ return endWith(path, '/') + f;
140
+ }
141
+
142
+ function addProguardRule() {
143
+ const proguardRulePath = config.src + 'android/app/proguard-rules.pro';
144
+ if (fs.existsSync(proguardRulePath)) {
145
+ var data = `-keep class com.facebook.react.turbomodule.** { *; }`;
146
+ fs.appendFileSync(proguardRulePath,data, 'utf8');
147
+ logger.info('***** added proguard rule ******')
148
+ }
149
+ }
150
+
151
+ function updateOptimizationFlags() {
152
+ logger.info('***** into optimization ******')
153
+ const buildGradlePath = config.src + 'android/app/build.gradle';
154
+ if (fs.existsSync(buildGradlePath)) {
155
+ let content = fs.readFileSync(buildGradlePath, 'utf8');
156
+ if (content.search(`def enableProguardInReleaseBuilds = false`) > -1) {
157
+ content = content.replace(/def enableProguardInReleaseBuilds = false/gm, `def enableProguardInReleaseBuilds = true`)
158
+ .replace(/minifyEnabled enableProguardInReleaseBuilds/gm, `minifyEnabled enableProguardInReleaseBuilds\n shrinkResources false\n`);
159
+ }
160
+ content = content.replace(
161
+ /shrinkResources\s*\(\sfindProperty\('android\.enableShrinkResourcesInReleaseBuilds'\)\?.?:\sfalse\s\)/g,
162
+ "shrinkResources true"
163
+ )
164
+ .replace(
165
+ /minifyEnabled\s+\(?\s*(enableProguardInReleaseBuilds)\s*\)?/g,
166
+ "minifyEnabled true\n " // Adds a proper newline & indentation
167
+ );
168
+ fs.writeFileSync(buildGradlePath, content);
169
+ }
170
+ }
171
+
172
+ async function updateAndroidBuildGradleFile(type) {
173
+ const buildGradlePath = config.src + 'android/app/build.gradle';
174
+ if (fs.existsSync(buildGradlePath)) {
175
+ let content = fs.readFileSync(buildGradlePath, 'utf8');
176
+ if (type === 'release') {
177
+ if (content.search(`entryFile: "index.js"`) === -1) {
178
+ content = content.replace(/^(?!\s)project\.ext\.react = \[/gm, `project.ext.react = [
179
+ entryFile: "index.js",
180
+ bundleAssetName: "index.android.bundle",
181
+ bundleInRelease: true,`);
182
+ } else {
183
+ content = content.replace(/bundleInDebug\: true/gm, `bundleInDebug: false,
184
+ bundleInRelease: true,`).replace(/devDisabledInDebug\: true/gm, ``)
185
+ .replace(/bundleInRelease\: false/gm, `bundleInRelease: true`);
186
+ }
187
+ } else {
188
+ if (content.search(`entryFile: "index.js"`) === -1 && content.search('project.ext.react =') >= 0) {
189
+ content = content.replace(/^(?!\s)project\.ext\.react = \[/gm, `project.ext.react = [
190
+ entryFile: "index.js",
191
+ bundleAssetName: "index.android.bundle",
192
+ bundleInDebug: true,
193
+ devDisabledInDebug: true,`);
194
+ } else if (content.indexOf(`bundleInDebug:`) >= 0) {
195
+ content = content.replace(/bundleInDebug\: false/gm, `bundleInDebug: true`)
196
+ .replace(/devDisabledInDebug\: false/gm, `devDisabledInDebug: true`)
197
+ .replace(/bundleInRelease\: true/gm, `bundleInRelease: false`);
198
+ } else {
199
+ await createJSBundle();
200
+ }
201
+ }
202
+ fs.writeFileSync(buildGradlePath, content);
203
+ }
204
+ }
205
+
206
+ function updateSettingsGradleFile(appName) {
207
+ const path = config.src + 'android/settings.gradle';
208
+ let content = fs.readFileSync(path, 'utf8');
209
+ if (content.search(/^rootProject.name = \'\'/gm) > -1) {
210
+ content = content.replace(/^rootProject.name = \'\'/gm, `rootProject.name = ${appName}`);
211
+ fs.writeFileSync(path, content);
212
+ }
213
+ }
214
+
215
+ async function createJSBundle() {
216
+ fs.mkdirpSync(config.src + '/android/app/src/main/assets');
217
+ return await exec('npx', ['expo', 'export:embed', '--platform', 'android',
218
+ '--dev', 'false', '--entry-file', 'index.js',
219
+ '--bundle-output', 'android/app/src/main/assets/index.android.bundle',
220
+ '--assets-dest', 'android/app/src/main/res/', '--reset-cache'], {
221
+ cwd: config.src
222
+ });
223
+ }
224
+
225
+ async function embed(args) {
226
+ const rnAndroidProject = `${config.src}/android`;
227
+ const embedAndroidProject = `${config.src}/android-embed`;
228
+ fs.mkdirpSync(embedAndroidProject);
229
+ logger.info({
230
+ label: loggerLabel,
231
+ message: 'copying Native Android project.'
232
+ });
233
+ fs.copySync(args.modulePath, embedAndroidProject);
234
+ fs.copySync(
235
+ `${__dirname}/../templates/embed/android/fragment_react_native_app.xml`,
236
+ `${embedAndroidProject}/rnApp/src/main/res/layout/fragment_react_native_app.xml`);
237
+ fs.copySync(
238
+ `${__dirname}/../templates/embed/android/ReactNativeAppFragment.java`,
239
+ `${embedAndroidProject}/app/src/main/java/com/wavemaker/reactnative/ReactNativeAppFragment.java`);
240
+ await readAndReplaceFileContent(
241
+ `${embedAndroidProject}/app/src/main/java/com/wavemaker/reactnative/ReactNativeAppFragment.java`,
242
+ content => content.replace(/\$\{packageName\}/g, config.metaData.id));
243
+ logger.info({
244
+ label: loggerLabel,
245
+ message: 'transforming Native Android files.'
246
+ });
247
+
248
+ // NATIVE CHANGES
249
+
250
+ // settings.gradle changes
251
+ await readAndReplaceFileContent(`${embedAndroidProject}/settings.gradle`, (content) => {
252
+ content = content.replace(`dependencyResolutionManagement {
253
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
254
+ repositories {
255
+ google()
256
+ mavenCentral()
257
+ }
258
+ }`, ``);
259
+ content = content.replace(`include ':app'`, `include ':app'\napply from:'./rnApp/root.settings.gradle'`);
260
+ return content;
261
+ });
262
+
263
+ // gradle.properties changes
264
+ await readAndReplaceFileContent(
265
+ `${embedAndroidProject}/gradle.properties`,
266
+ (content) => {
267
+ const nativeProperties = propertiesReader(`${embedAndroidProject}/gradle.properties`);
268
+ const rnProperties = propertiesReader(`${rnAndroidProject}/gradle.properties`);
269
+ content += (Object.keys(rnProperties.getAllProperties())
270
+ .filter(k => (nativeProperties.get(k) === null))
271
+ .map(k => `\n${k}=${rnProperties.get(k)}`)).join('') || '';
272
+ content = content.replace('android.nonTransitiveRClass=true', 'android.nonTransitiveRClass=false');
273
+ return content.replace(`org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8`, `org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m`);
274
+ });
275
+
276
+ // build.gradle changes
277
+ await readAndReplaceFileContent(`${embedAndroidProject}/build.gradle`, (content) => {
278
+ return content + `apply from:'./rnApp/root.build.gradle'`;
279
+ });
280
+
281
+ // app/build.gradle changes
282
+ await readAndReplaceFileContent(`${embedAndroidProject}/app/build.gradle`, (content) => {
283
+ content = content.replace(`plugins {`, `plugins {\n\tid "com.facebook.react"`);
284
+ content = content.replace(`dependencies {`, `dependencies {\n\timplementation project(':rnApp')\n\timplementation 'com.facebook.react:react-native:+'\n`);
285
+ return content;
286
+ });
287
+
288
+ logger.info({
289
+ label: loggerLabel,
290
+ message: 'Changed Native Android project.'
291
+ });
292
+
293
+ // REACT NATIVE CHANGES
294
+
295
+ fs.copySync(`${rnAndroidProject}/app`, `${embedAndroidProject}/rnApp`);
296
+ fs.copySync(`${rnAndroidProject}/build.gradle`, `${embedAndroidProject}/rnApp/root.build.gradle`);
297
+ fs.copySync(`${rnAndroidProject}/settings.gradle`, `${embedAndroidProject}/rnApp/root.settings.gradle`);
298
+
299
+ // rnApp/root.build.gradle changes
300
+ await readAndReplaceFileContent(`${embedAndroidProject}/rnApp/root.build.gradle`, (content) => {
301
+ return content + `\nallprojects {
302
+ configurations.all {
303
+ resolutionStrategy {
304
+ force "com.facebook.react:react-native:+"
305
+ force "com.facebook.react:hermes-android:+"
306
+ force "androidx.annotation:annotation:1.4.0"
307
+ }
308
+ }
309
+ }`;
310
+ });
311
+
312
+ // rnApp/root.settings.gradle changes
313
+ await readAndReplaceFileContent(`${embedAndroidProject}/rnApp/root.settings.gradle`, (content) => {
314
+ content = content.replace('rootProject.name', '//rootProject.name');
315
+ return content.replace(`':app'`, `':rnApp'`);
316
+ });
317
+
318
+ // rnApp/src/main/AndroidManifest changes
319
+ await readAndReplaceFileContent(
320
+ `${embedAndroidProject}/rnApp/src/main/AndroidManifest.xml`,
321
+ (markup) => markup.replace(
322
+ /<intent-filter>(.|\n)*?android:name="android.intent.category.LAUNCHER"(.|\n)*?<\/intent-filter>/g,
323
+ '<!-- Removed React Native Main activity as launcher. Check the embedApp with Launcher activity -->')
324
+ .replace(' android:theme="@style/AppTheme"', ''));
325
+
326
+ // rnApp/build.gradle changes
327
+ await readAndReplaceFileContent(
328
+ `${embedAndroidProject}/rnApp/build.gradle`,
329
+ (content) => {
330
+ return content.replace(
331
+ `apply plugin: "com.android.application"`,
332
+ `apply plugin: "com.android.library"`)
333
+ .replace(/\s*applicationId.*/, '')
334
+ .replace(`"/scripts/compose-source-maps.js",`,
335
+ `"/scripts/compose-source-maps.js",\n\tenableVmCleanup: false`)
336
+ .replace('applicationVariants.all { variant', '/*applicationVariants.all { variant')
337
+ .replace('implementation "com.facebook.react:react-native:+"', 'api "com.facebook.react:react-native:+"')
338
+ .replace(
339
+ /(versionCodes.get\(abi\)\s\*\s1048576\s\+\sdefaultConfig\.versionCode[\s|\n]*\}[\s|\n]*\}[\s|\n]*\})/,
340
+ '$1*/'
341
+ );
342
+ });
343
+
344
+ fs.copySync(
345
+ `${__dirname}/../templates/embed/android/SplashScreenReactActivityLifecycleListener.kt`,
346
+ `${config.src}/node_modules/expo-splash-screen/android/src/main/java/expo/modules/splashscreen/SplashScreenReactActivityLifecycleListener.kt`);
347
+ await readAndReplaceFileContent(
348
+ `${args.dest}/app.js`,
349
+ (content) => content.replace('props = props || {};', 'props = props || {};\n\tprops.landingPage = props.landingPage || props.pageName;'));
350
+ fs.mkdirpSync(`${config.src}/android-embed/rnApp/src/main/assets`);
351
+ const nodeModules = `${args.dest}/node_modules`;
352
+ const wmScope = fs.existsSync(`${nodeModules}/@wavemaker`)
353
+ ? '@wavemaker'
354
+ : fs.existsSync(`${nodeModules}/@wavemaker-ai`)
355
+ ? '@wavemaker-ai'
356
+ : null;
357
+ if (wmScope) {
358
+ const dialogContentPath = `${nodeModules}/${wmScope}/app-rn-runtime/components/dialogs/dialogcontent/dialogcontent.component.js`;
359
+ if (fs.existsSync(dialogContentPath)) {
360
+ await readAndReplaceFileContent(
361
+ dialogContentPath,
362
+ (content) => content.replace('height', 'maxHeight'));
363
+ }
364
+ }
365
+ await exec('npx', ['expo', 'export:embed', '--platform', 'android',
366
+ '--dev', 'false', '--entry-file', 'index.js',
367
+ '--bundle-output', 'android-embed/rnApp/src/main/assets/index.android.bundle',
368
+ '--assets-dest', 'android-embed/rnApp/src/main/res/', '--reset-cache'], {
369
+ cwd: config.src
370
+ });
371
+ logger.info({
372
+ label: loggerLabel,
373
+ message: 'Changed React Native project.'
374
+ });
375
+ }
376
+
377
+ async function invokeAndroidBuild(args) {
378
+ taskLogger.start(androidBuildSteps[4].start);
379
+ taskLogger.setTotal(androidBuildSteps[4].total);
380
+ let keyStore, storePassword, keyAlias,keyPassword;
381
+
382
+ if (args.buildType === 'debug' && !args.aKeyStore) {
383
+ keyStore = __dirname + '/../defaults/android-debug.keystore';
384
+ keyAlias = 'androiddebugkey';
385
+ keyPassword = 'android';
386
+ storePassword = 'android';
387
+ } else {
388
+ keyStore = args.aKeyStore,
389
+ storePassword = args.aStorePassword,
390
+ keyAlias = args.aKeyAlias,
391
+ keyPassword = args.aKeyPassword
392
+ }
393
+
394
+ if (!await checkForAndroidStudioAvailability()) {
395
+ return {
396
+ success: false
397
+ }
398
+ }
399
+ await readAndReplaceFileContent(
400
+ `${args.dest}/App.js`,
401
+ (content) => {
402
+ return content + `
403
+ // Remove cookies with no expiry time set
404
+ (function() {
405
+ try {
406
+ require('@react-native-cookies/cookies').removeSessionCookies();
407
+ } catch(e) {
408
+ console.error(e);
409
+ }
410
+ }());
411
+ `
412
+ });
413
+ updateJSEnginePreference();
414
+ const appName = config.metaData.name;
415
+ updateSettingsGradleFile(appName);
416
+ if (args.buildType === 'release') {
417
+ const errors = validateForAndroid(keyStore, storePassword, keyAlias, keyPassword);
418
+ if (errors.length > 0) {
419
+ return {
420
+ success: false,
421
+ errors: errors
422
+ }
423
+ }
424
+ addProguardRule();
425
+ updateOptimizationFlags();
426
+ updateAndroidBuildGradleFile(args.buildType);
427
+ taskLogger.incrementProgress(1);
428
+ await generateSignedApk(keyStore, storePassword, keyAlias, keyPassword, args.packageType);
429
+ taskLogger.succeed(androidBuildSteps[4].succeed);
430
+ } else {
431
+ await updateAndroidBuildGradleFile(args.buildType);
432
+ logger.info({
433
+ label: loggerLabel,
434
+ message: 'Updated build.gradle file with debug configuration'
435
+ });
436
+ taskLogger.incrementProgress(0.5)
437
+ try {
438
+ await exec('./gradlew', ['assembleDebug'], {
439
+ cwd: config.src + 'android'
440
+ });
441
+ taskLogger.incrementProgress(1.2)
442
+ taskLogger.succeed(androidBuildSteps[4].succeed);
443
+ } catch(e) {
444
+ console.error('error generating release apk. ', e);
445
+ taskLogger.fail(androidBuildSteps[4].fail);
446
+ return {
447
+ success: false,
448
+ errors: e
449
+ }
450
+ }
451
+ }
452
+ logger.info({
453
+ label: loggerLabel,
454
+ message: 'build completed'
455
+ });
456
+ taskLogger.succeed('build completed')
457
+ const output = args.dest + 'output/android/';
458
+ const outputFilePath = `${output}${appName}(${config.metaData.version}).${args.buildType}.${args.packageType === 'bundle' ? 'aab': 'apk'}`;
459
+
460
+ let bundlePath = null;
461
+ let folder = args.buildType === 'release' ? 'release' : 'debug';
462
+ if (args.packageType === 'bundle') {
463
+ bundlePath = findFile(`${args.dest}android/app/build/outputs/bundle/${folder}`, /\.aab?/);
464
+ } else {
465
+ bundlePath = findFile(`${args.dest}android/app/build/outputs/apk/${folder}`, /\.apk?/);
466
+ }
467
+ fs.mkdirSync(output, {recursive: true});
468
+ fs.copyFileSync(bundlePath, outputFilePath);
469
+ return {
470
+ success: true,
471
+ output: outputFilePath
472
+ };
473
+ }
474
+
475
+ module.exports = {
476
+ generateSignedApk: generateSignedApk,
477
+ invokeAndroidBuild: invokeAndroidBuild,
478
+ embed: embed
479
+ }