rnbuilder 0.6.71 → 0.6.73

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.
package/README.md CHANGED
@@ -10,10 +10,28 @@ React Native builder tool
10
10
 
11
11
  <!-- toc -->
12
12
  * [rnbuilder](#rnbuilder)
13
+ * [Setup](#setup)
13
14
  * [Usage](#usage)
14
15
  * [Commands](#commands)
15
16
  <!-- tocstop -->
16
17
 
18
+ # Setup
19
+
20
+ ## Google Play Store (Android)
21
+
22
+ Builds are uploaded to Google Play using **GCP service account impersonation** — no private key files stored on disk.
23
+
24
+ Run once per machine:
25
+
26
+ ```sh-session
27
+ $ rnbuilder setup-gcp
28
+ ```
29
+
30
+ Each client `.env` must set `GOOGLE_APPLICATION_CREDENTIALS` pointing to the SA's ADC file.
31
+ Google Play Console limits one service account to 10 developer accounts — use multiple SAs for more clients.
32
+
33
+ See [docs/gcp-service-account-impersonation.md](docs/gcp-service-account-impersonation.md) for full setup, adding new service accounts, client mapping, and troubleshooting.
34
+
17
35
  # Usage
18
36
 
19
37
  <!-- usage -->
@@ -22,7 +40,7 @@ $ npm install -g rnbuilder
22
40
  $ rnbuilder COMMAND
23
41
  running command...
24
42
  $ rnbuilder (-v|--version|version)
25
- rnbuilder/0.6.71 darwin-arm64 node-v24.13.0
43
+ rnbuilder/0.6.73 darwin-arm64 node-v24.13.0
26
44
  $ rnbuilder --help [COMMAND]
27
45
  USAGE
28
46
  $ rnbuilder COMMAND
@@ -44,6 +62,7 @@ USAGE
44
62
  * [`rnbuilder onesignal [BUILD]`](#rnbuilder-onesignal-build)
45
63
  * [`rnbuilder reject-review [REJECT-REVIEW]`](#rnbuilder-reject-review-reject-review)
46
64
  * [`rnbuilder setup [SETUP]`](#rnbuilder-setup-setup)
65
+ * [`rnbuilder setup-gcp`](#rnbuilder-setup-gcp)
47
66
  * [`rnbuilder submit-review [SUBMIT-REVIEW]`](#rnbuilder-submit-review-submit-review)
48
67
  * [`rnbuilder sync-udids [BUILD]`](#rnbuilder-sync-udids-build)
49
68
 
@@ -79,7 +98,7 @@ EXAMPLE
79
98
  hello world from ./src/build.ts!
80
99
  ```
81
100
 
82
- _See code: [src/commands/build.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.71/src/commands/build.ts)_
101
+ _See code: [src/commands/build.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.73/src/commands/build.ts)_
83
102
 
84
103
  ## `rnbuilder check-version [MATCH-CHECK]`
85
104
 
@@ -98,7 +117,7 @@ EXAMPLE
98
117
  Nuke certificates ./src/match-check.ts!
99
118
  ```
100
119
 
101
- _See code: [src/commands/check-version.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.71/src/commands/check-version.ts)_
120
+ _See code: [src/commands/check-version.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.73/src/commands/check-version.ts)_
102
121
 
103
122
  ## `rnbuilder codepush [BUILD]`
104
123
 
@@ -117,7 +136,7 @@ EXAMPLE
117
136
  Push changes to existing apps ./src/firebase-sync-udids.ts!
118
137
  ```
119
138
 
120
- _See code: [src/commands/codepush.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.71/src/commands/codepush.ts)_
139
+ _See code: [src/commands/codepush.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.73/src/commands/codepush.ts)_
121
140
 
122
141
  ## `rnbuilder google-key [MATCH-CHECK]`
123
142
 
@@ -136,7 +155,7 @@ EXAMPLE
136
155
  Nuke certificates ./src/match-check.ts!
137
156
  ```
138
157
 
139
- _See code: [src/commands/google-key.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.71/src/commands/google-key.ts)_
158
+ _See code: [src/commands/google-key.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.73/src/commands/google-key.ts)_
140
159
 
141
160
  ## `rnbuilder help [COMMAND]`
142
161
 
@@ -168,7 +187,7 @@ EXAMPLE
168
187
  initialize environment for first run from ./src/initialize.ts!
169
188
  ```
170
189
 
171
- _See code: [src/commands/initialize.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.71/src/commands/initialize.ts)_
190
+ _See code: [src/commands/initialize.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.73/src/commands/initialize.ts)_
172
191
 
173
192
  ## `rnbuilder match-check [MATCH-CHECK]`
174
193
 
@@ -187,7 +206,7 @@ EXAMPLE
187
206
  Check match certificates ./src/match-check.ts!
188
207
  ```
189
208
 
190
- _See code: [src/commands/match-check.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.71/src/commands/match-check.ts)_
209
+ _See code: [src/commands/match-check.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.73/src/commands/match-check.ts)_
191
210
 
192
211
  ## `rnbuilder match-nuke [BUILD]`
193
212
 
@@ -206,7 +225,7 @@ EXAMPLE
206
225
  Nuke certificates ./src/match-nuke.ts!
207
226
  ```
208
227
 
209
- _See code: [src/commands/match-nuke.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.71/src/commands/match-nuke.ts)_
228
+ _See code: [src/commands/match-nuke.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.73/src/commands/match-nuke.ts)_
210
229
 
211
230
  ## `rnbuilder onesignal [BUILD]`
212
231
 
@@ -228,7 +247,7 @@ EXAMPLE
228
247
  Create and upload push certificate to One Signal from ./src/commands/onesignal.ts!
229
248
  ```
230
249
 
231
- _See code: [src/commands/onesignal.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.71/src/commands/onesignal.ts)_
250
+ _See code: [src/commands/onesignal.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.73/src/commands/onesignal.ts)_
232
251
 
233
252
  ## `rnbuilder reject-review [REJECT-REVIEW]`
234
253
 
@@ -246,7 +265,7 @@ EXAMPLE
246
265
  $ rnbuilder reject-review
247
266
  ```
248
267
 
249
- _See code: [src/commands/reject-review.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.71/src/commands/reject-review.ts)_
268
+ _See code: [src/commands/reject-review.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.73/src/commands/reject-review.ts)_
250
269
 
251
270
  ## `rnbuilder setup [SETUP]`
252
271
 
@@ -264,7 +283,21 @@ EXAMPLE
264
283
  hello world from ./src/setup.ts!
265
284
  ```
266
285
 
267
- _See code: [src/commands/setup.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.71/src/commands/setup.ts)_
286
+ _See code: [src/commands/setup.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.73/src/commands/setup.ts)_
287
+
288
+ ## `rnbuilder setup-gcp`
289
+
290
+ Setup GCP service account impersonation
291
+
292
+ ```
293
+ USAGE
294
+ $ rnbuilder setup-gcp
295
+
296
+ OPTIONS
297
+ -h, --help show CLI help
298
+ ```
299
+
300
+ _See code: [src/commands/setup-gcp.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.73/src/commands/setup-gcp.ts)_
268
301
 
269
302
  ## `rnbuilder submit-review [SUBMIT-REVIEW]`
270
303
 
@@ -283,7 +316,7 @@ EXAMPLE
283
316
  Push changes to existing apps ./src/firebase-sync-udids.ts!
284
317
  ```
285
318
 
286
- _See code: [src/commands/submit-review.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.71/src/commands/submit-review.ts)_
319
+ _See code: [src/commands/submit-review.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.73/src/commands/submit-review.ts)_
287
320
 
288
321
  ## `rnbuilder sync-udids [BUILD]`
289
322
 
@@ -302,5 +335,5 @@ EXAMPLE
302
335
  Sync device UDIDs from firebase from ./src/firebase-sync-udids.ts!
303
336
  ```
304
337
 
305
- _See code: [src/commands/sync-udids.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.71/src/commands/sync-udids.ts)_
338
+ _See code: [src/commands/sync-udids.ts](https://github.com/r0b0t3d/rnbuilder/blob/v0.6.73/src/commands/sync-udids.ts)_
306
339
  <!-- commandsstop -->
@@ -0,0 +1,8 @@
1
+ import { Command } from '@oclif/command';
2
+ export default class SetupGcp extends Command {
3
+ static description: string;
4
+ static flags: {
5
+ help: import("@oclif/parser/lib/flags").IBooleanFlag<void>;
6
+ };
7
+ run(): Promise<void>;
8
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const command_1 = require("@oclif/command");
4
+ const child_process_1 = require("child_process");
5
+ const path = require("path");
6
+ const common_1 = require("../utils/common");
7
+ class SetupGcp extends command_1.Command {
8
+ async run() {
9
+ var _a, _b, _c, _d, _e;
10
+ const configs = common_1.getFastlaneConfigs();
11
+ const gcp = (_a = configs.gcp) !== null && _a !== void 0 ? _a : {};
12
+ const scriptPath = path.join(__dirname, '../../scripts/setup_gcp_impersonation.sh');
13
+ const result = child_process_1.spawnSync('bash', [scriptPath], {
14
+ stdio: 'inherit',
15
+ env: Object.assign(Object.assign({}, process.env), { GCP_PROJECT_ID: (_b = gcp.projectId) !== null && _b !== void 0 ? _b : '', GCP_USER_EMAIL: (_c = gcp.userEmail) !== null && _c !== void 0 ? _c : '', GCP_SERVICE_ACCOUNTS: ((_d = gcp.serviceAccounts) !== null && _d !== void 0 ? _d : []).join(',') }),
16
+ });
17
+ if (result.status !== 0) {
18
+ this.exit((_e = result.status) !== null && _e !== void 0 ? _e : 1);
19
+ }
20
+ }
21
+ }
22
+ exports.default = SetupGcp;
23
+ SetupGcp.description = 'Setup GCP service account impersonation';
24
+ SetupGcp.flags = {
25
+ help: command_1.flags.help({ char: 'h' }),
26
+ };
@@ -52,6 +52,7 @@ class Setup extends command_1.Command {
52
52
  // shell.exec(`mkdir configs/${client}`);
53
53
  const { appIconFile } = await app_icon_1.prepareAppIcon({
54
54
  iosAssetFolder,
55
+ androidAssetFolder,
55
56
  });
56
57
  const { launchScreen, launchScreenColor } = await launch_screen_1.prepareLaunchScreen({
57
58
  iosAssetFolder,
@@ -64,11 +65,9 @@ class Setup extends command_1.Command {
64
65
  if (launchScreenColor) {
65
66
  env_1.setEnvValue('LAUNCH_SCREEN_BACKGROUND_COLOR', launchScreenColor, envVars);
66
67
  }
67
- // Onesignal
68
- const { onesignal } = await onesignal_1.prepareOneSignal();
69
- if (onesignal) {
70
- env_1.setEnvValue('ONESIGNAL_APP_ID', onesignal, envVars);
71
- }
68
+ const devEnvPath = path.join(process.cwd(), `/configs/${client}/.env.dev`);
69
+ const stagingEnvPath = path.join(process.cwd(), `/configs/${client}/.env.staging`);
70
+ const prodEnvPath = path.join(process.cwd(), `/configs/${client}/.env.prod`);
72
71
  // Store dev env
73
72
  env_1.setEnvValue('ENV', 'dev', envVars);
74
73
  const devEnvVars = env_1.updateServiceDomain(envVars, 'app');
@@ -77,7 +76,7 @@ class Setup extends command_1.Command {
77
76
  }
78
77
  env_1.setEnvValue('BUNDLE_ID', `${bundleId}.dev`, devEnvVars);
79
78
  env_1.setEnvValue('APPLICATION_ID', `${applicationId}.dev`, devEnvVars);
80
- env_1.saveEnvValues(devEnvVars, path.join(process.cwd(), `/configs/${client}/.env.dev`));
79
+ env_1.saveEnvValues(devEnvVars, devEnvPath);
81
80
  // Store staging env
82
81
  env_1.setEnvValue('ENV', 'staging', envVars);
83
82
  const stagingEnvVars = env_1.updateServiceDomain(envVars, 'app');
@@ -86,7 +85,7 @@ class Setup extends command_1.Command {
86
85
  }
87
86
  env_1.setEnvValue('BUNDLE_ID', `${bundleId}.staging`, stagingEnvVars);
88
87
  env_1.setEnvValue('APPLICATION_ID', `${applicationId}.staging`, stagingEnvVars);
89
- env_1.saveEnvValues(stagingEnvVars, path.join(process.cwd(), `/configs/${client}/.env.staging`));
88
+ env_1.saveEnvValues(stagingEnvVars, stagingEnvPath);
90
89
  // Store prod env
91
90
  env_1.setEnvValue('ENV', 'prod', envVars);
92
91
  const prodEnvVars = env_1.updateServiceDomain(envVars, 'com');
@@ -95,7 +94,7 @@ class Setup extends command_1.Command {
95
94
  }
96
95
  env_1.setEnvValue('BUNDLE_ID', bundleId, prodEnvVars);
97
96
  env_1.setEnvValue('APPLICATION_ID', applicationId, prodEnvVars);
98
- env_1.saveEnvValues(prodEnvVars, path.join(process.cwd(), `/configs/${client}/.env.prod`));
97
+ env_1.saveEnvValues(prodEnvVars, prodEnvPath);
99
98
  // Setup fastlane
100
99
  const { firebaseIosApp, firebaseAndroidApp } = await fastlane_1.setupFastlane({
101
100
  client,
@@ -110,7 +109,7 @@ class Setup extends command_1.Command {
110
109
  env_1.setEnvValue('APP_IDENTIFIER', bundleId, fastlaneEnvVars);
111
110
  }
112
111
  if (applicationId) {
113
- env_1.setEnvValue('APP_IDENTIFIER_ANDROID', bundleId, fastlaneEnvVars);
112
+ env_1.setEnvValue('APP_IDENTIFIER_ANDROID', applicationId, fastlaneEnvVars);
114
113
  }
115
114
  if (appName) {
116
115
  env_1.setEnvValue('APP_NAME', appName, fastlaneEnvVars);
@@ -130,6 +129,25 @@ class Setup extends command_1.Command {
130
129
  env_1.setEnvValue('FIREBASE_ANDROID_APP', firebaseAndroidApp, fastlaneEnvVars);
131
130
  }
132
131
  env_1.saveEnvValues(fastlaneEnvVars, fastlaneDir + '/.env');
132
+ // Onesignal — creates the push cert via fastlane, needs the client fastlane
133
+ // env (APPLE_TEAM_ID, APP_IDENTIFIER, CLIENT) already written above.
134
+ const { onesignal, onesignalRestApiKey } = await onesignal_1.prepareOneSignal({
135
+ client,
136
+ fastlaneDir,
137
+ });
138
+ if (onesignal) {
139
+ env_1.setEnvValue('ONESIGNAL_APP_ID', onesignal, devEnvVars);
140
+ env_1.setEnvValue('ONESIGNAL_APP_ID', onesignal, stagingEnvVars);
141
+ env_1.setEnvValue('ONESIGNAL_APP_ID', onesignal, prodEnvVars);
142
+ env_1.saveEnvValues(devEnvVars, devEnvPath);
143
+ env_1.saveEnvValues(stagingEnvVars, stagingEnvPath);
144
+ env_1.saveEnvValues(prodEnvVars, prodEnvPath);
145
+ env_1.setEnvValue('ONESIGNAL_APP_ID', onesignal, fastlaneEnvVars);
146
+ if (onesignalRestApiKey) {
147
+ env_1.setEnvValue('ONESIGNAL_REST_API_KEY', onesignalRestApiKey, fastlaneEnvVars);
148
+ }
149
+ env_1.saveEnvValues(fastlaneEnvVars, fastlaneDir + '/.env');
150
+ }
133
151
  }
134
152
  }
135
153
  exports.default = Setup;
@@ -0,0 +1,4 @@
1
+ export declare function generateAndroidIcons({ sourceFile, androidAssetFolder, }: {
2
+ sourceFile: string;
3
+ androidAssetFolder: string;
4
+ }): Promise<void>;
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateAndroidIcons = void 0;
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const sharp = require("sharp");
7
+ const common_1 = require("../common");
8
+ const DENSITIES = [
9
+ { folder: 'mdpi', scale: 1 },
10
+ { folder: 'hdpi', scale: 1.5 },
11
+ { folder: 'xhdpi', scale: 2 },
12
+ { folder: 'xxhdpi', scale: 3 },
13
+ { folder: 'xxxhdpi', scale: 4 },
14
+ ];
15
+ const LEGACY_BASE_SIZE = 48;
16
+ const ADAPTIVE_CANVAS_BASE_SIZE = 108;
17
+ const ADAPTIVE_SAFE_ZONE_BASE_SIZE = 66;
18
+ const NOTIFICATION_BASE_SIZE = 24;
19
+ const ADAPTIVE_ICON_XML = `<?xml version="1.0" encoding="utf-8"?>
20
+ <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
21
+ <background android:drawable="@mipmap/ic_launcher_background"/>
22
+ <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
23
+ </adaptive-icon>
24
+ `;
25
+ function circleMask(size) {
26
+ return Buffer.from(`<svg width="${size}" height="${size}"><circle cx="${size / 2}" cy="${size / 2}" r="${size / 2}" fill="#fff"/></svg>`);
27
+ }
28
+ async function generateLegacyIcons(sourceFile, mipmapDir, size) {
29
+ const square = await sharp(sourceFile).resize(size, size).png().toBuffer();
30
+ await sharp(square).toFile(path.join(mipmapDir, 'ic_launcher.png'));
31
+ await sharp(square)
32
+ .composite([{ input: circleMask(size), blend: 'dest-in' }])
33
+ .png()
34
+ .toFile(path.join(mipmapDir, 'ic_launcher_round.png'));
35
+ }
36
+ async function generateAdaptiveLayers(sourceFile, mipmapDir, canvasSize, safeZoneSize) {
37
+ await sharp({
38
+ create: { width: canvasSize, height: canvasSize, channels: 4, background: { r: 255, g: 255, b: 255, alpha: 1 } },
39
+ })
40
+ .png()
41
+ .toFile(path.join(mipmapDir, 'ic_launcher_background.png'));
42
+ const offset = Math.round((canvasSize - safeZoneSize) / 2);
43
+ const foreground = await sharp(sourceFile).resize(safeZoneSize, safeZoneSize).toBuffer();
44
+ await sharp({
45
+ create: { width: canvasSize, height: canvasSize, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } },
46
+ })
47
+ .composite([{ input: foreground, left: offset, top: offset }])
48
+ .png()
49
+ .toFile(path.join(mipmapDir, 'ic_launcher_foreground.png'));
50
+ }
51
+ // Notification icons must be a white silhouette on transparent. If the source
52
+ // has an alpha channel we use it directly (correct shape); otherwise we fall
53
+ // back to a luminance threshold, which assumes a dark mark on a light background.
54
+ async function buildSilhouetteMask(sourceFile, size) {
55
+ const meta = await sharp(sourceFile).metadata();
56
+ if (meta.hasAlpha) {
57
+ return sharp(sourceFile).resize(size, size).ensureAlpha().png().toBuffer();
58
+ }
59
+ const alphaChannel = await sharp(sourceFile)
60
+ .resize(size, size)
61
+ .greyscale()
62
+ .negate()
63
+ .threshold(128)
64
+ .toBuffer();
65
+ const rgb = await sharp({
66
+ create: { width: size, height: size, channels: 3, background: { r: 255, g: 255, b: 255 } },
67
+ })
68
+ .png()
69
+ .toBuffer();
70
+ return sharp(rgb).joinChannel(alphaChannel).png().toBuffer();
71
+ }
72
+ async function generateNotificationIcon(sourceFile, drawableDir, size) {
73
+ const mask = await buildSilhouetteMask(sourceFile, size);
74
+ const white = await sharp({
75
+ create: { width: size, height: size, channels: 4, background: { r: 255, g: 255, b: 255, alpha: 1 } },
76
+ })
77
+ .png()
78
+ .toBuffer();
79
+ await sharp(white)
80
+ .composite([{ input: mask, blend: 'dest-in' }])
81
+ .png()
82
+ .toFile(path.join(drawableDir, 'ic_notification.png'));
83
+ }
84
+ async function generateAndroidIcons({ sourceFile, androidAssetFolder, }) {
85
+ const anydpiDir = path.join(androidAssetFolder, 'mipmap-anydpi-v26');
86
+ common_1.ensureDir(anydpiDir);
87
+ fs.writeFileSync(path.join(anydpiDir, 'ic_launcher.xml'), ADAPTIVE_ICON_XML, 'utf-8');
88
+ fs.writeFileSync(path.join(anydpiDir, 'ic_launcher_round.xml'), ADAPTIVE_ICON_XML, 'utf-8');
89
+ for (const { folder, scale } of DENSITIES) {
90
+ const mipmapDir = path.join(androidAssetFolder, `mipmap-${folder}`);
91
+ const drawableDir = path.join(androidAssetFolder, `drawable-${folder}`);
92
+ common_1.ensureDir(mipmapDir);
93
+ common_1.ensureDir(drawableDir);
94
+ await generateLegacyIcons(sourceFile, mipmapDir, Math.round(LEGACY_BASE_SIZE * scale));
95
+ await generateAdaptiveLayers(sourceFile, mipmapDir, Math.round(ADAPTIVE_CANVAS_BASE_SIZE * scale), Math.round(ADAPTIVE_SAFE_ZONE_BASE_SIZE * scale));
96
+ await generateNotificationIcon(sourceFile, drawableDir, Math.round(NOTIFICATION_BASE_SIZE * scale));
97
+ }
98
+ console.log(`Generated Android launcher + adaptive + notification icons in ${androidAssetFolder}`);
99
+ console.log('Note: reference "ic_notification" from AndroidManifest.xml / push SDK config manually if not already wired.');
100
+ }
101
+ exports.generateAndroidIcons = generateAndroidIcons;
@@ -1,4 +1,3 @@
1
- export declare function addProductFlavor({ client, applicationId, }: {
1
+ export declare function addProductFlavor({ client, }: {
2
2
  client: string;
3
- applicationId: string;
4
3
  }): void;
@@ -3,49 +3,57 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.addProductFlavor = void 0;
4
4
  const fs = require("fs");
5
5
  const path = require("path");
6
- function insertIntoProductFlavors(gradle, flavor) {
7
- // Find productFlavors block and insert before its closing brace
6
+ function insertIntoProductFlavors(gradle, client) {
7
+ var _a;
8
8
  const blockStart = gradle.indexOf('productFlavors {');
9
9
  if (blockStart === -1) {
10
- // No productFlavors block — append one inside android {}
11
10
  const androidEnd = gradle.lastIndexOf('}');
12
11
  return (gradle.slice(0, androidEnd) +
13
- `\n productFlavors {\n${flavor}\n }\n` +
12
+ `\n productFlavors {\n ${client}\n }\n` +
14
13
  gradle.slice(androidEnd));
15
14
  }
16
- // Walk forward matching braces to find closing }
17
- let depth = 0;
18
- let i = blockStart;
19
- while (i < gradle.length) {
15
+ const bracePos = gradle.indexOf('{', blockStart) + 1;
16
+ // Find closing brace of productFlavors block
17
+ let depth = 1;
18
+ let i = bracePos;
19
+ while (i < gradle.length && depth > 0) {
20
20
  if (gradle[i] === '{')
21
21
  depth++;
22
- else if (gradle[i] === '}') {
22
+ else if (gradle[i] === '}')
23
23
  depth--;
24
- if (depth === 0)
25
- break;
26
- }
27
24
  i++;
28
25
  }
29
- return gradle.slice(0, i) + '\n' + flavor + '\n' + gradle.slice(i);
26
+ const blockEnd = i - 1;
27
+ const blockContent = gradle.slice(bracePos, blockEnd);
28
+ const lines = blockContent.split('\n');
29
+ // Detect indentation from existing flavor lines
30
+ const existingLine = lines.find(l => /^\s+\w+\s*$/.test(l));
31
+ const indent = existingLine ? ((_a = existingLine.match(/^(\s+)/)) !== null && _a !== void 0 ? _a : ['', ' '])[1] : ' ';
32
+ // Find insertion point to maintain alphabetical order
33
+ let insertAt = lines.length - 1; // default: before last empty/closing line
34
+ for (let li = 0; li < lines.length; li++) {
35
+ const name = lines[li].trim();
36
+ if (/^\w+$/.test(name) && name.localeCompare(client) > 0) {
37
+ insertAt = li;
38
+ break;
39
+ }
40
+ }
41
+ lines.splice(insertAt, 0, `${indent}${client}`);
42
+ return gradle.slice(0, bracePos) + lines.join('\n') + gradle.slice(blockEnd);
30
43
  }
31
- function addProductFlavor({ client, applicationId, }) {
44
+ function addProductFlavor({ client, }) {
32
45
  const gradlePath = path.join(process.cwd(), 'android/app/build.gradle');
33
46
  if (!fs.existsSync(gradlePath)) {
34
47
  console.warn(`build.gradle not found at ${gradlePath} — skipping`);
35
48
  return;
36
49
  }
37
50
  const gradle = fs.readFileSync(gradlePath, 'utf-8');
38
- // Already has this flavor
39
- const flavorRegex = new RegExp(`\\b${client}\\s*\\{`);
51
+ const flavorRegex = new RegExp(`^\\s+${client}\\s*$`, 'm');
40
52
  if (flavorRegex.test(gradle)) {
41
53
  console.log(`Flavor "${client}" already exists in build.gradle — skipping`);
42
54
  return;
43
55
  }
44
- const flavorBlock = ` ${client} {\n` +
45
- ` dimension "default"\n` +
46
- ` applicationId "${applicationId}"\n` +
47
- ` }`;
48
- const updated = insertIntoProductFlavors(gradle, flavorBlock);
56
+ const updated = insertIntoProductFlavors(gradle, client);
49
57
  fs.writeFileSync(gradlePath, updated, 'utf-8');
50
58
  console.log(`Added flavor "${client}" to android/app/build.gradle`);
51
59
  }
@@ -1,3 +1,3 @@
1
- export declare const prepareAppIcon: ({ iosAssetFolder }: any) => Promise<{
1
+ export declare const prepareAppIcon: ({ iosAssetFolder, androidAssetFolder }: any) => Promise<{
2
2
  appIconFile: string;
3
3
  }>;
@@ -5,29 +5,27 @@ const inquirer = require("inquirer");
5
5
  const path = require("path");
6
6
  const sharp = require("sharp");
7
7
  const common_1 = require("../common");
8
- exports.prepareAppIcon = async ({ iosAssetFolder }) => {
8
+ const android_icon_1 = require("./android-icon");
9
+ exports.prepareAppIcon = async ({ iosAssetFolder, androidAssetFolder }) => {
9
10
  const questions = [
10
- {
11
- type: 'confirm',
12
- name: 'android-icon',
13
- message: 'It is recommend to use Android Studio to create App icon and notification icon for android',
14
- },
15
11
  {
16
12
  type: 'input',
17
13
  name: 'appIconFile',
18
- message: 'App Icon file for iOS?',
14
+ message: 'App Icon file (1024x1024, used for iOS + Android)?',
19
15
  },
20
16
  ];
21
17
  const { appIconFile } = await inquirer.prompt(questions);
22
18
  if (appIconFile) {
19
+ const normalisedIcon = common_1.normalise(appIconFile);
23
20
  // Generate app icon for ios
24
21
  // Copy template files
25
22
  await common_1.copyDir('./template/AppIcon.appiconset', path.join(iosAssetFolder, 'AppIcon.appiconset'));
26
- await sharp(common_1.normalise(appIconFile))
23
+ await sharp(normalisedIcon)
27
24
  .resize(1024, 1024)
28
25
  .png()
29
26
  .removeAlpha()
30
27
  .toFile(path.join(iosAssetFolder, 'AppIcon.appiconset/ItunesArtwork@2x.png'));
28
+ await android_icon_1.generateAndroidIcons({ sourceFile: normalisedIcon, androidAssetFolder });
31
29
  }
32
30
  return { appIconFile: common_1.normalise(appIconFile) };
33
31
  };
@@ -1 +1,7 @@
1
- export declare const getAppleTeam: () => Promise<any>;
1
+ export declare const getAppleTeam: () => Promise<{
2
+ appleTeamId?: undefined;
3
+ itcTeamId?: undefined;
4
+ } | {
5
+ appleTeamId: any;
6
+ itcTeamId: any;
7
+ }>;
@@ -3,6 +3,35 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getAppleTeam = void 0;
4
4
  const inquirer = require("inquirer");
5
5
  const shell = require("shelljs");
6
+ function stripAnsi(input) {
7
+ // eslint-disable-next-line no-control-regex
8
+ return input.replace(/\x1b\[[0-9;]*m/g, '');
9
+ }
10
+ function parseTeamIds(output, sectionHeader) {
11
+ const startIdx = output.indexOf(sectionHeader);
12
+ if (startIdx === -1)
13
+ return [];
14
+ const rest = output.slice(startIdx + sectionHeader.length);
15
+ const nextSectionIdx = rest.indexOf('----');
16
+ const section = nextSectionIdx === -1 ? rest : rest.slice(0, nextSectionIdx);
17
+ const matches = [...section.matchAll(/([^\r\n(]+?)\s+\(([A-Za-z0-9]+)\)/g)];
18
+ return matches.map(m => ({ name: m[1].trim(), id: m[2] }));
19
+ }
20
+ async function resolveTeamId(candidates, label) {
21
+ if (candidates.length === 0)
22
+ return undefined;
23
+ if (candidates.length === 1)
24
+ return candidates[0].id;
25
+ const { chosen } = await inquirer.prompt([
26
+ {
27
+ type: 'list',
28
+ name: 'chosen',
29
+ message: `Multiple ${label} found — pick one:`,
30
+ choices: candidates.map(c => ({ name: `${c.name} (${c.id})`, value: c.id })),
31
+ },
32
+ ]);
33
+ return chosen;
34
+ }
6
35
  exports.getAppleTeam = async () => {
7
36
  const { account } = await inquirer.prompt([
8
37
  {
@@ -21,17 +50,35 @@ exports.getAppleTeam = async () => {
21
50
  message: 'What is Apple team name (using for searching)?',
22
51
  },
23
52
  ]);
24
- shell.exec(`fastlane get_team_names search:${team} id:${account}`);
25
- return inquirer.prompt([
26
- {
53
+ const result = shell.exec(`fastlane get_team_names search:${team} id:${account}`);
54
+ const output = stripAnsi(`${result.stdout}\n${result.stderr}`);
55
+ const devTeams = parseTeamIds(output, 'Developer teams for');
56
+ const itcTeams = parseTeamIds(output, 'Appstore Connect teams for');
57
+ const appleTeamId = await resolveTeamId(devTeams, 'Apple Developer teams');
58
+ const itcTeamId = await resolveTeamId(itcTeams, 'App Store Connect teams');
59
+ if (appleTeamId && itcTeamId) {
60
+ return { appleTeamId, itcTeamId };
61
+ }
62
+ const manualQuestions = [];
63
+ if (!appleTeamId) {
64
+ manualQuestions.push({
27
65
  type: 'input',
28
66
  name: 'appleTeamId',
29
67
  message: 'What is Developer Team Id?',
30
- },
31
- {
68
+ });
69
+ }
70
+ if (!itcTeamId) {
71
+ manualQuestions.push({
32
72
  type: 'input',
33
73
  name: 'itcTeamId',
34
74
  message: 'What is Appstore Connect Team Id?',
35
- },
36
- ]);
75
+ });
76
+ }
77
+ const manual = manualQuestions.length
78
+ ? await inquirer.prompt(manualQuestions)
79
+ : {};
80
+ return {
81
+ appleTeamId: appleTeamId || manual.appleTeamId,
82
+ itcTeamId: itcTeamId || manual.itcTeamId,
83
+ };
37
84
  };
@@ -24,7 +24,7 @@ exports.createClientConfig = async (client) => {
24
24
  default: `com.edular.${client}`,
25
25
  },
26
26
  ]);
27
- android_1.addProductFlavor({ client, applicationId });
27
+ android_1.addProductFlavor({ client });
28
28
  ios_scheme_1.createXcodeScheme({ client, bundleId });
29
29
  return { appName, bundleId, applicationId };
30
30
  };
@@ -53,14 +53,14 @@ exports.prepareLaunchScreen = async ({ iosAssetFolder, androidAssetFolder, fallb
53
53
  .png()
54
54
  .toFile(path.join(iosAssetFolder, 'BootSplashLogo.imageset/bootsplash_logo.png'));
55
55
  }
56
- else if (launchScreen === 'fullscreen') {
57
- if (inputFile) {
58
- // ios
59
- await common_1.copyDir('./template/BootSplashImage.imageset', path.join(iosAssetFolder, 'BootSplashImage.imageset'));
60
- await sharp(inputFile)
61
- .png()
62
- .toFile(path.join(iosAssetFolder, 'BootSplashImage.imageset/splashTablet.png'));
63
- }
56
+ }
57
+ else if (launchScreen === 'fullscreen') {
58
+ if (inputFile) {
59
+ // ios
60
+ await common_1.copyDir('./template/BootSplashImage.imageset', path.join(iosAssetFolder, 'BootSplashImage.imageset'));
61
+ await sharp(inputFile)
62
+ .png()
63
+ .toFile(path.join(iosAssetFolder, 'BootSplashImage.imageset/splashTablet.png'));
64
64
  }
65
65
  }
66
66
  // There is no full screen for android, so generate icon
@@ -1,3 +1,7 @@
1
- export declare const prepareOneSignal: () => Promise<any> & {
2
- ui: import("inquirer/lib/ui/prompt");
3
- };
1
+ export declare const prepareOneSignal: ({ client, fastlaneDir, }: {
2
+ client: string;
3
+ fastlaneDir: string;
4
+ }) => Promise<{
5
+ onesignal?: string | undefined;
6
+ onesignalRestApiKey?: string | undefined;
7
+ }>;
@@ -2,12 +2,71 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.prepareOneSignal = void 0;
4
4
  const inquirer = require("inquirer");
5
- exports.prepareOneSignal = () => {
6
- return inquirer.prompt([
5
+ const shell = require("shelljs");
6
+ const common_1 = require("../common");
7
+ function parseValue(output, key) {
8
+ var _a;
9
+ const match = output.match(new RegExp(`^${key}=(.*)$`, 'm'));
10
+ const value = (_a = match === null || match === void 0 ? void 0 : match[1]) === null || _a === void 0 ? void 0 : _a.trim();
11
+ return value ? value : undefined;
12
+ }
13
+ async function askAppIdManually() {
14
+ const { onesignal } = await inquirer.prompt([
7
15
  {
8
16
  type: 'input',
9
17
  name: 'onesignal',
10
18
  message: 'What is OneSignal App Id?',
11
19
  },
12
20
  ]);
21
+ return { onesignal, onesignalRestApiKey: undefined };
22
+ }
23
+ exports.prepareOneSignal = async ({ client, fastlaneDir, }) => {
24
+ const fastlaneConfigs = common_1.getFastlaneConfigs();
25
+ let authToken = fastlaneConfigs.onesignalAuthToken;
26
+ if (!authToken) {
27
+ const { inputToken } = await inquirer.prompt([
28
+ {
29
+ type: 'input',
30
+ name: 'inputToken',
31
+ message: 'OneSignal Organization Auth Token (add "onesignalAuthToken" to fastlane/configs.json to skip this prompt)?',
32
+ },
33
+ ]);
34
+ authToken = inputToken;
35
+ }
36
+ if (!authToken) {
37
+ return askAppIdManually();
38
+ }
39
+ const { autoCreate } = await inquirer.prompt([
40
+ {
41
+ type: 'confirm',
42
+ name: 'autoCreate',
43
+ message: `Create OneSignal app "${client}" + push certificate via fastlane now?`,
44
+ default: true,
45
+ },
46
+ ]);
47
+ if (!autoCreate) {
48
+ return askAppIdManually();
49
+ }
50
+ const { androidToken, androidGcmSenderId } = await inquirer.prompt([
51
+ {
52
+ type: 'input',
53
+ name: 'androidToken',
54
+ message: 'Android GCM key (optional)?',
55
+ },
56
+ {
57
+ type: 'input',
58
+ name: 'androidGcmSenderId',
59
+ message: 'Android GCM Sender Id (optional)?',
60
+ },
61
+ ]);
62
+ const parameters = common_1.buildKeyValuePairs(Object.assign(Object.assign({ auth_token: authToken }, (androidToken ? { android_token: androidToken } : {})), (androidGcmSenderId ? { android_gcm_sender_id: androidGcmSenderId } : {})));
63
+ // shell.exec('bundle install', { cwd: fastlaneDir } as any);
64
+ const result = shell.exec(`bundle exec fastlane sync_onesignal ${parameters.join(' ')}`, { cwd: fastlaneDir });
65
+ const output = `${result.stdout}\n${result.stderr}`;
66
+ const onesignal = parseValue(output, 'ONESIGNAL_APP_ID');
67
+ const onesignalRestApiKey = parseValue(output, 'ONESIGNAL_APP_AUTH_KEY');
68
+ if (!onesignal) {
69
+ console.warn('Could not parse OneSignal App Id from fastlane output — check the logs above and set ONESIGNAL_APP_ID manually if needed.');
70
+ }
71
+ return { onesignal, onesignalRestApiKey };
13
72
  };
@@ -1 +1 @@
1
- {"version":"0.6.71","commands":{"build":{"id":"build","description":"Build react native apps","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder build\nhello world from ./src/build.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Set client that you want to build app"},"target":{"name":"target","type":"option","char":"t","description":"Set the build target: android, ios or both","options":["android","ios","android,ios"]},"env":{"name":"env","type":"option","char":"e","description":"Set the environment: prod, staging, dev"},"branch":{"name":"branch","type":"option","char":"b","description":"Set the source branch used to build the app"},"version":{"name":"version","type":"option","char":"v","description":"Set the version number for this build"},"installr":{"name":"installr","type":"option","char":"i","description":"If enabled, the build will be uploaded to installR"},"distribute":{"name":"distribute","type":"option","char":"d","description":"Specify where to distribute app"},"firebase":{"name":"firebase","type":"boolean","char":"f","description":"If enabled, the build will be uploaded to firebase","allowNo":false},"testersIos":{"name":"testersIos","type":"option","char":"t","description":"Tester ids for iOS distribution"},"testersAndroid":{"name":"testersAndroid","type":"option","char":"u","description":"Tester ids for Android distribution"},"gitReset":{"name":"gitReset","type":"boolean","char":"g","description":"Do git reset when building","allowNo":false},"cleanup":{"name":"cleanup","type":"boolean","char":"s","description":"Do cleanup when building","allowNo":false},"groupsIos":{"name":"groupsIos","type":"option","description":"Groups ids for iOS distribution"},"groupsAndroid":{"name":"groupsAndroid","type":"option","description":"Groups ids for Android distribution"},"skipCheckVersion":{"name":"skipCheckVersion","type":"boolean","description":"Skip checking current version on stores","allowNo":false},"install":{"name":"install","type":"boolean","char":"n","description":"Install dependencies before building","allowNo":false}},"args":[{"name":"build"}]},"check-version":{"id":"check-version","description":"Check live versions","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder check-version\n Nuke certificates ./src/match-check.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Select client that you want to sync udids"}},"args":[{"name":"match-check"}]},"codepush":{"id":"codepush","description":"Push changes to existing apps","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder codepush\n Push changes to existing apps ./src/firebase-sync-udids.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Select client that you want to push update"}},"args":[{"name":"build"}]},"google-key":{"id":"google-key","description":"Nuke certificates","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder match-check\n Nuke certificates ./src/match-check.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Select client that you want to sync udids"}},"args":[{"name":"match-check"}]},"initialize":{"id":"initialize","description":"Initialize the environment for first run","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder initialize\ninitialize environment for first run from ./src/initialize.ts!\n"],"flags":{},"args":[]},"match-check":{"id":"match-check","description":"Check match certificates","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder match-check\n Check match certificates ./src/match-check.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Select client that you want to check match certificates"}},"args":[{"name":"match-check"}]},"match-nuke":{"id":"match-nuke","description":"Nuke certificates","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder match-nuke\n Nuke certificates ./src/match-nuke.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Select client that you want to sync udids"}},"args":[{"name":"build"}]},"onesignal":{"id":"onesignal","description":"Create and upload push cerfiticate to One Signal","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder onesignal\n Create and upload push certificate to One Signal from ./src/commands/onesignal.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Select client that you want to sync udids"},"auth_token":{"name":"auth_token","type":"option","description":"Auth token for OneSignal"},"android_token":{"name":"android_token","type":"option"},"android_gcm_sender_id":{"name":"android_gcm_sender_id","type":"option"}},"args":[{"name":"build"}]},"reject-review":{"id":"reject-review","description":"Reject current App Store review version (In Review or Pending Developer Release)","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder reject-review\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Select client to reject App Store review version"}},"args":[{"name":"reject-review"}]},"setup":{"id":"setup","description":"Setup new client","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder build\nhello world from ./src/setup.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[{"name":"setup"}]},"submit-review":{"id":"submit-review","description":"Submit ios for review","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder submit-review\n Push changes to existing apps ./src/firebase-sync-udids.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Select client that you want to push update"}},"args":[{"name":"submit-review"}]},"sync-udids":{"id":"sync-udids","description":"Sync device UDIDs from firebase","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder sync-udids\n Sync device UDIDs from firebase from ./src/firebase-sync-udids.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Select client that you want to sync udids"}},"args":[{"name":"build"}]}}}
1
+ {"version":"0.6.73","commands":{"build":{"id":"build","description":"Build react native apps","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder build\nhello world from ./src/build.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Set client that you want to build app"},"target":{"name":"target","type":"option","char":"t","description":"Set the build target: android, ios or both","options":["android","ios","android,ios"]},"env":{"name":"env","type":"option","char":"e","description":"Set the environment: prod, staging, dev"},"branch":{"name":"branch","type":"option","char":"b","description":"Set the source branch used to build the app"},"version":{"name":"version","type":"option","char":"v","description":"Set the version number for this build"},"installr":{"name":"installr","type":"option","char":"i","description":"If enabled, the build will be uploaded to installR"},"distribute":{"name":"distribute","type":"option","char":"d","description":"Specify where to distribute app"},"firebase":{"name":"firebase","type":"boolean","char":"f","description":"If enabled, the build will be uploaded to firebase","allowNo":false},"testersIos":{"name":"testersIos","type":"option","char":"t","description":"Tester ids for iOS distribution"},"testersAndroid":{"name":"testersAndroid","type":"option","char":"u","description":"Tester ids for Android distribution"},"gitReset":{"name":"gitReset","type":"boolean","char":"g","description":"Do git reset when building","allowNo":false},"cleanup":{"name":"cleanup","type":"boolean","char":"s","description":"Do cleanup when building","allowNo":false},"groupsIos":{"name":"groupsIos","type":"option","description":"Groups ids for iOS distribution"},"groupsAndroid":{"name":"groupsAndroid","type":"option","description":"Groups ids for Android distribution"},"skipCheckVersion":{"name":"skipCheckVersion","type":"boolean","description":"Skip checking current version on stores","allowNo":false},"install":{"name":"install","type":"boolean","char":"n","description":"Install dependencies before building","allowNo":false}},"args":[{"name":"build"}]},"check-version":{"id":"check-version","description":"Check live versions","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder check-version\n Nuke certificates ./src/match-check.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Select client that you want to sync udids"}},"args":[{"name":"match-check"}]},"codepush":{"id":"codepush","description":"Push changes to existing apps","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder codepush\n Push changes to existing apps ./src/firebase-sync-udids.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Select client that you want to push update"}},"args":[{"name":"build"}]},"google-key":{"id":"google-key","description":"Nuke certificates","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder match-check\n Nuke certificates ./src/match-check.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Select client that you want to sync udids"}},"args":[{"name":"match-check"}]},"initialize":{"id":"initialize","description":"Initialize the environment for first run","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder initialize\ninitialize environment for first run from ./src/initialize.ts!\n"],"flags":{},"args":[]},"match-check":{"id":"match-check","description":"Check match certificates","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder match-check\n Check match certificates ./src/match-check.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Select client that you want to check match certificates"}},"args":[{"name":"match-check"}]},"match-nuke":{"id":"match-nuke","description":"Nuke certificates","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder match-nuke\n Nuke certificates ./src/match-nuke.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Select client that you want to sync udids"}},"args":[{"name":"build"}]},"onesignal":{"id":"onesignal","description":"Create and upload push cerfiticate to One Signal","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder onesignal\n Create and upload push certificate to One Signal from ./src/commands/onesignal.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Select client that you want to sync udids"},"auth_token":{"name":"auth_token","type":"option","description":"Auth token for OneSignal"},"android_token":{"name":"android_token","type":"option"},"android_gcm_sender_id":{"name":"android_gcm_sender_id","type":"option"}},"args":[{"name":"build"}]},"reject-review":{"id":"reject-review","description":"Reject current App Store review version (In Review or Pending Developer Release)","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder reject-review\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Select client to reject App Store review version"}},"args":[{"name":"reject-review"}]},"setup-gcp":{"id":"setup-gcp","description":"Setup GCP service account impersonation","pluginName":"rnbuilder","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"setup":{"id":"setup","description":"Setup new client","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder build\nhello world from ./src/setup.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[{"name":"setup"}]},"submit-review":{"id":"submit-review","description":"Submit ios for review","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder submit-review\n Push changes to existing apps ./src/firebase-sync-udids.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Select client that you want to push update"}},"args":[{"name":"submit-review"}]},"sync-udids":{"id":"sync-udids","description":"Sync device UDIDs from firebase","pluginName":"rnbuilder","pluginType":"core","aliases":[],"examples":["$ rnbuilder sync-udids\n Sync device UDIDs from firebase from ./src/firebase-sync-udids.ts!\n"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false},"client":{"name":"client","type":"option","char":"c","description":"Select client that you want to sync udids"}},"args":[{"name":"build"}]}}}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rnbuilder",
3
3
  "description": "React Native builder tool",
4
- "version": "0.6.71",
4
+ "version": "0.6.73",
5
5
  "author": "Tuan Luong @r0b0t3d",
6
6
  "bin": "./bin/run",
7
7
  "bugs": "https://github.com/r0b0t3d/rnbuilder/issues",
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if ! command -v gcloud >/dev/null 2>&1; then
5
+ echo "gcloud is not installed. Install Google Cloud SDK first."
6
+ exit 1
7
+ fi
8
+
9
+ if ! command -v python3 >/dev/null 2>&1; then
10
+ echo "python3 is required but not found."
11
+ exit 1
12
+ fi
13
+
14
+ echo "Available projects:"
15
+ gcloud projects list --format="table(projectId,name)" 2>/dev/null || echo " (could not list projects)"
16
+ echo ""
17
+
18
+ DEFAULT_PROJECT_ID="${GCP_PROJECT_ID:-edular-19fe4}"
19
+ read -rp "Enter Google Cloud project ID [$DEFAULT_PROJECT_ID]: " PROJECT_ID
20
+ PROJECT_ID="${PROJECT_ID:-$DEFAULT_PROJECT_ID}"
21
+
22
+ DEFAULT_USER_EMAIL="${GCP_USER_EMAIL:-klassapp2013@gmail.com}"
23
+ read -rp "Enter your Google account email [$DEFAULT_USER_EMAIL]: " USER_EMAIL
24
+ USER_EMAIL="${USER_EMAIL:-$DEFAULT_USER_EMAIL}"
25
+
26
+ gcloud config set project "$PROJECT_ID"
27
+
28
+ echo ""
29
+ echo "Logging into gcloud as $USER_EMAIL..."
30
+ gcloud auth login "$USER_EMAIL"
31
+
32
+ echo ""
33
+ echo "Creating base ADC (user credentials, no impersonation)..."
34
+ gcloud auth application-default login
35
+
36
+ BASE_ADC="$HOME/.config/gcloud/application_default_credentials.json"
37
+ ADC_DIR="$HOME/.config/gcloud"
38
+
39
+ # Generate an impersonated ADC file for a service account.
40
+ # Reuses the base user credentials — no extra gcloud login needed.
41
+ generate_sa_adc() {
42
+ local sa_email="$1"
43
+ local sa_slug
44
+ sa_slug=$(printf '%s' "$sa_email" | cut -d'@' -f1 | tr -cs '[:alnum:]' '_' | sed 's/_*$//')
45
+ local output="$ADC_DIR/adc_${sa_slug}.json"
46
+
47
+ echo ""
48
+ echo "Granting roles/iam.serviceAccountTokenCreator to $USER_EMAIL for $sa_email..."
49
+ # Unset gcloud impersonation so the grant runs as the user, not as an SA
50
+ gcloud config unset auth/impersonate_service_account 2>/dev/null || true
51
+ gcloud iam service-accounts add-iam-policy-binding \
52
+ "$sa_email" \
53
+ --role="roles/iam.serviceAccountTokenCreator" \
54
+ --member="user:$USER_EMAIL" \
55
+ --project="$PROJECT_ID"
56
+
57
+ python3 - "$BASE_ADC" "$sa_email" "$output" <<'PYEOF'
58
+ import json, sys
59
+
60
+ base_path, sa_email, output_path = sys.argv[1], sys.argv[2], sys.argv[3]
61
+
62
+ with open(base_path) as f:
63
+ base = json.load(f)
64
+
65
+ # If base is already an impersonated ADC, extract its source_credentials.
66
+ # Otherwise use base directly as the source (authorized_user).
67
+ src = base.get("source_credentials", base)
68
+
69
+ adc = {
70
+ "delegates": [],
71
+ "service_account_impersonation_url": (
72
+ "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/"
73
+ f"{sa_email}:generateAccessToken"
74
+ ),
75
+ "source_credentials": src,
76
+ "type": "impersonated_service_account",
77
+ }
78
+
79
+ with open(output_path, "w") as f:
80
+ json.dump(adc, f, indent=2)
81
+
82
+ print(f"Created: {output_path}")
83
+ print(f"Add to client .env:")
84
+ print(f" GOOGLE_APPLICATION_CREDENTIALS={output_path}")
85
+ PYEOF
86
+ }
87
+
88
+ # Service accounts from repo's fastlane/configs.json (gcp.serviceAccounts), if provided.
89
+ if [ -n "${GCP_SERVICE_ACCOUNTS:-}" ]; then
90
+ echo ""
91
+ echo "Service accounts from fastlane/configs.json:"
92
+ IFS=',' read -ra CONFIG_SAS <<< "$GCP_SERVICE_ACCOUNTS"
93
+ for sa in "${CONFIG_SAS[@]}"; do
94
+ echo " - $sa"
95
+ done
96
+ for sa in "${CONFIG_SAS[@]}"; do
97
+ generate_sa_adc "$sa"
98
+ done
99
+ else
100
+ read -rp "Enter service account email [fastlane@edular-19fe4.iam.gserviceaccount.com]: " SERVICE_ACCOUNT_EMAIL
101
+ SERVICE_ACCOUNT_EMAIL="${SERVICE_ACCOUNT_EMAIL:-fastlane@edular-19fe4.iam.gserviceaccount.com}"
102
+ generate_sa_adc "$SERVICE_ACCOUNT_EMAIL"
103
+ fi
104
+
105
+ # Additional service accounts
106
+ while true; do
107
+ echo ""
108
+ read -rp "Add another service account? (y/N): " ADD_MORE
109
+ case "${ADD_MORE:-N}" in
110
+ [Yy]*)
111
+ read -rp "Enter service account email: " EXTRA_SA
112
+ if [ -n "${EXTRA_SA:-}" ]; then
113
+ generate_sa_adc "$EXTRA_SA"
114
+ fi
115
+ ;;
116
+ *) break ;;
117
+ esac
118
+ done
119
+
120
+ echo ""
121
+ echo "Done."
122
+ echo ""
123
+ echo "To test a service account token:"
124
+ echo " gcloud auth print-access-token --impersonate-service-account=<SA_EMAIL>"
125
+ echo ""
126
+ echo "ADC files are in: $ADC_DIR/adc_*.json"
127
+ echo "Each client .env needs:"
128
+ echo " GOOGLE_APPLICATION_CREDENTIALS=<path to their SA's adc file>"