react-native-3rddigital-appupdate 1.0.11 → 1.0.13
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/package.json +1 -1
- package/scripts/bundle.js +750 -152
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-3rddigital-appupdate",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "A React Native library for seamless over-the-air (OTA) updates with version checks, automatic bundle download, and customizable user prompts for iOS and Android.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
package/scripts/bundle.js
CHANGED
|
@@ -15,17 +15,14 @@ const APPUPDATE_AWS_SECRET_ACCESS_KEY =
|
|
|
15
15
|
process.env.APPUPDATE_AWS_SECRET_ACCESS_KEY;
|
|
16
16
|
const APPUPDATE_AWS_BUCKET_NAME = process.env.APPUPDATE_AWS_BUCKET_NAME;
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
* Decrypt helper logic
|
|
20
|
-
*/
|
|
21
18
|
function DecriptEnv(wrappedKey) {
|
|
22
19
|
if (!wrappedKey) return '';
|
|
23
20
|
if (typeof wrappedKey !== 'string')
|
|
24
21
|
throw new TypeError('wrappedKey must be a string');
|
|
25
22
|
if (wrappedKey.length <= 8) throw new Error('wrappedKey too short to unwrap');
|
|
23
|
+
|
|
26
24
|
const trimmed = wrappedKey.slice(4, -2);
|
|
27
|
-
|
|
28
|
-
return result;
|
|
25
|
+
return trimmed.slice(0, 2) + trimmed.slice(4);
|
|
29
26
|
}
|
|
30
27
|
|
|
31
28
|
const s3Client = new S3Client({
|
|
@@ -36,9 +33,17 @@ const s3Client = new S3Client({
|
|
|
36
33
|
},
|
|
37
34
|
});
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
function run(command, cwd = process.cwd()) {
|
|
37
|
+
try {
|
|
38
|
+
console.log(`\n➡️ Running: ${command}\n`);
|
|
39
|
+
execSync(command, { stdio: 'inherit', cwd });
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error(`❌ Command failed: ${command}`);
|
|
42
|
+
console.error(err.message);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
42
47
|
async function uploadFileToS3(filePath, bucketName, folder) {
|
|
43
48
|
const fileName = path.basename(filePath);
|
|
44
49
|
const cleanFileName = fileName.replace(/\s+/g, '_');
|
|
@@ -67,24 +72,6 @@ async function uploadFileToS3(filePath, bucketName, folder) {
|
|
|
67
72
|
}
|
|
68
73
|
}
|
|
69
74
|
|
|
70
|
-
/**
|
|
71
|
-
* Run a shell command synchronously.
|
|
72
|
-
*/
|
|
73
|
-
function run(command) {
|
|
74
|
-
try {
|
|
75
|
-
console.log(`\n➡️ Running: ${command}\n`);
|
|
76
|
-
execSync(command, { stdio: 'inherit' });
|
|
77
|
-
} catch (err) {
|
|
78
|
-
console.error(`❌ Command failed: ${command}`);
|
|
79
|
-
console.error(err.message);
|
|
80
|
-
process.exit(1);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Step 1: Upload to S3
|
|
86
|
-
* Step 2: Register with Backend
|
|
87
|
-
*/
|
|
88
75
|
async function uploadBundle({ filePath, platform, config }) {
|
|
89
76
|
console.log(`📤 Starting upload process for ${platform}...`);
|
|
90
77
|
|
|
@@ -94,7 +81,6 @@ async function uploadBundle({ filePath, platform, config }) {
|
|
|
94
81
|
}
|
|
95
82
|
|
|
96
83
|
try {
|
|
97
|
-
// 1. Upload to S3
|
|
98
84
|
const s3Result = await uploadFileToS3(
|
|
99
85
|
filePath,
|
|
100
86
|
DecriptEnv(APPUPDATE_AWS_BUCKET_NAME),
|
|
@@ -105,12 +91,11 @@ async function uploadBundle({ filePath, platform, config }) {
|
|
|
105
91
|
|
|
106
92
|
console.log(`✅ S3 Upload Complete: ${s3Result.Key}`);
|
|
107
93
|
|
|
108
|
-
// 2. Prepare Payload for Backend
|
|
109
94
|
const stats = fs.statSync(filePath);
|
|
110
95
|
const payload = {
|
|
111
96
|
projectId: config.PROJECT_ID,
|
|
112
97
|
environment: config.ENVIRONMENT,
|
|
113
|
-
platform
|
|
98
|
+
platform,
|
|
114
99
|
version: config.VERSION,
|
|
115
100
|
forceUpdate: config.FORCE_UPDATE,
|
|
116
101
|
s3Key: s3Result.Key,
|
|
@@ -142,151 +127,668 @@ async function uploadBundle({ filePath, platform, config }) {
|
|
|
142
127
|
}
|
|
143
128
|
}
|
|
144
129
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
130
|
+
function getProjectRoot() {
|
|
131
|
+
const cwdPackageJson = path.join(process.cwd(), 'package.json');
|
|
132
|
+
if (fs.existsSync(cwdPackageJson)) {
|
|
133
|
+
return process.cwd();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let projectRoot = path.resolve(__dirname);
|
|
137
|
+
while (
|
|
138
|
+
projectRoot.includes('node_modules') &&
|
|
139
|
+
!fs.existsSync(path.join(projectRoot, 'package.json'))
|
|
140
|
+
) {
|
|
141
|
+
projectRoot = path.resolve(projectRoot, '..');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (projectRoot.includes('node_modules')) {
|
|
145
|
+
projectRoot = path.resolve(projectRoot, '../../');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return projectRoot;
|
|
161
149
|
}
|
|
162
150
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
*/
|
|
166
|
-
function buildIOS() {
|
|
167
|
-
console.log('📦 Building iOS bundle...');
|
|
168
|
-
const outputPath = path.join('ios', 'main.jsbundle.zip');
|
|
169
|
-
run(
|
|
170
|
-
`mkdir -p ios/output && ` +
|
|
171
|
-
`react-native bundle --platform ios --dev false --entry-file index.js ` +
|
|
172
|
-
`--bundle-output ios/output/main.jsbundle --assets-dest ios/output ` +
|
|
173
|
-
`--sourcemap-output ios/sourcemap.js && ` +
|
|
174
|
-
`cd ios && find output -type f | zip main.jsbundle.zip -@ && ` +
|
|
175
|
-
`zip sourcemap.zip sourcemap.js && cd .. && rm -rf ios/output && rm -rf ios/sourcemap.js`
|
|
176
|
-
);
|
|
177
|
-
console.log(`✅ iOS bundle created at ${outputPath}`);
|
|
178
|
-
return outputPath;
|
|
151
|
+
function findFirstExistingPath(possiblePaths) {
|
|
152
|
+
return possiblePaths.find((item) => fs.existsSync(item)) ?? null;
|
|
179
153
|
}
|
|
180
154
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
projectRoot = path.resolve(projectRoot, '..');
|
|
155
|
+
function findFirstXcodeProj(dir) {
|
|
156
|
+
const files = fs.readdirSync(dir);
|
|
157
|
+
|
|
158
|
+
for (const file of files) {
|
|
159
|
+
const fullPath = path.join(dir, file);
|
|
160
|
+
const stat = fs.statSync(fullPath);
|
|
161
|
+
|
|
162
|
+
if (stat.isDirectory()) {
|
|
163
|
+
if (file.endsWith('.xcodeproj')) return fullPath;
|
|
164
|
+
const nested = findFirstXcodeProj(fullPath);
|
|
165
|
+
if (nested) return nested;
|
|
193
166
|
}
|
|
167
|
+
}
|
|
194
168
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function walkFiles(dir, matcher, result = []) {
|
|
173
|
+
if (!fs.existsSync(dir)) return result;
|
|
174
|
+
|
|
175
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
176
|
+
for (const entry of entries) {
|
|
177
|
+
const fullPath = path.join(dir, entry.name);
|
|
178
|
+
if (entry.isDirectory()) {
|
|
179
|
+
walkFiles(fullPath, matcher, result);
|
|
180
|
+
continue;
|
|
198
181
|
}
|
|
199
182
|
|
|
200
|
-
if (
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
183
|
+
if (matcher(fullPath)) {
|
|
184
|
+
result.push(fullPath);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function extractBracedBlock(content, startIndex) {
|
|
192
|
+
const openIndex = content.indexOf('{', startIndex);
|
|
193
|
+
if (openIndex === -1) return null;
|
|
194
|
+
|
|
195
|
+
let depth = 0;
|
|
196
|
+
let inSingleQuote = false;
|
|
197
|
+
let inDoubleQuote = false;
|
|
198
|
+
let inTemplate = false;
|
|
199
|
+
let inLineComment = false;
|
|
200
|
+
let inBlockComment = false;
|
|
201
|
+
|
|
202
|
+
for (let index = openIndex; index < content.length; index += 1) {
|
|
203
|
+
const char = content[index];
|
|
204
|
+
const nextChar = content[index + 1];
|
|
205
|
+
const prevChar = content[index - 1];
|
|
206
|
+
|
|
207
|
+
if (inLineComment) {
|
|
208
|
+
if (char === '\n') inLineComment = false;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (inBlockComment) {
|
|
213
|
+
if (prevChar === '*' && char === '/') inBlockComment = false;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!inSingleQuote && !inDoubleQuote && !inTemplate) {
|
|
218
|
+
if (char === '/' && nextChar === '/') {
|
|
219
|
+
inLineComment = true;
|
|
220
|
+
index += 1;
|
|
221
|
+
continue;
|
|
217
222
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
+
|
|
224
|
+
if (char === '/' && nextChar === '*') {
|
|
225
|
+
inBlockComment = true;
|
|
226
|
+
index += 1;
|
|
227
|
+
continue;
|
|
223
228
|
}
|
|
229
|
+
}
|
|
224
230
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
231
|
+
if (!inDoubleQuote && !inTemplate && char === "'" && prevChar !== '\\') {
|
|
232
|
+
inSingleQuote = !inSingleQuote;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
228
235
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
236
|
+
if (!inSingleQuote && !inTemplate && char === '"' && prevChar !== '\\') {
|
|
237
|
+
inDoubleQuote = !inDoubleQuote;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (!inSingleQuote && !inDoubleQuote && char === '`' && prevChar !== '\\') {
|
|
242
|
+
inTemplate = !inTemplate;
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (inSingleQuote || inDoubleQuote || inTemplate) continue;
|
|
247
|
+
|
|
248
|
+
if (char === '{') depth += 1;
|
|
249
|
+
if (char === '}') {
|
|
250
|
+
depth -= 1;
|
|
251
|
+
if (depth === 0) {
|
|
252
|
+
return {
|
|
253
|
+
content: content.slice(openIndex + 1, index),
|
|
254
|
+
start: openIndex,
|
|
255
|
+
end: index,
|
|
256
|
+
};
|
|
232
257
|
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function extractNamedBlock(content, blockName) {
|
|
265
|
+
const blockRegex = new RegExp(`\\b${blockName}\\b\\s*\\{`, 'm');
|
|
266
|
+
const match = blockRegex.exec(content);
|
|
267
|
+
if (!match) return null;
|
|
268
|
+
return extractBracedBlock(content, match.index);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function parseTopLevelNamedBlocks(content) {
|
|
272
|
+
const blocks = [];
|
|
273
|
+
let cursor = 0;
|
|
274
|
+
|
|
275
|
+
while (cursor < content.length) {
|
|
276
|
+
const remainder = content.slice(cursor);
|
|
277
|
+
const nameMatch = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*\{/.exec(remainder);
|
|
278
|
+
|
|
279
|
+
if (!nameMatch) {
|
|
280
|
+
cursor += 1;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const nameIndex = cursor + nameMatch.index;
|
|
285
|
+
const name = nameMatch[1];
|
|
286
|
+
const block = extractBracedBlock(content, nameIndex);
|
|
287
|
+
|
|
288
|
+
if (!block) break;
|
|
289
|
+
|
|
290
|
+
blocks.push({ name, content: block.content });
|
|
291
|
+
cursor = block.end + 1;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return blocks;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function readQuotedGradleValue(blockContent, key) {
|
|
298
|
+
const match = blockContent.match(
|
|
299
|
+
new RegExp(`\\b${key}\\b\\s+["']([^"']+)["']`)
|
|
300
|
+
);
|
|
301
|
+
return match?.[1] ?? null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function cleanPbxString(value) {
|
|
305
|
+
if (!value) return null;
|
|
306
|
+
return value.replace(/^"(.*)"$/, '$1').trim();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function readPbxValue(body, key) {
|
|
310
|
+
const match = body.match(new RegExp(`\\b${key}\\s*=\\s*([^;]+);`));
|
|
311
|
+
return match?.[1]?.trim() ?? null;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function parsePbxArray(body, key) {
|
|
315
|
+
const match = body.match(new RegExp(`\\b${key}\\s*=\\s*\\(([\\s\\S]*?)\\);`));
|
|
316
|
+
if (!match) return [];
|
|
317
|
+
|
|
318
|
+
return match[1]
|
|
319
|
+
.split('\n')
|
|
320
|
+
.map((line) => line.trim())
|
|
321
|
+
.filter(Boolean)
|
|
322
|
+
.map((line) => line.replace(/,$/, ''))
|
|
323
|
+
.map((line) => {
|
|
324
|
+
const idMatch = line.match(/^([A-F0-9]{24})/);
|
|
325
|
+
return idMatch?.[1] ?? null;
|
|
326
|
+
})
|
|
327
|
+
.filter(Boolean);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function parsePbxprojObjectsByIsa(pbxprojContent, isa) {
|
|
331
|
+
const objectRegex = new RegExp(
|
|
332
|
+
`([A-F0-9]{24}) /\\* ([^*]+) \\*/ = \\{[\\s\\S]*?isa = ${isa};([\\s\\S]*?)\\n\\s*\\};`,
|
|
333
|
+
'g'
|
|
334
|
+
);
|
|
335
|
+
const objects = [];
|
|
336
|
+
let match;
|
|
337
|
+
|
|
338
|
+
while ((match = objectRegex.exec(pbxprojContent)) !== null) {
|
|
339
|
+
objects.push({
|
|
340
|
+
id: match[1],
|
|
341
|
+
comment: match[2].trim(),
|
|
342
|
+
body: match[3],
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return objects;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function isAppLikeTarget(targetName, productType) {
|
|
350
|
+
if (productType?.includes('application')) return true;
|
|
351
|
+
|
|
352
|
+
return ![
|
|
353
|
+
'Tests',
|
|
354
|
+
'UITests',
|
|
355
|
+
'UnitTests',
|
|
356
|
+
'NotificationService',
|
|
357
|
+
'Extension',
|
|
358
|
+
'Widget',
|
|
359
|
+
].some((suffix) => targetName.endsWith(suffix));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function getAndroidBuildGradlePath(projectRoot) {
|
|
363
|
+
return findFirstExistingPath([
|
|
364
|
+
path.join(projectRoot, 'android', 'app', 'build.gradle'),
|
|
365
|
+
path.join(projectRoot, 'android', 'app', 'build.gradle.kts'),
|
|
366
|
+
]);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function getAndroidProjectMetadata() {
|
|
370
|
+
const projectRoot = getProjectRoot();
|
|
371
|
+
const gradlePath = getAndroidBuildGradlePath(projectRoot);
|
|
372
|
+
if (!gradlePath) {
|
|
373
|
+
console.warn('⚠️ Android build.gradle not found.');
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const gradleContent = fs.readFileSync(gradlePath, 'utf8');
|
|
378
|
+
const defaultConfigBlock = extractNamedBlock(gradleContent, 'defaultConfig');
|
|
379
|
+
const productFlavorsBlock = extractNamedBlock(
|
|
380
|
+
gradleContent,
|
|
381
|
+
'productFlavors'
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
const defaultAppId =
|
|
385
|
+
readQuotedGradleValue(defaultConfigBlock?.content ?? '', 'applicationId') ??
|
|
386
|
+
null;
|
|
387
|
+
const defaultVersion =
|
|
388
|
+
readQuotedGradleValue(defaultConfigBlock?.content ?? '', 'versionName') ??
|
|
389
|
+
null;
|
|
390
|
+
|
|
391
|
+
const flavors = parseTopLevelNamedBlocks(
|
|
392
|
+
productFlavorsBlock?.content ?? ''
|
|
393
|
+
).map(({ name, content }) => {
|
|
394
|
+
const flavorAppId = readQuotedGradleValue(content, 'applicationId');
|
|
395
|
+
const flavorAppIdSuffix = readQuotedGradleValue(
|
|
396
|
+
content,
|
|
397
|
+
'applicationIdSuffix'
|
|
398
|
+
);
|
|
399
|
+
const flavorVersion = readQuotedGradleValue(content, 'versionName');
|
|
400
|
+
const flavorVersionSuffix = readQuotedGradleValue(
|
|
401
|
+
content,
|
|
402
|
+
'versionNameSuffix'
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
name,
|
|
407
|
+
label: name,
|
|
408
|
+
appId: flavorAppId ?? `${defaultAppId ?? ''}${flavorAppIdSuffix ?? ''}`,
|
|
409
|
+
version:
|
|
410
|
+
flavorVersion ??
|
|
411
|
+
(defaultVersion
|
|
412
|
+
? `${defaultVersion}${flavorVersionSuffix ?? ''}`
|
|
413
|
+
: null),
|
|
414
|
+
};
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
defaultConfig: {
|
|
419
|
+
name: 'default',
|
|
420
|
+
label: 'Default',
|
|
421
|
+
appId: defaultAppId,
|
|
422
|
+
version: defaultVersion,
|
|
423
|
+
},
|
|
424
|
+
flavors,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async function getAndroidFlavorSelection() {
|
|
429
|
+
const metadata = getAndroidProjectMetadata();
|
|
430
|
+
if (!metadata) return null;
|
|
431
|
+
|
|
432
|
+
if (!metadata.flavors.length) {
|
|
433
|
+
return metadata.defaultConfig;
|
|
434
|
+
}
|
|
233
435
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
436
|
+
let selectedFlavor;
|
|
437
|
+
let isFlavorConfirmed = false;
|
|
438
|
+
|
|
439
|
+
while (!isFlavorConfirmed) {
|
|
440
|
+
selectedFlavor = await select({
|
|
441
|
+
message: 'Select Android flavor:',
|
|
442
|
+
choices: [
|
|
443
|
+
{
|
|
444
|
+
name: `Default (${metadata.defaultConfig.appId ?? 'unknown app id'} / ${metadata.defaultConfig.version ?? 'unknown version'})`,
|
|
445
|
+
value: metadata.defaultConfig,
|
|
446
|
+
},
|
|
447
|
+
...metadata.flavors.map((flavor) => ({
|
|
448
|
+
name: `${flavor.name} (${flavor.appId ?? 'unknown app id'} / ${flavor.version ?? 'unknown version'})`,
|
|
449
|
+
value: flavor,
|
|
450
|
+
})),
|
|
451
|
+
],
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
isFlavorConfirmed = await confirm({
|
|
455
|
+
message: `Continue with Android flavor ${selectedFlavor.label ?? selectedFlavor.name}?`,
|
|
456
|
+
default: true,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return selectedFlavor;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function getIosProjectFiles() {
|
|
464
|
+
const projectRoot = getProjectRoot();
|
|
465
|
+
const iosDir = path.join(projectRoot, 'ios');
|
|
466
|
+
if (!fs.existsSync(iosDir)) {
|
|
467
|
+
console.warn(`⚠️ iOS folder not found at ${iosDir}`);
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const xcodeProjPath = findFirstXcodeProj(iosDir);
|
|
472
|
+
if (!xcodeProjPath) {
|
|
473
|
+
console.warn('⚠️ .xcodeproj not found inside ios directory.');
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const pbxprojPath = path.join(xcodeProjPath, 'project.pbxproj');
|
|
478
|
+
if (!fs.existsSync(pbxprojPath)) {
|
|
479
|
+
console.warn('⚠️ project.pbxproj not found.');
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return { iosDir, xcodeProjPath, pbxprojPath };
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function getIosTargetMetadata() {
|
|
487
|
+
const projectFiles = getIosProjectFiles();
|
|
488
|
+
if (!projectFiles) return null;
|
|
489
|
+
|
|
490
|
+
const pbxprojContent = fs.readFileSync(projectFiles.pbxprojPath, 'utf8');
|
|
491
|
+
const configObjects = parsePbxprojObjectsByIsa(
|
|
492
|
+
pbxprojContent,
|
|
493
|
+
'XCBuildConfiguration'
|
|
494
|
+
);
|
|
495
|
+
const configMap = new Map(
|
|
496
|
+
configObjects.map((config) => [
|
|
497
|
+
config.id,
|
|
498
|
+
{
|
|
499
|
+
name: cleanPbxString(readPbxValue(config.body, 'name')),
|
|
500
|
+
version: cleanPbxString(readPbxValue(config.body, 'MARKETING_VERSION')),
|
|
501
|
+
appId: cleanPbxString(
|
|
502
|
+
readPbxValue(config.body, 'PRODUCT_BUNDLE_IDENTIFIER')
|
|
503
|
+
),
|
|
504
|
+
productName: cleanPbxString(readPbxValue(config.body, 'PRODUCT_NAME')),
|
|
505
|
+
},
|
|
506
|
+
])
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
const configListObjects = parsePbxprojObjectsByIsa(
|
|
510
|
+
pbxprojContent,
|
|
511
|
+
'XCConfigurationList'
|
|
512
|
+
);
|
|
513
|
+
const configListMap = new Map(
|
|
514
|
+
configListObjects.map((configList) => [
|
|
515
|
+
configList.id,
|
|
516
|
+
{
|
|
517
|
+
defaultName: cleanPbxString(
|
|
518
|
+
readPbxValue(configList.body, 'defaultConfigurationName')
|
|
519
|
+
),
|
|
520
|
+
buildConfigurations: parsePbxArray(
|
|
521
|
+
configList.body,
|
|
522
|
+
'buildConfigurations'
|
|
523
|
+
),
|
|
524
|
+
},
|
|
525
|
+
])
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
const targetObjects = parsePbxprojObjectsByIsa(
|
|
529
|
+
pbxprojContent,
|
|
530
|
+
'PBXNativeTarget'
|
|
531
|
+
);
|
|
532
|
+
const targets = targetObjects
|
|
533
|
+
.map((target) => {
|
|
534
|
+
const targetName = cleanPbxString(readPbxValue(target.body, 'name'));
|
|
535
|
+
const productType = cleanPbxString(
|
|
536
|
+
readPbxValue(target.body, 'productType')
|
|
537
|
+
);
|
|
538
|
+
const configListId = cleanPbxString(
|
|
539
|
+
readPbxValue(target.body, 'buildConfigurationList')
|
|
540
|
+
)?.match(/^([A-F0-9]{24})/)?.[1];
|
|
541
|
+
|
|
542
|
+
if (
|
|
543
|
+
!targetName ||
|
|
544
|
+
!configListId ||
|
|
545
|
+
!isAppLikeTarget(targetName, productType)
|
|
546
|
+
) {
|
|
237
547
|
return null;
|
|
238
548
|
}
|
|
239
549
|
|
|
240
|
-
const
|
|
241
|
-
const
|
|
550
|
+
const configList = configListMap.get(configListId);
|
|
551
|
+
const buildConfigs =
|
|
552
|
+
configList?.buildConfigurations
|
|
553
|
+
.map((configId) => configMap.get(configId))
|
|
554
|
+
.filter(Boolean) ?? [];
|
|
242
555
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
556
|
+
const configsByName = new Map(
|
|
557
|
+
buildConfigs
|
|
558
|
+
.filter((config) => config.name)
|
|
559
|
+
.map((config) => [config.name, config])
|
|
560
|
+
);
|
|
561
|
+
|
|
562
|
+
const preferredConfig =
|
|
563
|
+
buildConfigs.find(
|
|
564
|
+
(config) => config.name === configList?.defaultName
|
|
565
|
+
) ??
|
|
566
|
+
buildConfigs.find((config) => config.name === 'Release') ??
|
|
567
|
+
buildConfigs[0];
|
|
568
|
+
|
|
569
|
+
if (!preferredConfig) return null;
|
|
570
|
+
|
|
571
|
+
return {
|
|
572
|
+
name: targetName,
|
|
573
|
+
label: targetName,
|
|
574
|
+
appId: preferredConfig.appId ?? null,
|
|
575
|
+
version: preferredConfig.version ?? null,
|
|
576
|
+
productName: preferredConfig.productName ?? targetName,
|
|
577
|
+
buildConfiguration: preferredConfig.name ?? null,
|
|
578
|
+
configsByName,
|
|
579
|
+
};
|
|
580
|
+
})
|
|
581
|
+
.filter(Boolean);
|
|
582
|
+
|
|
583
|
+
const uniqueTargets = targets.filter(
|
|
584
|
+
(target, index, allTargets) =>
|
|
585
|
+
allTargets.findIndex((candidate) => candidate.name === target.name) ===
|
|
586
|
+
index
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
return {
|
|
590
|
+
projectFiles,
|
|
591
|
+
defaultConfig: uniqueTargets[0] ?? null,
|
|
592
|
+
targets: uniqueTargets,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function parseSchemeFile(filePath) {
|
|
597
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
598
|
+
const schemeName = path.basename(filePath, '.xcscheme');
|
|
599
|
+
|
|
600
|
+
const blueprintName =
|
|
601
|
+
content.match(/BlueprintName\s*=\s*"([^"]+)"/)?.[1] ?? schemeName;
|
|
602
|
+
const buildConfiguration =
|
|
603
|
+
content.match(
|
|
604
|
+
/ArchiveAction[^>]*buildConfiguration\s*=\s*"([^"]+)"/
|
|
605
|
+
)?.[1] ??
|
|
606
|
+
content.match(/LaunchAction[^>]*buildConfiguration\s*=\s*"([^"]+)"/)?.[1] ??
|
|
607
|
+
content.match(
|
|
608
|
+
/ProfileAction[^>]*buildConfiguration\s*=\s*"([^"]+)"/
|
|
609
|
+
)?.[1] ??
|
|
610
|
+
'Release';
|
|
611
|
+
|
|
612
|
+
return {
|
|
613
|
+
scheme: schemeName,
|
|
614
|
+
targetName: blueprintName,
|
|
615
|
+
buildConfiguration,
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function getIosSchemeMetadata() {
|
|
620
|
+
const iosMetadata = getIosTargetMetadata();
|
|
621
|
+
if (!iosMetadata) return null;
|
|
622
|
+
|
|
623
|
+
const schemeFiles = walkFiles(iosMetadata.projectFiles.iosDir, (filePath) =>
|
|
624
|
+
filePath.endsWith('.xcscheme')
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
const targetsByName = new Map(
|
|
628
|
+
iosMetadata.targets.map((target) => [target.name, target])
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
const schemes = schemeFiles
|
|
632
|
+
.map(parseSchemeFile)
|
|
633
|
+
.map((scheme) => {
|
|
634
|
+
const target = targetsByName.get(scheme.targetName);
|
|
635
|
+
if (!target) return null;
|
|
636
|
+
|
|
637
|
+
const config = target.configsByName.get(scheme.buildConfiguration) ??
|
|
638
|
+
target.configsByName.get('Release') ?? {
|
|
639
|
+
appId: target.appId,
|
|
640
|
+
version: target.version,
|
|
641
|
+
productName: target.productName,
|
|
642
|
+
name: target.buildConfiguration,
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
return {
|
|
646
|
+
name: scheme.scheme,
|
|
647
|
+
label: scheme.scheme,
|
|
648
|
+
targetName: target.name,
|
|
649
|
+
buildConfiguration: scheme.buildConfiguration,
|
|
650
|
+
appId: config.appId ?? target.appId ?? null,
|
|
651
|
+
version: config.version ?? target.version ?? null,
|
|
652
|
+
productName: config.productName ?? target.productName,
|
|
653
|
+
};
|
|
654
|
+
})
|
|
655
|
+
.filter(Boolean);
|
|
656
|
+
|
|
657
|
+
const schemesByTargetName = new Map(
|
|
658
|
+
schemes.map((scheme) => [scheme.targetName, scheme])
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
const mergedSchemes = iosMetadata.targets.map((target) => {
|
|
662
|
+
const matchedScheme = schemesByTargetName.get(target.name);
|
|
663
|
+
if (matchedScheme) {
|
|
664
|
+
return matchedScheme;
|
|
248
665
|
}
|
|
249
|
-
|
|
250
|
-
|
|
666
|
+
|
|
667
|
+
return {
|
|
668
|
+
name: target.name,
|
|
669
|
+
label: `${target.label}${target.buildConfiguration ? ` [${target.buildConfiguration}]` : ''}`,
|
|
670
|
+
targetName: target.name,
|
|
671
|
+
buildConfiguration: target.buildConfiguration,
|
|
672
|
+
appId: target.appId,
|
|
673
|
+
version: target.version,
|
|
674
|
+
productName: target.productName,
|
|
675
|
+
};
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
const uniqueSchemes = mergedSchemes.filter(
|
|
679
|
+
(scheme, index, allSchemes) =>
|
|
680
|
+
allSchemes.findIndex((candidate) => candidate.name === scheme.name) ===
|
|
681
|
+
index
|
|
682
|
+
);
|
|
683
|
+
|
|
684
|
+
return {
|
|
685
|
+
defaultConfig: uniqueSchemes[0] ?? iosMetadata.defaultConfig,
|
|
686
|
+
schemes: uniqueSchemes,
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
async function getIosSchemeSelection() {
|
|
691
|
+
const metadata = getIosSchemeMetadata();
|
|
692
|
+
if (!metadata) return null;
|
|
693
|
+
|
|
694
|
+
if (metadata.schemes.length <= 1) {
|
|
695
|
+
return metadata.defaultConfig;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
let selectedScheme;
|
|
699
|
+
let isSchemeConfirmed = false;
|
|
700
|
+
|
|
701
|
+
while (!isSchemeConfirmed) {
|
|
702
|
+
selectedScheme = await select({
|
|
703
|
+
message: 'Select iOS scheme:',
|
|
704
|
+
choices: metadata.schemes.map((scheme) => ({
|
|
705
|
+
name: `${scheme.label} (${scheme.appId ?? 'unknown app id'} / ${scheme.version ?? 'unknown version'})`,
|
|
706
|
+
value: scheme,
|
|
707
|
+
})),
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
isSchemeConfirmed = await confirm({
|
|
711
|
+
message: `Continue with iOS scheme ${selectedScheme.label ?? selectedScheme.name}?`,
|
|
712
|
+
default: true,
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
return selectedScheme;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
function getPlatformAppVersion(platform, selection) {
|
|
720
|
+
if (selection?.version) return selection.version;
|
|
721
|
+
|
|
722
|
+
if (platform === 'android') {
|
|
723
|
+
const metadata = getAndroidProjectMetadata();
|
|
724
|
+
return metadata?.defaultConfig.version ?? null;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
if (platform === 'ios') {
|
|
728
|
+
const metadata = getIosSchemeMetadata();
|
|
729
|
+
return metadata?.defaultConfig.version ?? null;
|
|
251
730
|
}
|
|
252
731
|
|
|
253
732
|
return null;
|
|
254
733
|
}
|
|
255
734
|
|
|
256
|
-
/**
|
|
257
|
-
* Get common configuration (API token, project ID, env).
|
|
258
|
-
*/
|
|
259
735
|
async function getCommonConfig() {
|
|
260
|
-
console.log(`\n⚙️ Enter common configuration
|
|
736
|
+
console.log(`\n⚙️ Enter common configuration for the app\n`);
|
|
261
737
|
|
|
262
738
|
const API_TOKEN = await input({
|
|
263
|
-
message:
|
|
739
|
+
message: 'Enter API Token:',
|
|
264
740
|
validate: (val) => (val.trim() ? true : 'API Token is required'),
|
|
265
741
|
});
|
|
266
742
|
|
|
267
743
|
const PROJECT_ID = await input({
|
|
268
|
-
message:
|
|
744
|
+
message: 'Enter Project ID:',
|
|
269
745
|
validate: (val) => (val.trim() ? true : 'Project ID is required'),
|
|
270
746
|
});
|
|
271
747
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
748
|
+
let ENVIRONMENT;
|
|
749
|
+
let isEnvironmentConfirmed = false;
|
|
750
|
+
|
|
751
|
+
while (!isEnvironmentConfirmed) {
|
|
752
|
+
ENVIRONMENT = await select({
|
|
753
|
+
message: 'Select Environment:',
|
|
754
|
+
choices: [
|
|
755
|
+
{ name: 'development', value: 'development' },
|
|
756
|
+
{ name: 'production', value: 'production' },
|
|
757
|
+
],
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
isEnvironmentConfirmed = await confirm({
|
|
761
|
+
message: `Continue with ${ENVIRONMENT} environment?`,
|
|
762
|
+
default: true,
|
|
763
|
+
});
|
|
764
|
+
}
|
|
279
765
|
|
|
280
766
|
return { API_TOKEN, PROJECT_ID, ENVIRONMENT };
|
|
281
767
|
}
|
|
282
768
|
|
|
283
|
-
|
|
284
|
-
* Get platform-specific configuration with version auto-detection.
|
|
285
|
-
*/
|
|
286
|
-
async function getPlatformSpecificConfig(platform) {
|
|
769
|
+
async function getPlatformConfig(platform) {
|
|
287
770
|
console.log(`\n🔧 Configuring ${platform.toUpperCase()}...\n`);
|
|
288
771
|
|
|
289
|
-
|
|
772
|
+
const selection =
|
|
773
|
+
platform === 'android'
|
|
774
|
+
? await getAndroidFlavorSelection()
|
|
775
|
+
: platform === 'ios'
|
|
776
|
+
? await getIosSchemeSelection()
|
|
777
|
+
: null;
|
|
778
|
+
|
|
779
|
+
if (platform === 'android' && selection?.label) {
|
|
780
|
+
console.log(
|
|
781
|
+
`📦 Selected Android flavor: ${selection.label} (${selection.appId ?? 'unknown app id'})`
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
if (platform === 'ios' && selection?.label) {
|
|
786
|
+
console.log(
|
|
787
|
+
`🍎 Selected iOS scheme: ${selection.label} (${selection.appId ?? 'unknown app id'})`
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
let detectedVersion = getPlatformAppVersion(platform, selection);
|
|
290
792
|
if (detectedVersion) {
|
|
291
793
|
console.log(`📱 Detected ${platform} version: ${detectedVersion}`);
|
|
292
794
|
} else {
|
|
@@ -302,12 +804,106 @@ async function getPlatformSpecificConfig(platform) {
|
|
|
302
804
|
default: false,
|
|
303
805
|
});
|
|
304
806
|
|
|
305
|
-
return {
|
|
807
|
+
return {
|
|
808
|
+
VERSION: detectedVersion,
|
|
809
|
+
FORCE_UPDATE,
|
|
810
|
+
APP_ID: selection?.appId ?? null,
|
|
811
|
+
VARIANT: selection?.name ?? null,
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function getAndroidOutputPaths(projectRoot) {
|
|
816
|
+
return {
|
|
817
|
+
bundleZipPath: path.join(
|
|
818
|
+
projectRoot,
|
|
819
|
+
'android',
|
|
820
|
+
'index.android.bundle.zip'
|
|
821
|
+
),
|
|
822
|
+
outputDir: path.join(projectRoot, 'android', 'output'),
|
|
823
|
+
sourceMapPath: path.join(projectRoot, 'android', 'sourcemap.js'),
|
|
824
|
+
sourceMapZipPath: path.join(projectRoot, 'android', 'sourcemap.zip'),
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function getIosOutputPaths(projectRoot) {
|
|
829
|
+
return {
|
|
830
|
+
bundleZipPath: path.join(projectRoot, 'ios', 'main.jsbundle.zip'),
|
|
831
|
+
outputDir: path.join(projectRoot, 'ios', 'output'),
|
|
832
|
+
sourceMapPath: path.join(projectRoot, 'ios', 'sourcemap.js'),
|
|
833
|
+
sourceMapZipPath: path.join(projectRoot, 'ios', 'sourcemap.zip'),
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function buildAndroid(selection) {
|
|
838
|
+
const projectRoot = getProjectRoot();
|
|
839
|
+
const paths = getAndroidOutputPaths(projectRoot);
|
|
840
|
+
|
|
841
|
+
console.log('📦 Building Android bundle...');
|
|
842
|
+
if (selection?.label) {
|
|
843
|
+
console.log(`📦 Using Android flavor: ${selection.label}`);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
fs.rmSync(paths.outputDir, { recursive: true, force: true });
|
|
847
|
+
fs.rmSync(paths.bundleZipPath, { force: true });
|
|
848
|
+
fs.rmSync(paths.sourceMapPath, { force: true });
|
|
849
|
+
fs.rmSync(paths.sourceMapZipPath, { force: true });
|
|
850
|
+
fs.mkdirSync(paths.outputDir, { recursive: true });
|
|
851
|
+
|
|
852
|
+
run(
|
|
853
|
+
`react-native bundle --platform android --dev false --entry-file index.js ` +
|
|
854
|
+
`--bundle-output ${path.relative(projectRoot, path.join(paths.outputDir, 'index.android.bundle'))} ` +
|
|
855
|
+
`--assets-dest ${path.relative(projectRoot, paths.outputDir)} ` +
|
|
856
|
+
`--sourcemap-output ${path.relative(projectRoot, paths.sourceMapPath)}`,
|
|
857
|
+
projectRoot
|
|
858
|
+
);
|
|
859
|
+
run(
|
|
860
|
+
`find output -type f | zip index.android.bundle.zip -@`,
|
|
861
|
+
path.join(projectRoot, 'android')
|
|
862
|
+
);
|
|
863
|
+
run(`zip sourcemap.zip sourcemap.js`, path.join(projectRoot, 'android'));
|
|
864
|
+
|
|
865
|
+
fs.rmSync(paths.outputDir, { recursive: true, force: true });
|
|
866
|
+
fs.rmSync(paths.sourceMapPath, { force: true });
|
|
867
|
+
|
|
868
|
+
console.log(`✅ Android bundle created at ${paths.bundleZipPath}`);
|
|
869
|
+
return paths.bundleZipPath;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function buildIOS(selection) {
|
|
873
|
+
const projectRoot = getProjectRoot();
|
|
874
|
+
const paths = getIosOutputPaths(projectRoot);
|
|
875
|
+
|
|
876
|
+
console.log('📦 Building iOS bundle...');
|
|
877
|
+
if (selection?.label) {
|
|
878
|
+
console.log(`🍎 Using iOS scheme: ${selection.label}`);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
fs.rmSync(paths.outputDir, { recursive: true, force: true });
|
|
882
|
+
fs.rmSync(paths.bundleZipPath, { force: true });
|
|
883
|
+
fs.rmSync(paths.sourceMapPath, { force: true });
|
|
884
|
+
fs.rmSync(paths.sourceMapZipPath, { force: true });
|
|
885
|
+
fs.mkdirSync(paths.outputDir, { recursive: true });
|
|
886
|
+
|
|
887
|
+
run(
|
|
888
|
+
`react-native bundle --platform ios --dev false --entry-file index.js ` +
|
|
889
|
+
`--bundle-output ${path.relative(projectRoot, path.join(paths.outputDir, 'main.jsbundle'))} ` +
|
|
890
|
+
`--assets-dest ${path.relative(projectRoot, paths.outputDir)} ` +
|
|
891
|
+
`--sourcemap-output ${path.relative(projectRoot, paths.sourceMapPath)}`,
|
|
892
|
+
projectRoot
|
|
893
|
+
);
|
|
894
|
+
run(
|
|
895
|
+
`find output -type f | zip main.jsbundle.zip -@`,
|
|
896
|
+
path.join(projectRoot, 'ios')
|
|
897
|
+
);
|
|
898
|
+
run(`zip sourcemap.zip sourcemap.js`, path.join(projectRoot, 'ios'));
|
|
899
|
+
|
|
900
|
+
fs.rmSync(paths.outputDir, { recursive: true, force: true });
|
|
901
|
+
fs.rmSync(paths.sourceMapPath, { force: true });
|
|
902
|
+
|
|
903
|
+
console.log(`✅ iOS bundle created at ${paths.bundleZipPath}`);
|
|
904
|
+
return paths.bundleZipPath;
|
|
306
905
|
}
|
|
307
906
|
|
|
308
|
-
/**
|
|
309
|
-
* Entry point
|
|
310
|
-
*/
|
|
311
907
|
(async () => {
|
|
312
908
|
try {
|
|
313
909
|
const platform = process.argv[2];
|
|
@@ -319,34 +915,36 @@ async function getPlatformSpecificConfig(platform) {
|
|
|
319
915
|
const commonConfig = await getCommonConfig();
|
|
320
916
|
|
|
321
917
|
if (platform === 'android') {
|
|
322
|
-
const
|
|
323
|
-
const
|
|
324
|
-
const androidFile = buildAndroid();
|
|
918
|
+
const androidConfig = await getPlatformConfig('android');
|
|
919
|
+
const androidFile = buildAndroid({ label: androidConfig.VARIANT });
|
|
325
920
|
await uploadBundle({
|
|
326
921
|
filePath: androidFile,
|
|
327
922
|
platform: 'android',
|
|
328
|
-
config,
|
|
923
|
+
config: { ...commonConfig, ...androidConfig },
|
|
329
924
|
});
|
|
330
925
|
} else if (platform === 'ios') {
|
|
331
|
-
const
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
926
|
+
const iosConfig = await getPlatformConfig('ios');
|
|
927
|
+
const iosFile = buildIOS({ label: iosConfig.VARIANT });
|
|
928
|
+
await uploadBundle({
|
|
929
|
+
filePath: iosFile,
|
|
930
|
+
platform: 'ios',
|
|
931
|
+
config: { ...commonConfig, ...iosConfig },
|
|
932
|
+
});
|
|
335
933
|
} else if (platform === 'all') {
|
|
336
|
-
const
|
|
337
|
-
const androidFile = buildAndroid();
|
|
934
|
+
const androidConfig = await getPlatformConfig('android');
|
|
935
|
+
const androidFile = buildAndroid({ label: androidConfig.VARIANT });
|
|
338
936
|
await uploadBundle({
|
|
339
937
|
filePath: androidFile,
|
|
340
938
|
platform: 'android',
|
|
341
|
-
config: { ...commonConfig, ...
|
|
939
|
+
config: { ...commonConfig, ...androidConfig },
|
|
342
940
|
});
|
|
343
941
|
|
|
344
|
-
const
|
|
345
|
-
const iosFile = buildIOS();
|
|
942
|
+
const iosConfig = await getPlatformConfig('ios');
|
|
943
|
+
const iosFile = buildIOS({ label: iosConfig.VARIANT });
|
|
346
944
|
await uploadBundle({
|
|
347
945
|
filePath: iosFile,
|
|
348
946
|
platform: 'ios',
|
|
349
|
-
config: { ...commonConfig, ...
|
|
947
|
+
config: { ...commonConfig, ...iosConfig },
|
|
350
948
|
});
|
|
351
949
|
} else {
|
|
352
950
|
console.log('❌ Invalid option. Use: android | ios | all');
|