codeplay-common 4.10.4 → 4.10.6
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/CHANGELOG.md +77 -67
- package/LICENSE +21 -21
- package/README.md +215 -215
- package/RELEASE_NOTES.md +75 -65
- package/files/buildCodeplay/add-splash-screen.js +255 -255
- package/files/buildCodeplay/apk-store-builder.js +97 -97
- package/files/buildCodeplay/codeplayBeforeBuild.js +4660 -4658
- package/files/buildCodeplay/fix-onesignal-plugin.js +96 -96
- package/files/buildCodeplay/ios-emi-admob-modification.js +24 -24
- package/files/buildCodeplay/manifestModification.js +35 -35
- package/files/buildCodeplay/packageidBaseModification.js +445 -445
- package/files/buildCodeplay/project-scripts.cjs +56 -56
- package/files/buildCodeplay/release-sync.config.json +4 -4
- package/files/buildCodeplay/setSplashAnimation.js +226 -226
- package/files/buildCodeplay/sync-admob-nextgen.js +218 -218
- package/files/buildCodeplay/versions.json +27 -27
- package/files/finalrelease +1239 -1239
- package/files/generate-ai-promo-video.js +23 -23
- package/files/ionic.config.json +6 -6
- package/files/scripts-common/run-codeplay-sync-after.js +21 -21
- package/files/scripts-common/screenshot/background-decorations.js +120 -120
- package/files/scripts-common/screenshot/frame-samples.js +117 -117
- package/files/scripts-common/setup-baseline-profile.js +265 -265
- package/files/take-screen-image.js +4304 -4304
- package/files/take-screen-video.js +644 -644
- package/package.json +27 -27
- package/scripts/sync-files.js +110 -85
- package/scripts/uninstall.js +2 -2
package/files/finalrelease
CHANGED
|
@@ -1,1239 +1,1239 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Can be run like
|
|
3
|
-
node finalrelease
|
|
4
|
-
node finalrelease 1
|
|
5
|
-
node finalrelease "1,2,7"
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { existsSync, readFileSync, readdirSync, promises, constants, writeFileSync, unlinkSync, mkdirSync, renameSync } from 'fs';
|
|
9
|
-
import { join, resolve } from 'path';
|
|
10
|
-
import { execSync, execFileSync } from 'child_process';
|
|
11
|
-
import { createInterface } from 'readline';
|
|
12
|
-
|
|
13
|
-
import { fileURLToPath } from 'url';
|
|
14
|
-
import { dirname } from 'path';
|
|
15
|
-
|
|
16
|
-
/* const path = require('path');
|
|
17
|
-
const fs = require('fs'); */
|
|
18
|
-
|
|
19
|
-
import fs from 'fs';
|
|
20
|
-
import path from 'path';
|
|
21
|
-
|
|
22
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
23
|
-
const __dirname = process.cwd();
|
|
24
|
-
|
|
25
|
-
function stripJavaScriptComments(content) {
|
|
26
|
-
let result = '';
|
|
27
|
-
let state = 'code';
|
|
28
|
-
|
|
29
|
-
for (let index = 0; index < content.length; index++) {
|
|
30
|
-
const character = content[index];
|
|
31
|
-
const nextCharacter = content[index + 1];
|
|
32
|
-
|
|
33
|
-
if (state === 'lineComment') {
|
|
34
|
-
if (character === '\n') {
|
|
35
|
-
result += character;
|
|
36
|
-
state = 'code';
|
|
37
|
-
} else {
|
|
38
|
-
result += ' ';
|
|
39
|
-
}
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (state === 'blockComment') {
|
|
44
|
-
if (character === '*' && nextCharacter === '/') {
|
|
45
|
-
result += ' ';
|
|
46
|
-
index++;
|
|
47
|
-
state = 'code';
|
|
48
|
-
} else {
|
|
49
|
-
result += character === '\n' ? '\n' : ' ';
|
|
50
|
-
}
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (state !== 'code') {
|
|
55
|
-
result += character;
|
|
56
|
-
|
|
57
|
-
if (character === '\\') {
|
|
58
|
-
if (nextCharacter !== undefined) {
|
|
59
|
-
result += nextCharacter;
|
|
60
|
-
index++;
|
|
61
|
-
}
|
|
62
|
-
} else if (
|
|
63
|
-
(state === 'singleQuote' && character === "'") ||
|
|
64
|
-
(state === 'doubleQuote' && character === '"') ||
|
|
65
|
-
(state === 'templateLiteral' && character === '`')
|
|
66
|
-
) {
|
|
67
|
-
state = 'code';
|
|
68
|
-
}
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (character === '/' && nextCharacter === '/') {
|
|
73
|
-
result += ' ';
|
|
74
|
-
index++;
|
|
75
|
-
state = 'lineComment';
|
|
76
|
-
} else if (character === '/' && nextCharacter === '*') {
|
|
77
|
-
result += ' ';
|
|
78
|
-
index++;
|
|
79
|
-
state = 'blockComment';
|
|
80
|
-
} else {
|
|
81
|
-
result += character;
|
|
82
|
-
|
|
83
|
-
if (character === "'") state = 'singleQuote';
|
|
84
|
-
else if (character === '"') state = 'doubleQuote';
|
|
85
|
-
else if (character === '`') state = 'templateLiteral';
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return result;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function checkProductionAdmobAlias() {
|
|
93
|
-
const capacitorConfig = JSON.parse(readFileSync(resolve(__dirname, 'capacitor.config.json'), 'utf8'));
|
|
94
|
-
if (capacitorConfig.plugins?.AdMob?.ADMOB_ENABLED === false) {
|
|
95
|
-
console.log('ℹ️ AdMob is disabled; production @admob alias validation skipped.');
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const possibleConfigFiles = ['vite.config.mjs', 'vite.config.js'];
|
|
100
|
-
const viteConfigPath = possibleConfigFiles
|
|
101
|
-
.map(configFile => resolve(__dirname, configFile))
|
|
102
|
-
.find(filePath => existsSync(filePath));
|
|
103
|
-
|
|
104
|
-
if (!viteConfigPath) {
|
|
105
|
-
console.error('❌ FINAL RELEASE BLOCKED: No vite.config.mjs or vite.config.js file found.');
|
|
106
|
-
process.exit(1);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const viteConfigContent = stripJavaScriptComments(readFileSync(viteConfigPath, 'utf8'));
|
|
110
|
-
const aliasPattern = /(['"])@admob\1\s*:\s*path\.resolve\(\s*__dirname\s*,\s*(['"])([^'"\r\n]+)\2\s*\)/g;
|
|
111
|
-
const activeAliases = [...viteConfigContent.matchAll(aliasPattern)].map(match => match[3]);
|
|
112
|
-
const productionAliasPattern = /^\.\/src\/js\/Ads\/(?:admob-nextgen-\d+(?:\.\d+)+\/admob-emi-nextgen|admob-emi-nextgen-\d+(?:\.\d+)+)\.js$/;
|
|
113
|
-
|
|
114
|
-
if (activeAliases.length === 0) {
|
|
115
|
-
console.error('❌ FINAL RELEASE BLOCKED: The production @admob alias is commented out or missing in the Vite config.');
|
|
116
|
-
console.error(" Enable: '@admob': path.resolve(__dirname, './src/js/Ads/admob-nextgen-x.x/admob-emi-nextgen.js')");
|
|
117
|
-
process.exit(1);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (activeAliases.length > 1) {
|
|
121
|
-
console.error('❌ FINAL RELEASE BLOCKED: Multiple active @admob aliases were found in the Vite config.');
|
|
122
|
-
console.error(' Keep exactly one active production @admob alias.');
|
|
123
|
-
process.exit(1);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const activeAlias = activeAliases[0];
|
|
127
|
-
if (!productionAliasPattern.test(activeAlias)) {
|
|
128
|
-
console.error(`❌ FINAL RELEASE BLOCKED: @admob currently points to '${activeAlias}'.`);
|
|
129
|
-
console.error(" It must point to './src/js/Ads/admob-nextgen-x.x/admob-emi-nextgen.js', not a test AdMob file.");
|
|
130
|
-
process.exit(1);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const productionAdmobPath = resolve(__dirname, activeAlias);
|
|
134
|
-
if (!existsSync(productionAdmobPath)) {
|
|
135
|
-
console.error(`❌ FINAL RELEASE BLOCKED: The production @admob file does not exist: ${productionAdmobPath}`);
|
|
136
|
-
process.exit(1);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
console.log(`✅ Production @admob alias verified: ${activeAlias}`);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Validate the production alias first when AdMob is enabled.
|
|
143
|
-
checkProductionAdmobAlias();
|
|
144
|
-
|
|
145
|
-
// Define a mapping between store IDs and store names
|
|
146
|
-
/* const storeNames = {
|
|
147
|
-
"1": "PlayStore",
|
|
148
|
-
"2": "SamsungStore",
|
|
149
|
-
"7": "AmazonStore"
|
|
150
|
-
}; */
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const isOnlyOneAPK = false; // 🔥 change to false for normal behavior
|
|
154
|
-
|
|
155
|
-
const storeNames = {
|
|
156
|
-
"1": "PlayStore",
|
|
157
|
-
"2": "SamsungStore",
|
|
158
|
-
"7": "AmazonStore",
|
|
159
|
-
|
|
160
|
-
"3": "MiStore",
|
|
161
|
-
"4": "HuaweiStore",
|
|
162
|
-
"5": "OppoStore",
|
|
163
|
-
//"6": "iOSStore",
|
|
164
|
-
"8": "VivoStore"
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
//1=> PlayStore 2=> SamsungStore 3=>MiStore 4=>HuaweiStore 5=>OppoStore 6=>iOSStore 7=> AmazonStore 8=> VivoStore
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
let isAdmobFound = false;
|
|
177
|
-
|
|
178
|
-
const amazonMinSdkVersion=24;
|
|
179
|
-
|
|
180
|
-
const androidManifestPath = join("android", "app", "src", "main", "AndroidManifest.xml");
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const configPath = path.join(process.cwd(), 'capacitor.config.json');
|
|
184
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
185
|
-
|
|
186
|
-
const _appUniqueId = config.android?.APP_UNIQUE_ID;
|
|
187
|
-
const _appName=config.appName;
|
|
188
|
-
const _appPackageId=config.appId;
|
|
189
|
-
|
|
190
|
-
const appReleaseSyncConfigPath = join(process.cwd(), 'buildCodeplay', 'release-sync.config.json');
|
|
191
|
-
const releaseSyncConfigPath = appReleaseSyncConfigPath;
|
|
192
|
-
let releaseSyncConfig = {};
|
|
193
|
-
if (existsSync(releaseSyncConfigPath)) {
|
|
194
|
-
try {
|
|
195
|
-
releaseSyncConfig = JSON.parse(readFileSync(releaseSyncConfigPath, 'utf8'));
|
|
196
|
-
} catch (error) {
|
|
197
|
-
console.warn(`⚠️ Could not read ${releaseSyncConfigPath}: ${error.message}`);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
const RELEASE_SYNC_URL = process.env.CODEPLAY_RELEASE_SYNC_URL || releaseSyncConfig.url || 'https://htmlcodeplay.com/codeplay/api/v1/release/sync';
|
|
201
|
-
const RELEASE_SYNC_TOKEN = process.env.CODEPLAY_RELEASE_SYNC_TOKEN || releaseSyncConfig.token || '';
|
|
202
|
-
const RELEASE_PLATFORM_IDS = { PlayStore: 1, SamsungStore: 13, AmazonStore: 17, MiStore: 15, HuaweiStore: 12, OppoStore: 14, VivoStore: 16 };
|
|
203
|
-
|
|
204
|
-
function syncReleaseToCodeplay(storeName, finalVersionName, finalVersionCode, releasedDate) {
|
|
205
|
-
if (!RELEASE_SYNC_TOKEN) {
|
|
206
|
-
console.warn(`⚠️ Codeplay release sync skipped: add a token to ${releaseSyncConfigPath}.`);
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
const payload = JSON.stringify({
|
|
210
|
-
app_name: _appName,
|
|
211
|
-
package_id: _appPackageId,
|
|
212
|
-
version_name: String(finalVersionName),
|
|
213
|
-
version_code: String(finalVersionCode),
|
|
214
|
-
released_date: releasedDate,
|
|
215
|
-
language_id: 0,
|
|
216
|
-
language_name: 'Capacitor+ Framework 7',
|
|
217
|
-
notes: 'Automated release update',
|
|
218
|
-
status: 1,
|
|
219
|
-
folder_name: path.basename(process.cwd()),
|
|
220
|
-
app_unique_id: String(_appUniqueId || ''),
|
|
221
|
-
platform_id: RELEASE_PLATFORM_IDS[storeName],
|
|
222
|
-
});
|
|
223
|
-
try {
|
|
224
|
-
const response = execFileSync('curl.exe', ['-sS', '--fail-with-body', '-X', 'POST', RELEASE_SYNC_URL,
|
|
225
|
-
'-H', 'Content-Type: application/json', '-H', `X-Release-Token: ${RELEASE_SYNC_TOKEN}`, '--data-binary', payload],
|
|
226
|
-
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
227
|
-
console.log(`✅ Codeplay sync (${storeName}): ${response.trim()}`);
|
|
228
|
-
} catch (error) {
|
|
229
|
-
console.warn(`⚠️ Codeplay sync failed for ${storeName}; continuing release builds: ${error.message}`);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const FLOWCHART_API_APP_ID = 228;
|
|
234
|
-
const FLOWCHART_SHARED_PATH = join('public', 'src', 'shared.js');
|
|
235
|
-
const FLOWCHART_PLAY_API_BASE = 'https://htmlcodeplay.com/appfiles/128FlowChart';
|
|
236
|
-
const FLOWCHART_OTHER_STORE_API_BASE = 'https://htmlcodeplay.com/appfiles/128FlowChart/otherapi';
|
|
237
|
-
const FLOWCHART_OTHER_STORE_API_HASH = 'd1a3774529b74206ae2117f9c25f258e485a1f7348620e769db19c2796aa1fdf';
|
|
238
|
-
const flowchartSharedOriginal = existsSync(FLOWCHART_SHARED_PATH)
|
|
239
|
-
? readFileSync(FLOWCHART_SHARED_PATH, 'utf8')
|
|
240
|
-
: null;
|
|
241
|
-
|
|
242
|
-
function applyFlowchartStoreApi(storeId) {
|
|
243
|
-
if (String(_appUniqueId) !== String(FLOWCHART_API_APP_ID) || !flowchartSharedOriginal) return false;
|
|
244
|
-
const apiBase = String(storeId) === '1' ? FLOWCHART_PLAY_API_BASE : FLOWCHART_OTHER_STORE_API_BASE;
|
|
245
|
-
const accessHash = String(storeId) === '1' ? '' : FLOWCHART_OTHER_STORE_API_HASH;
|
|
246
|
-
const apiBaseMarker = /const FLOWCHART_API_BASE = "[^"]*";/;
|
|
247
|
-
const accessHashMarker = /const FLOWCHART_API_ACCESS_HASH = "[^"]*";/;
|
|
248
|
-
|
|
249
|
-
if (!apiBaseMarker.test(flowchartSharedOriginal) || !accessHashMarker.test(flowchartSharedOriginal)) {
|
|
250
|
-
throw new Error(`Flowchart API configuration markers were not found in ${FLOWCHART_SHARED_PATH}.`);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const updated = flowchartSharedOriginal
|
|
254
|
-
.replace(apiBaseMarker, `const FLOWCHART_API_BASE = "${apiBase}";`)
|
|
255
|
-
.replace(accessHashMarker, `const FLOWCHART_API_ACCESS_HASH = "${accessHash}";`);
|
|
256
|
-
writeFileSync(FLOWCHART_SHARED_PATH, updated, 'utf8');
|
|
257
|
-
console.log(`✅ App 228 API endpoint configured for Store ID ${storeId}.`);
|
|
258
|
-
return true;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function restoreFlowchartStoreApi() {
|
|
262
|
-
if (String(_appUniqueId) === String(FLOWCHART_API_APP_ID) && flowchartSharedOriginal) {
|
|
263
|
-
writeFileSync(FLOWCHART_SHARED_PATH, flowchartSharedOriginal, 'utf8');
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
function fileExists(filePath) {
|
|
270
|
-
return existsSync(filePath);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const ffmpegPluginXmlPath = path.join(__dirname, 'node_modules', 'codeplay-fmpg', 'plugin.xml');
|
|
275
|
-
const capacitorConfigPath = join(process.cwd(), 'capacitor.config.json');
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
function isFfmpegUsed() {
|
|
280
|
-
try {
|
|
281
|
-
const xmlContent = fs.readFileSync(ffmpegPluginXmlPath, 'utf8');
|
|
282
|
-
// Simple check: if plugin.xml exists and contains <plugin> tag (or any FFmpeg-specific tag)
|
|
283
|
-
return /<plugin\b/.test(xmlContent);
|
|
284
|
-
} catch (err) {
|
|
285
|
-
console.log('⚠️ FFmpeg plugin not found:', err.message);
|
|
286
|
-
return false;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const _isffmpegUsed = isFfmpegUsed();
|
|
291
|
-
|
|
292
|
-
function getAdMobConfig() {
|
|
293
|
-
if (!fileExists(capacitorConfigPath)) {
|
|
294
|
-
throw new Error('❌ capacitor.config.json not found. Ensure this is a Capacitor project.');
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const config = JSON.parse(readFileSync(capacitorConfigPath, 'utf8'));
|
|
298
|
-
const admobConfig = config.plugins?.AdMob;
|
|
299
|
-
|
|
300
|
-
//_appPackageId=config.appId;
|
|
301
|
-
|
|
302
|
-
if (!admobConfig) {
|
|
303
|
-
throw new Error('❌ AdMob configuration is missing in capacitor.config.json.');
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Default to true if ADMOB_ENABLED is not specified
|
|
307
|
-
const isEnabled = admobConfig.ADMOB_ENABLED !== false;
|
|
308
|
-
|
|
309
|
-
if (!isEnabled) {
|
|
310
|
-
return { ADMOB_ENABLED: false }; // Skip further validation
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (!admobConfig.APP_ID_ANDROID || !admobConfig.APP_ID_IOS) {
|
|
314
|
-
throw new Error(' ❌ AdMob configuration is incomplete. Ensure APP_ID_ANDROID and APP_ID_IOS are defined.');
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return {
|
|
318
|
-
ADMOB_ENABLED: true,
|
|
319
|
-
APP_ID_ANDROID: admobConfig.APP_ID_ANDROID,
|
|
320
|
-
APP_ID_IOS: admobConfig.APP_ID_IOS,
|
|
321
|
-
USE_LITE_ADS: admobConfig.USE_LITE_ADS === "lite",
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const _capacitorConfig = getAdMobConfig();
|
|
328
|
-
|
|
329
|
-
const _isADMOB_ENABLED=_capacitorConfig.ADMOB_ENABLED;
|
|
330
|
-
isAdmobFound = _isADMOB_ENABLED;
|
|
331
|
-
|
|
332
|
-
// Proceed only if ADMOB_ENABLED is true
|
|
333
|
-
//if (_capacitorConfig.ADMOB_ENABLED)
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const admobConfigPath = join('src', 'js','Ads', 'admob-ad-configuration-nextgen.json');
|
|
337
|
-
let admobConfig;
|
|
338
|
-
|
|
339
|
-
if (_isADMOB_ENABLED)
|
|
340
|
-
{
|
|
341
|
-
try
|
|
342
|
-
{
|
|
343
|
-
admobConfig = JSON.parse(readFileSync(admobConfigPath, 'utf8'));
|
|
344
|
-
}
|
|
345
|
-
catch (err)
|
|
346
|
-
{
|
|
347
|
-
console.error("❌ Failed to read admob-ad-configuration.json", err);
|
|
348
|
-
process.exit(1);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
function updateIOSVersion(versionCode, versionName) {
|
|
357
|
-
|
|
358
|
-
const pbxprojPath = join("ios", "App", "App.xcodeproj", "project.pbxproj");
|
|
359
|
-
|
|
360
|
-
// 🔥 Single check only
|
|
361
|
-
if (!existsSync(pbxprojPath)) {
|
|
362
|
-
console.warn("⚠️ iOS project not found, skipping version update");
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
let content = readFileSync(pbxprojPath, 'utf8');
|
|
368
|
-
|
|
369
|
-
// Update MARKETING_VERSION
|
|
370
|
-
content = content.replace(
|
|
371
|
-
/MARKETING_VERSION = .*?;/g,
|
|
372
|
-
`MARKETING_VERSION = ${versionName};`
|
|
373
|
-
);
|
|
374
|
-
|
|
375
|
-
// Update CURRENT_PROJECT_VERSION
|
|
376
|
-
/* content = content.replace(
|
|
377
|
-
/CURRENT_PROJECT_VERSION = .*?;/g,
|
|
378
|
-
`CURRENT_PROJECT_VERSION = ${versionCode};`
|
|
379
|
-
); */
|
|
380
|
-
|
|
381
|
-
writeFileSync(pbxprojPath, content, 'utf8');
|
|
382
|
-
|
|
383
|
-
console.log(`🍏 iOS updated via Xcode settings → version: ${versionName}, build: ${versionCode}`);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const checkCommonFileStoreId=()=>{
|
|
407
|
-
const possibleConfigFiles = ['vite.config.mjs', 'vite.config.js'];
|
|
408
|
-
let viteConfigPath;
|
|
409
|
-
for (const configFile of possibleConfigFiles) {
|
|
410
|
-
const fullPath = resolve( configFile);
|
|
411
|
-
if (existsSync(fullPath)) {
|
|
412
|
-
viteConfigPath = fullPath;
|
|
413
|
-
break;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
if (!viteConfigPath) {
|
|
418
|
-
console.error('❌ Error: No vite.config.mjs or vite.config.js file found.');
|
|
419
|
-
process.exit(1);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
try {
|
|
423
|
-
// Read vite config file
|
|
424
|
-
const viteConfigContent = readFileSync(viteConfigPath, 'utf-8');
|
|
425
|
-
|
|
426
|
-
// Extract @common alias path
|
|
427
|
-
const aliasPattern = /'@common':\s*path\.resolve\(__dirname,\s*'(.+?)'\)/;
|
|
428
|
-
const match = viteConfigContent.match(aliasPattern);
|
|
429
|
-
|
|
430
|
-
if (!match) {
|
|
431
|
-
console.error(`❌ Error: @common alias not found in ${viteConfigPath}`);
|
|
432
|
-
process.exit(1);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
const commonFilePath = match[1];
|
|
436
|
-
const resolvedCommonPath = resolve(__dirname, commonFilePath);
|
|
437
|
-
|
|
438
|
-
// Read the common file content
|
|
439
|
-
if (!existsSync(resolvedCommonPath)) {
|
|
440
|
-
console.error(`❌ Error: Resolved common file does not exist: ${resolvedCommonPath}`);
|
|
441
|
-
process.exit(1);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
const commonFileContent = readFileSync(resolvedCommonPath, 'utf-8');
|
|
445
|
-
|
|
446
|
-
// Check for the _storeid export line
|
|
447
|
-
const storeIdPattern = /export\s+let\s+_storeid\s*=\s*import\.meta\.env\.VITE_STORE_ID\s*\|\|\s*1\s*;/;
|
|
448
|
-
if (!storeIdPattern.test(commonFileContent)) {
|
|
449
|
-
console.error(`❌ Error: _storeid value is wrong in ${commonFilePath}`);
|
|
450
|
-
process.exit(1);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
console.log(commonFilePath,'Success - No problem found');
|
|
454
|
-
} catch (error) {
|
|
455
|
-
console.error('❌ Error:', error);
|
|
456
|
-
process.exit(1);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
const checkIsTestingInAdmob=()=>{
|
|
462
|
-
|
|
463
|
-
if (admobConfig.config && admobConfig.config.isTesting === true) {
|
|
464
|
-
console.error(`❌ Problem found while generating the AAB file. Please change "isTesting: true" to "isTesting: false" in the "admob-ad-configuration-nextgen.json" file.`);
|
|
465
|
-
process.exit(1); // Exit with an error code to halt the process
|
|
466
|
-
} else {
|
|
467
|
-
console.log('✅ No problem found. "isTesting" is either already false or not defined.');
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
const checkIsAdsDisableByReturnStatement=()=>{
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
// Inspect the controller selected by the validated production alias.
|
|
475
|
-
const viteFile = ['vite.config.mjs', 'vite.config.js']
|
|
476
|
-
.map(file => resolve(__dirname, file)).find(file => existsSync(file));
|
|
477
|
-
const config = stripJavaScriptComments(readFileSync(viteFile, 'utf8'));
|
|
478
|
-
const alias = config.match(/(['"])@admob\1\s*:\s*path\.resolve\(\s*__dirname\s*,\s*(['"])([^'"\r\n]+)\2\s*\)/);
|
|
479
|
-
const filePath = resolve(__dirname, alias[3]);
|
|
480
|
-
const admobFile = alias[3];
|
|
481
|
-
const content = readFileSync(filePath, 'utf-8');
|
|
482
|
-
|
|
483
|
-
// Step 2: Extract the adsOnDeviceReady function body
|
|
484
|
-
const functionRegex = /async\s+function\s+adsOnDeviceReady\s*\([^)]*\)\s*{([\s\S]*?)^}/m;
|
|
485
|
-
const match = content.match(functionRegex);
|
|
486
|
-
|
|
487
|
-
if (!match) {
|
|
488
|
-
console.log(`❌ Function 'adsOnDeviceReady' not found in file: ${admobFile}`);
|
|
489
|
-
process.exit(1);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
const body = match[1];
|
|
493
|
-
const lines = body.split('\n').map(line => line.trim());
|
|
494
|
-
|
|
495
|
-
// Step 3: Skip blank lines and comments, get the first real code line
|
|
496
|
-
let firstCodeLine = '';
|
|
497
|
-
for (const line of lines) {
|
|
498
|
-
if (line === '' || line.startsWith('//')) continue;
|
|
499
|
-
firstCodeLine = line;
|
|
500
|
-
break;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// Step 4: Block if it's any of the unwanted returns
|
|
504
|
-
const badReturnPattern = /^return\s*(true|false)?\s*;?$/;
|
|
505
|
-
|
|
506
|
-
if (badReturnPattern.test(firstCodeLine)) {
|
|
507
|
-
console.log(`❌ BLOCKED in file '${admobFile}': First active line in 'adsOnDeviceReady' is '${firstCodeLine}'`);
|
|
508
|
-
process.exit(2);
|
|
509
|
-
} else {
|
|
510
|
-
console.log(`✅ Safe: No early return (true/false) found in 'adsOnDeviceReady' of file '${admobFile}'.`);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
const addPermission_AD_ID=async()=>{
|
|
517
|
-
|
|
518
|
-
if (isAdmobFound) {
|
|
519
|
-
if (existsSync(androidManifestPath)) {
|
|
520
|
-
let manifestContent = readFileSync(androidManifestPath, 'utf8');
|
|
521
|
-
let modified = false;
|
|
522
|
-
|
|
523
|
-
// --- Step 1: Ensure AD_ID permission exists ---
|
|
524
|
-
const adIdPermission = '<uses-permission android:name="com.google.android.gms.permission.AD_ID" />';
|
|
525
|
-
if (!manifestContent.includes(adIdPermission)) {
|
|
526
|
-
console.log("📄 AD_ID permission not found. Adding to AndroidManifest.xml.");
|
|
527
|
-
manifestContent = manifestContent.replace('</manifest>', ` ${adIdPermission}\n</manifest>`);
|
|
528
|
-
console.log("✅ AD_ID permission added successfully.");
|
|
529
|
-
modified = true;
|
|
530
|
-
} else {
|
|
531
|
-
console.log("ℹ️ AD_ID permission already exists in AndroidManifest.xml.");
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// --- Step 2: Ensure OPTIMIZE_AD_LOADING meta-data exists ---
|
|
535
|
-
const optimizeAdMeta = `<meta-data android:name="com.google.android.gms.ads.flag.OPTIMIZE_AD_LOADING" android:value="true" />`;
|
|
536
|
-
|
|
537
|
-
if (!manifestContent.includes(optimizeAdMeta)) {
|
|
538
|
-
console.log("📄 OPTIMIZE_AD_LOADING meta-data not found. Adding to AndroidManifest.xml.");
|
|
539
|
-
|
|
540
|
-
const appTagPattern = /<application[^>]*>/;
|
|
541
|
-
if (appTagPattern.test(manifestContent)) {
|
|
542
|
-
manifestContent = manifestContent.replace(appTagPattern, match => `${match}\n ${optimizeAdMeta}`);
|
|
543
|
-
console.log("✅ OPTIMIZE_AD_LOADING meta-data added successfully.");
|
|
544
|
-
modified = true;
|
|
545
|
-
} else {
|
|
546
|
-
console.error("❌ <application> tag not found in AndroidManifest.xml.");
|
|
547
|
-
}
|
|
548
|
-
} else {
|
|
549
|
-
console.log("ℹ️ OPTIMIZE_AD_LOADING meta-data already exists in AndroidManifest.xml.");
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
// --- Step 3: Write only if modified ---
|
|
553
|
-
if (modified) {
|
|
554
|
-
writeFileSync(androidManifestPath, manifestContent, 'utf8');
|
|
555
|
-
console.log("💾 AndroidManifest.xml updated successfully.");
|
|
556
|
-
} else {
|
|
557
|
-
console.log("✅ No changes needed. AndroidManifest.xml is up to date.");
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
} else {
|
|
561
|
-
console.error("❌ AndroidManifest.xml not found at the specified path.");
|
|
562
|
-
}
|
|
563
|
-
} else {
|
|
564
|
-
console.log("\x1b[33m%s\x1b[0m", "⚠️ No AdMob found, so AD_ID permission and meta-data were not added");
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
checkCommonFileStoreId();
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
if (_isADMOB_ENABLED)
|
|
589
|
-
{
|
|
590
|
-
checkIsTestingInAdmob();
|
|
591
|
-
checkIsAdsDisableByReturnStatement()
|
|
592
|
-
|
|
593
|
-
await addPermission_AD_ID()
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
let originalReleaseType, originalSigningType;
|
|
599
|
-
|
|
600
|
-
function readCapacitorConfig() {
|
|
601
|
-
return JSON.parse(fs.readFileSync(capacitorConfigPath, 'utf8'));
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
function writeCapacitorConfig(config) {
|
|
605
|
-
fs.writeFileSync(capacitorConfigPath, JSON.stringify(config, null, 2), 'utf8');
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
/* function updateCapacitorConfig(releaseType, signingType) {
|
|
609
|
-
const config = readCapacitorConfig();
|
|
610
|
-
|
|
611
|
-
// Save original values once
|
|
612
|
-
if (originalReleaseType === undefined) originalReleaseType = config.android?.buildOptions?.releaseType || '';
|
|
613
|
-
if (originalSigningType === undefined) originalSigningType = config.android?.buildOptions?.signingType || '';
|
|
614
|
-
|
|
615
|
-
// Update values
|
|
616
|
-
config.android = config.android || {};
|
|
617
|
-
config.android.buildOptions = config.android.buildOptions || {};
|
|
618
|
-
config.android.buildOptions.releaseType = releaseType;
|
|
619
|
-
config.android.buildOptions.signingType = signingType;
|
|
620
|
-
|
|
621
|
-
writeCapacitorConfig(config);
|
|
622
|
-
console.log(`ℹ️ capacitor.config.json updated: releaseType=${releaseType}, signingType=${signingType}`);
|
|
623
|
-
} */
|
|
624
|
-
|
|
625
|
-
/* function restoreCapacitorConfig() {
|
|
626
|
-
const config = readCapacitorConfig();
|
|
627
|
-
if (originalReleaseType !== undefined) config.android.buildOptions.releaseType = originalReleaseType;
|
|
628
|
-
if (originalSigningType !== undefined) config.android.buildOptions.signingType = originalSigningType;
|
|
629
|
-
writeCapacitorConfig(config);
|
|
630
|
-
console.log(`✅ capacitor.config.json restored to original values.`);
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
process.on('SIGINT', () => {
|
|
634
|
-
console.log('\n⚠️ Detected Ctrl+C, restoring capacitor.config.json...');
|
|
635
|
-
restoreCapacitorConfig();
|
|
636
|
-
process.exit(0);
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
process.on('exit', () => {
|
|
640
|
-
restoreCapacitorConfig();
|
|
641
|
-
}); */
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const { playstore, samsung, amazon } = (_isADMOB_ENABLED && admobConfig.IAP) ? admobConfig.IAP : { playstore: false, samsung: false, amazon: false };
|
|
646
|
-
console.log(`ℹ️ IAP Configurations - PlayStore: ${playstore}, Samsung: ${samsung}, Amazon: ${amazon}`);
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
// Get the store ID from the command line arguments
|
|
654
|
-
/* const storeIdArg = ["2","3"]//process.argv[2]; // Get the store ID from the command line
|
|
655
|
-
const storeIds = storeIdArg ? storeIdArg : Object.keys(storeNames);//["1", "2", "7"]; // If a specific ID is provided, use it; otherwise, use all store IDs
|
|
656
|
-
|
|
657
|
-
debugger; */
|
|
658
|
-
|
|
659
|
-
let storeIdArg = process.argv[2]; // command-line argument
|
|
660
|
-
let storeIds;
|
|
661
|
-
|
|
662
|
-
if (storeIdArg) {
|
|
663
|
-
try {
|
|
664
|
-
// Try parsing as JSON array first
|
|
665
|
-
const parsed = JSON.parse(storeIdArg);
|
|
666
|
-
if (Array.isArray(parsed)) {
|
|
667
|
-
storeIds = parsed.map(String); // ensure strings
|
|
668
|
-
} else {
|
|
669
|
-
storeIds = [parsed.toString()];
|
|
670
|
-
}
|
|
671
|
-
} catch (e) {
|
|
672
|
-
// Not JSON, assume comma-separated string
|
|
673
|
-
storeIds = storeIdArg.split(",").map(s => s.trim());
|
|
674
|
-
}
|
|
675
|
-
} else {
|
|
676
|
-
// No argument, use all store IDs
|
|
677
|
-
storeIds = Object.keys(storeNames);
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
// 🔥 If only one APK mode, override APK storeIds
|
|
682
|
-
/* if (isOnlyOneAPK) {
|
|
683
|
-
console.log("⚡ isOnlyOneAPK enabled → Only MiStore (3) will be used for APK build");
|
|
684
|
-
storeIds = ["3"]; // Only MiStore
|
|
685
|
-
} */
|
|
686
|
-
|
|
687
|
-
// Store the original minSdkVersion globally
|
|
688
|
-
let originalMinSdkVersion;
|
|
689
|
-
|
|
690
|
-
// Remove any existing AAB files before starting the build process
|
|
691
|
-
const aabDirectory = join("android", "app", "build", "outputs", "bundle", "release");
|
|
692
|
-
if (existsSync(aabDirectory)) {
|
|
693
|
-
const files = readdirSync(aabDirectory).filter(file => file.endsWith('.aab'));
|
|
694
|
-
files.forEach(file => {
|
|
695
|
-
const filePath = join(aabDirectory, file);
|
|
696
|
-
unlinkSync(filePath);
|
|
697
|
-
console.log(`ℹ️ Deleted existing AAB file: ${file}`);
|
|
698
|
-
});
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
const aabOutputDir = join("AAB");
|
|
702
|
-
if (!existsSync(aabOutputDir)) {
|
|
703
|
-
mkdirSync(aabOutputDir);
|
|
704
|
-
console.log(`Created directory: ${aabOutputDir}`);
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
if (existsSync(aabOutputDir)) {
|
|
708
|
-
const files = readdirSync(aabOutputDir).filter(
|
|
709
|
-
file => file.endsWith('.aab') || file.endsWith('.apk')
|
|
710
|
-
);
|
|
711
|
-
|
|
712
|
-
files.forEach(file => {
|
|
713
|
-
const filePath = join(aabOutputDir, file);
|
|
714
|
-
unlinkSync(filePath);
|
|
715
|
-
console.log(`Deleted existing build file: ${file}`);
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
// Extract version code and version name from build.gradle
|
|
721
|
-
const gradleFilePath = join("android", "app", "build.gradle");
|
|
722
|
-
const gradleContent = readFileSync(gradleFilePath, 'utf8');
|
|
723
|
-
|
|
724
|
-
// Return the character range of a Gradle block, allowing nested blocks inside it.
|
|
725
|
-
function findGradleBlock(content, headerPattern) {
|
|
726
|
-
const headerMatch = content.match(headerPattern);
|
|
727
|
-
if (!headerMatch) return null;
|
|
728
|
-
const openingBrace = content.indexOf('{', headerMatch.index + headerMatch[0].length);
|
|
729
|
-
if (openingBrace === -1) return null;
|
|
730
|
-
let depth = 0;
|
|
731
|
-
let quote = null;
|
|
732
|
-
for (let index = openingBrace; index < content.length; index += 1) {
|
|
733
|
-
const character = content[index];
|
|
734
|
-
const previous = content[index - 1];
|
|
735
|
-
if (quote) {
|
|
736
|
-
if (character === quote && previous !== '\\') quote = null;
|
|
737
|
-
continue;
|
|
738
|
-
}
|
|
739
|
-
if (character === '\"' || character === "'") quote = character;
|
|
740
|
-
else if (character === '{') depth += 1;
|
|
741
|
-
else if (character === '}' && --depth === 0) return { start: openingBrace + 1, end: index };
|
|
742
|
-
}
|
|
743
|
-
return null;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
function ensureReleaseCodeOptimization() {
|
|
747
|
-
const currentContent = readFileSync(gradleFilePath, 'utf8');
|
|
748
|
-
const releaseBlock = findGradleBlock(currentContent, /buildTypes\s*\{[\s\S]*?\brelease\s*/);
|
|
749
|
-
if (!releaseBlock) throw new Error('Release build configuration was not found.');
|
|
750
|
-
|
|
751
|
-
const releaseContent = currentContent.slice(releaseBlock.start, releaseBlock.end);
|
|
752
|
-
let optimizedContent = releaseContent.replace(
|
|
753
|
-
/getDefaultProguardFile\(\s*(['"])proguard-android\.txt\1\s*\)/g,
|
|
754
|
-
"getDefaultProguardFile('proguard-android-optimize.txt')"
|
|
755
|
-
);
|
|
756
|
-
if (!/getDefaultProguardFile\(\s*(['"])proguard-android-optimize\.txt\1\s*\)/.test(optimizedContent)) {
|
|
757
|
-
throw new Error('Release build must use the optimisation-enabled default ProGuard rules.');
|
|
758
|
-
}
|
|
759
|
-
const newline = currentContent.includes('\r\n') ? '\r\n' : '\n';
|
|
760
|
-
// Preserve commented settings and explicit false values as app-specific opt-outs.
|
|
761
|
-
const comments = optimizedContent.match(/\/\/[^\r\n]*|\/\*[\s\S]*?\*\//g) || [];
|
|
762
|
-
if (!comments.some(comment => /\bshrinkResources\b/.test(comment))) {
|
|
763
|
-
const activeContent = optimizedContent.replace(/\/\/[^\r\n]*|\/\*[\s\S]*?\*\//g, '');
|
|
764
|
-
const shrinkResources = /^[ \t]*minifyEnabled\s*(?:=\s*)?true\b/m.test(activeContent);
|
|
765
|
-
const shrinkPattern = /^([ \t]*shrinkResources\s*(?:=\s*)?)(true|false)\b/m;
|
|
766
|
-
if (shrinkPattern.test(optimizedContent)) {
|
|
767
|
-
if (!shrinkResources) optimizedContent = optimizedContent.replace(shrinkPattern, '$1false');
|
|
768
|
-
} else {
|
|
769
|
-
optimizedContent = `${newline} shrinkResources ${shrinkResources}${optimizedContent}`;
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
if (optimizedContent !== releaseContent) {
|
|
773
|
-
writeFileSync(gradleFilePath, currentContent.slice(0, releaseBlock.start)
|
|
774
|
-
+ optimizedContent + currentContent.slice(releaseBlock.end), 'utf8');
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
const propertiesPath = join(__dirname, 'android', 'gradle.properties');
|
|
778
|
-
const properties = readFileSync(propertiesPath, 'utf8');
|
|
779
|
-
const propertyPattern = /^[ \t]*[#!]?[ \t]*android\.r8\.optimizedResourceShrinking(?:[ \t]*[=:]|[ \t]+|[ \t]*$)/m;
|
|
780
|
-
const property = 'android.r8.optimizedResourceShrinking=true';
|
|
781
|
-
const nextProperties = propertyPattern.test(properties)
|
|
782
|
-
? properties
|
|
783
|
-
: `${properties.trimEnd()}${newline}${property}${newline}`;
|
|
784
|
-
if (nextProperties !== properties) writeFileSync(propertiesPath, nextProperties, 'utf8');
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
function signReleaseBundleWithSha256() {
|
|
788
|
-
const buildOptions = readCapacitorConfig().android?.buildOptions;
|
|
789
|
-
if (!buildOptions?.keystorePath || !buildOptions.keystorePassword
|
|
790
|
-
|| !buildOptions.keystoreAlias || !buildOptions.keystoreAliasPassword) {
|
|
791
|
-
throw new Error('Android AAB signing requires the existing keystore path, alias and passwords.');
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
const keystorePath = resolve(__dirname, 'android', buildOptions.keystorePath);
|
|
795
|
-
const bundleDirectory = join(__dirname, 'android', 'app', 'build', 'outputs', 'bundle', 'release');
|
|
796
|
-
const unsignedBundle = join(bundleDirectory, 'app-release.aab');
|
|
797
|
-
const signedBundle = join(bundleDirectory, 'app-release-signed.aab');
|
|
798
|
-
if (!existsSync(keystorePath) || !existsSync(unsignedBundle)) {
|
|
799
|
-
throw new Error('The configured upload keystore or release AAB was not found.');
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
const jarsigner = process.env.JAVA_HOME
|
|
803
|
-
? join(process.env.JAVA_HOME, 'bin', process.platform === 'win32' ? 'jarsigner.exe' : 'jarsigner')
|
|
804
|
-
: 'jarsigner';
|
|
805
|
-
try {
|
|
806
|
-
// Keep passwords out of command arguments and error output.
|
|
807
|
-
execFileSync(jarsigner, [
|
|
808
|
-
'-sigalg', 'SHA256withRSA', '-digestalg', 'SHA-256',
|
|
809
|
-
'-keystore', keystorePath,
|
|
810
|
-
'-storepass:env', 'CODEPLAY_AAB_STORE_PASSWORD',
|
|
811
|
-
'-keypass:env', 'CODEPLAY_AAB_KEY_PASSWORD',
|
|
812
|
-
'-signedjar', signedBundle, unsignedBundle, buildOptions.keystoreAlias,
|
|
813
|
-
], {
|
|
814
|
-
stdio: 'pipe',
|
|
815
|
-
env: {
|
|
816
|
-
...process.env,
|
|
817
|
-
CODEPLAY_AAB_STORE_PASSWORD: buildOptions.keystorePassword,
|
|
818
|
-
CODEPLAY_AAB_KEY_PASSWORD: buildOptions.keystoreAliasPassword,
|
|
819
|
-
},
|
|
820
|
-
});
|
|
821
|
-
const verification = execFileSync(jarsigner, [
|
|
822
|
-
'-J-Duser.language=en', '-J-Duser.country=US', '-verify', signedBundle,
|
|
823
|
-
], { encoding: 'utf8', stdio: 'pipe' });
|
|
824
|
-
if (!verification.includes('jar verified.') || verification.includes('treated as unsigned')) {
|
|
825
|
-
throw new Error('Signature verification failed.');
|
|
826
|
-
}
|
|
827
|
-
} catch {
|
|
828
|
-
throw new Error('SHA-256 AAB signing or verification failed. Check the configured upload key and JDK; release stopped.');
|
|
829
|
-
}
|
|
830
|
-
console.log('✅ Release AAB signed with SHA-256 using the existing upload key and signature verified.');
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
const amazonWindowExtensionsDependency = 'implementation "androidx.window.extensions.core:core:1.0.0"';
|
|
834
|
-
const amazonWindowExtensionsDependencyPattern = /^[ \t]*implementation\s+["']androidx\.window\.extensions\.core:core:[^"']+["'][ \t]*(?:\r?\n)?/gm;
|
|
835
|
-
|
|
836
|
-
function setAmazonWindowExtensionsDependency(enabled, { cleanup = false } = {}) {
|
|
837
|
-
const currentGradleContent = readFileSync(gradleFilePath, 'utf8');
|
|
838
|
-
let nextGradleContent = currentGradleContent.replace(amazonWindowExtensionsDependencyPattern, '');
|
|
839
|
-
|
|
840
|
-
if (enabled) {
|
|
841
|
-
const dependenciesBlock = findGradleBlock(nextGradleContent, /^\s*dependencies\s*/m);
|
|
842
|
-
if (!dependenciesBlock) {
|
|
843
|
-
throw new Error(`The dependencies block was not found in ${gradleFilePath}.`);
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
const newline = nextGradleContent.includes('\r\n') ? '\r\n' : '\n';
|
|
847
|
-
const contentBeforeClosingBrace = nextGradleContent.slice(0, dependenciesBlock.end);
|
|
848
|
-
const linePrefix = contentBeforeClosingBrace.endsWith('\n') ? '' : newline;
|
|
849
|
-
nextGradleContent = contentBeforeClosingBrace
|
|
850
|
-
+ linePrefix
|
|
851
|
-
+ ` ${amazonWindowExtensionsDependency}${newline}`
|
|
852
|
-
+ nextGradleContent.slice(dependenciesBlock.end);
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
if (nextGradleContent !== currentGradleContent) {
|
|
856
|
-
writeFileSync(gradleFilePath, nextGradleContent, 'utf8');
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
console.log(enabled
|
|
860
|
-
? '✅ Added Amazon Fire WebView compatibility dependency for Store ID 7.'
|
|
861
|
-
: cleanup
|
|
862
|
-
? 'ℹ️ Removed temporary Amazon Fire WebView compatibility dependency after build cleanup.'
|
|
863
|
-
: 'ℹ️ Amazon Fire WebView compatibility dependency is not included for this build.');
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
const versionCodeMatch = gradleContent.match(/versionCode\s+(\d+)/);
|
|
867
|
-
const versionNameMatch = gradleContent.match(/versionName\s+"([^"]+)"/);
|
|
868
|
-
|
|
869
|
-
const versionCode = versionCodeMatch ? versionCodeMatch[1] : '';
|
|
870
|
-
const versionName = versionNameMatch ? versionNameMatch[1] : '';
|
|
871
|
-
|
|
872
|
-
// Display the current versionCode and versionName
|
|
873
|
-
console.log(`Current versionCode: ${versionCode}`);
|
|
874
|
-
console.log(`Current versionName: ${versionName}`);
|
|
875
|
-
|
|
876
|
-
// Create an interface for user input
|
|
877
|
-
async function checkBaselineProfileBeforeRelease() {
|
|
878
|
-
const profilePaths = [
|
|
879
|
-
join(__dirname, 'baselineProfiles', 'baseline-prof.txt'),
|
|
880
|
-
join(__dirname, 'android', 'app', 'src', 'main', 'generated', 'baselineProfiles', 'baseline-prof.txt'),
|
|
881
|
-
join(__dirname, 'android', 'app', 'src', 'main', 'baseline-prof.txt'),
|
|
882
|
-
join(__dirname, 'android', 'app', 'src', 'main', 'baselineProfiles', 'baseline-prof.txt'),
|
|
883
|
-
];
|
|
884
|
-
const hasProfile = () => profilePaths.some(profilePath =>
|
|
885
|
-
existsSync(profilePath) && readFileSync(profilePath, 'utf8').trim().length > 0);
|
|
886
|
-
if (hasProfile()) return;
|
|
887
|
-
|
|
888
|
-
console.log('⚠️ baseline-prof.txt is missing or empty.');
|
|
889
|
-
console.log('Generation command: npm run baseline-profile:generate');
|
|
890
|
-
console.log('Generation requires an ADB-connected Android device or a configured emulator.');
|
|
891
|
-
const answer = await new Promise(resolveAnswer => {
|
|
892
|
-
rl.question('Generate AAB/APK without a Baseline Profile? [Enter/Y = continue, n = generate profile first]: ', resolveAnswer);
|
|
893
|
-
});
|
|
894
|
-
if (answer.trim().toLowerCase() !== 'n') return;
|
|
895
|
-
|
|
896
|
-
rl.pause();
|
|
897
|
-
try {
|
|
898
|
-
execSync('npm run baseline-profile:generate', { cwd: __dirname, stdio: 'inherit' });
|
|
899
|
-
if (!hasProfile()) {
|
|
900
|
-
throw new Error('Baseline Profile generation did not produce a non-empty baseline-prof.txt; release stopped.');
|
|
901
|
-
}
|
|
902
|
-
// Reuse setup to back up and synchronise the generated profile before packaging.
|
|
903
|
-
execFileSync(process.execPath, [join(__dirname, 'scripts-common', 'setup-baseline-profile.js')], {
|
|
904
|
-
cwd: __dirname,
|
|
905
|
-
stdio: 'inherit',
|
|
906
|
-
});
|
|
907
|
-
} finally {
|
|
908
|
-
rl.resume();
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
const rl = createInterface({
|
|
913
|
-
input: process.stdin,
|
|
914
|
-
output: process.stdout
|
|
915
|
-
});
|
|
916
|
-
|
|
917
|
-
// Ask for new versionCode
|
|
918
|
-
rl.question('Enter new versionCode (press enter to keep current): ', (newVersionCode) => {
|
|
919
|
-
const finalVersionCode = newVersionCode || versionCode; // Use existing if no input
|
|
920
|
-
|
|
921
|
-
// Ask for new versionName
|
|
922
|
-
rl.question('Enter new versionName (press enter to keep current): ', async (newVersionName) => {
|
|
923
|
-
const finalVersionName = newVersionName || versionName; // Use existing if no input
|
|
924
|
-
|
|
925
|
-
// Log the final version details
|
|
926
|
-
console.log(`📦 Final versionCode: ${finalVersionCode}`);
|
|
927
|
-
console.log(`📝 Final versionName: ${finalVersionName}`);
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
updateIOSVersion(finalVersionCode, finalVersionName);
|
|
931
|
-
|
|
932
|
-
// Update build.gradle with the new version details
|
|
933
|
-
let updatedGradleContent = gradleContent
|
|
934
|
-
.replace(/versionCode\s+\d+/, `versionCode ${finalVersionCode}`)
|
|
935
|
-
.replace(/versionName\s+"[^"]+"/, `versionName "${finalVersionName}"`);
|
|
936
|
-
|
|
937
|
-
// Check if resConfigs "en" already exists
|
|
938
|
-
const resConfigsLine = ' resConfigs "en"';
|
|
939
|
-
if (!updatedGradleContent.includes(resConfigsLine)) {
|
|
940
|
-
// Add resConfigs "en" below versionName
|
|
941
|
-
updatedGradleContent = updatedGradleContent.replace(/versionName\s+"[^"]+"/, `versionName "${finalVersionName}"\n${resConfigsLine}`);
|
|
942
|
-
} else {
|
|
943
|
-
console.log('ℹ️ resConfigs "en" already exists in build.gradle.');
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
// List of package IDs for which minify should be false
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
// Determine desired minify value
|
|
952
|
-
const desiredMinify = !_isffmpegUsed;
|
|
953
|
-
|
|
954
|
-
// Limit minification changes to release; debug may have its own test settings.
|
|
955
|
-
const minificationBlock = findGradleBlock(updatedGradleContent, /buildTypes\s*\{[\s\S]*?\brelease\s*/);
|
|
956
|
-
if (!minificationBlock) {
|
|
957
|
-
throw new Error('Release build configuration was not found.');
|
|
958
|
-
}
|
|
959
|
-
let minificationContent = updatedGradleContent.slice(minificationBlock.start, minificationBlock.end);
|
|
960
|
-
if (/\bminifyEnabled\s*(?:=\s*)?(true|false)\b/.test(minificationContent)) {
|
|
961
|
-
// Replace existing value with desired
|
|
962
|
-
minificationContent = minificationContent.replace(/\bminifyEnabled\s*(?:=\s*)?(true|false)\b/, `minifyEnabled ${desiredMinify}`);
|
|
963
|
-
console.log(`Replaced minifyEnabled with ${desiredMinify}.`);
|
|
964
|
-
} else {
|
|
965
|
-
// Insert minifyEnabled if not present
|
|
966
|
-
minificationContent = `\n minifyEnabled ${desiredMinify}${minificationContent}`;
|
|
967
|
-
console.log(`✅ Inserted minifyEnabled ${desiredMinify} into release block.`);
|
|
968
|
-
}
|
|
969
|
-
updatedGradleContent = updatedGradleContent.slice(0, minificationBlock.start)
|
|
970
|
-
+ minificationContent + updatedGradleContent.slice(minificationBlock.end);
|
|
971
|
-
|
|
972
|
-
// Add native debug symbols for release AABs, but leave any existing setting untouched.
|
|
973
|
-
const releaseBlock = findGradleBlock(updatedGradleContent, /buildTypes\s*\{[\s\S]*?\brelease\s*/);
|
|
974
|
-
if (releaseBlock) {
|
|
975
|
-
const releaseContent = updatedGradleContent.slice(releaseBlock.start, releaseBlock.end);
|
|
976
|
-
if (/\bdebugSymbolLevel\b/.test(releaseContent)) {
|
|
977
|
-
console.log('ℹ️ debugSymbolLevel already exists in buildTypes.release; leaving it unchanged.');
|
|
978
|
-
} else {
|
|
979
|
-
const ndkBlock = `\n ndk {\n debugSymbolLevel 'FULL'\n }`;
|
|
980
|
-
updatedGradleContent = updatedGradleContent.slice(0, releaseBlock.end)
|
|
981
|
-
+ ndkBlock
|
|
982
|
-
+ updatedGradleContent.slice(releaseBlock.end);
|
|
983
|
-
console.log("✅ Added debugSymbolLevel 'FULL' to buildTypes.release.");
|
|
984
|
-
}
|
|
985
|
-
} else {
|
|
986
|
-
console.log('⚠️ Warning: buildTypes.release block not found. debugSymbolLevel was not added.');
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
// Write the updated gradle content back to build.gradle
|
|
995
|
-
writeFileSync(gradleFilePath, updatedGradleContent, 'utf8');
|
|
996
|
-
console.log(`✅ Updated build.gradle with versionCode: ${finalVersionCode}, versionName: ${finalVersionName}, resConfigs "en" and "minifyEnabled true"`);
|
|
997
|
-
|
|
998
|
-
let baselineProfileChecked = false;
|
|
999
|
-
for (const id of storeIds) {
|
|
1000
|
-
console.log(`ℹ️ Building for Store ID ${id}`);
|
|
1001
|
-
|
|
1002
|
-
// Set the environment variable for store ID
|
|
1003
|
-
process.env.VITE_STORE_ID = id;
|
|
1004
|
-
|
|
1005
|
-
// Conditionally set the new file name
|
|
1006
|
-
let newFileName;
|
|
1007
|
-
let storeName = storeNames[id];
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
if (
|
|
1011
|
-
isOnlyOneAPK &&
|
|
1012
|
-
["OppoStore", "VivoStore"].includes(storeName)
|
|
1013
|
-
) {
|
|
1014
|
-
console.log(`⏭ Skipping ${storeName} because isOnlyOneAPK is enabled`);
|
|
1015
|
-
continue; // skip this iteration only
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
// build:storeid runs the shared before-build package/permission preparation.
|
|
1020
|
-
|
|
1021
|
-
// 🔥 Only change filename logic — nothing else touched
|
|
1022
|
-
if (isOnlyOneAPK && ["MiStore","OppoStore","VivoStore"].includes(storeName)) {
|
|
1023
|
-
|
|
1024
|
-
newFileName =
|
|
1025
|
-
`${_appUniqueId}_${_appName.replaceAll(" ","_")}-VI_MI_OPPO-b${finalVersionCode}-v${finalVersionName.replace(/\./g,'_')}.aab`;
|
|
1026
|
-
|
|
1027
|
-
}
|
|
1028
|
-
else if (storeName === "SamsungStore") {
|
|
1029
|
-
|
|
1030
|
-
newFileName =
|
|
1031
|
-
`${_appUniqueId}_${_appName.replaceAll(" ","_")}-${(storeName.toUpperCase()).replace("STORE","")}-b${finalVersionCode}-v${finalVersionName.replace(/\./g,'_')}.aab`;
|
|
1032
|
-
|
|
1033
|
-
}
|
|
1034
|
-
else {
|
|
1035
|
-
|
|
1036
|
-
newFileName =
|
|
1037
|
-
`${_appUniqueId}_${_appName.replaceAll(" ","_")}-${(storeName.toUpperCase()).replace("STORE","")}-b${finalVersionCode}-v${finalVersionName}.aab`;
|
|
1038
|
-
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
//storeName="amazon"
|
|
1042
|
-
const checkFullPath = join("AAB", newFileName); // Update to point to the new AAB directory
|
|
1043
|
-
|
|
1044
|
-
// Modify minSdkVersion in variables.gradle for SamsungStore
|
|
1045
|
-
const variablesGradleFilePath = join("android", "variables.gradle");
|
|
1046
|
-
let variablesGradleContent = readFileSync(variablesGradleFilePath, 'utf8');
|
|
1047
|
-
|
|
1048
|
-
// Extract the current minSdkVersion
|
|
1049
|
-
const minSdkVersionMatch = variablesGradleContent.match(/minSdkVersion\s*=\s*(\d+)/);
|
|
1050
|
-
const currentMinSdkVersion = minSdkVersionMatch ? parseInt(minSdkVersionMatch[1], 10) : null;
|
|
1051
|
-
|
|
1052
|
-
// Store the original minSdkVersion (only on the first iteration)
|
|
1053
|
-
if (!originalMinSdkVersion) {
|
|
1054
|
-
originalMinSdkVersion = currentMinSdkVersion;
|
|
1055
|
-
}
|
|
1056
|
-
try {
|
|
1057
|
-
applyFlowchartStoreApi(id);
|
|
1058
|
-
const shouldIncludeAmazonWindowExtensions = String(id) === '7' && amazon === true;
|
|
1059
|
-
setAmazonWindowExtensionsDependency(shouldIncludeAmazonWindowExtensions);
|
|
1060
|
-
// Modify the minSdkVersion based on the store
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
if(currentMinSdkVersion==23 || currentMinSdkVersion==24)
|
|
1064
|
-
{
|
|
1065
|
-
if (storeName === "SamsungStore" || storeName === "PlayStore" ||
|
|
1066
|
-
//_appPackageId=="vfx.green.editor" || _appPackageId=="audio.music.sound.editor" || _appPackageId=="video.to.gif.maker"
|
|
1067
|
-
|
|
1068
|
-
//ffmpegUsedPackages.includes(_appPackageId)
|
|
1069
|
-
_isffmpegUsed
|
|
1070
|
-
) {
|
|
1071
|
-
if (currentMinSdkVersion !== 24) {
|
|
1072
|
-
variablesGradleContent = variablesGradleContent.replace(/minSdkVersion\s*=\s*\d+/, 'minSdkVersion = 24');
|
|
1073
|
-
console.log('minSdkVersion updated to 24 for SamsungStore & PlayStore');
|
|
1074
|
-
writeFileSync(variablesGradleFilePath, variablesGradleContent);
|
|
1075
|
-
}
|
|
1076
|
-
} else {
|
|
1077
|
-
// For PlayStore and AmazonStore, ensure minSdkVersion is originalMinSdkVersion
|
|
1078
|
-
//if (currentMinSdkVersion !== originalMinSdkVersion) {
|
|
1079
|
-
variablesGradleContent = variablesGradleContent.replace(/minSdkVersion\s*=\s*\d+/, `minSdkVersion = ${amazonMinSdkVersion}`);
|
|
1080
|
-
console.log(`minSdkVersion reverted to ${amazonMinSdkVersion} for ${storeName}`);
|
|
1081
|
-
writeFileSync(variablesGradleFilePath, variablesGradleContent);
|
|
1082
|
-
//}
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
// Run the Node.js script to modify plugin.xml
|
|
1088
|
-
if (isAdmobFound) {
|
|
1089
|
-
const modifyPluginXmlPath = join('buildCodeplay', 'modify-plugin-xml.js');
|
|
1090
|
-
if (existsSync(modifyPluginXmlPath)) {
|
|
1091
|
-
execSync(`node ${modifyPluginXmlPath}`, { stdio: 'inherit' });
|
|
1092
|
-
} else {
|
|
1093
|
-
console.log("\x1b[33m%s\x1b[0m", "⚠️ modify-plugin-xml.js not found. Skipping old AdMob plugin.xml modification.");
|
|
1094
|
-
}
|
|
1095
|
-
} else {
|
|
1096
|
-
console.log("\x1b[33m%s\x1b[0m", "Seems to Pro Version [No ads found]");
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
// Run the Vite build
|
|
1100
|
-
execSync(`npm run build:storeid${id}`, { stdio: 'inherit' });
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
// Copy the built files to the appropriate folder
|
|
1108
|
-
const src = join("www");
|
|
1109
|
-
const dest = join("android", "app", "src", "main", "assets", "public");
|
|
1110
|
-
|
|
1111
|
-
// Copy the web build with Node so releases work on every platform.
|
|
1112
|
-
fs.cpSync(src, dest, { recursive: true, force: true });
|
|
1113
|
-
|
|
1114
|
-
// Build Android AAB file
|
|
1115
|
-
//child_process.execSync('cd android && ./gradlew bundleRelease', { stdio: 'inherit' });
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
// Build Android AAB or APK file based on store
|
|
1119
|
-
console.log(`🏗️ Building release for ${storeName}...`);
|
|
1120
|
-
|
|
1121
|
-
/* let buildType = "AAB";
|
|
1122
|
-
if (["VivoStore", "OppoStore", "MiStore"].includes(storeName)) {
|
|
1123
|
-
buildType = "APK";
|
|
1124
|
-
} */
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
// Determine build type and signing type per store
|
|
1128
|
-
let buildType = ["VivoStore","OppoStore","MiStore"].includes(storeName) ? "APK" : "AAB";
|
|
1129
|
-
let signingType = buildType === "APK" ? "apksigner" : "jarsigner";
|
|
1130
|
-
|
|
1131
|
-
// Update capacitor.config.json for this store - This is not needed
|
|
1132
|
-
//updateCapacitorConfig(buildType, signingType);
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
/* execSync('npx cap sync android', { stdio: 'inherit' });
|
|
1137
|
-
execSync(`npx cap build android --androidreleasetype=${buildType}`, { stdio: 'inherit' }); */
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
execSync('npx cap sync android', { stdio: 'inherit' });
|
|
1141
|
-
|
|
1142
|
-
if (!baselineProfileChecked) {
|
|
1143
|
-
await checkBaselineProfileBeforeRelease();
|
|
1144
|
-
baselineProfileChecked = true;
|
|
1145
|
-
}
|
|
1146
|
-
ensureReleaseCodeOptimization();
|
|
1147
|
-
|
|
1148
|
-
if (buildType === "APK") {
|
|
1149
|
-
console.log("📦 Using custom Node signing script for APK build...");
|
|
1150
|
-
execFileSync(process.execPath, [join(process.cwd(), 'buildCodeplay', 'apk-store-builder.js')], { stdio: 'inherit' });
|
|
1151
|
-
} else {
|
|
1152
|
-
execSync(`npx cap build android --androidreleasetype=${buildType}`, { stdio: 'inherit' });
|
|
1153
|
-
signReleaseBundleWithSha256();
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
// Determine output paths
|
|
1159
|
-
let oldFilePath;
|
|
1160
|
-
let newExt = buildType === "APK" ? "apk" : "aab";
|
|
1161
|
-
|
|
1162
|
-
if (buildType === "APK") {
|
|
1163
|
-
//oldFilePath = join("android", "app", "build", "outputs", "apk", "release", "app-release-signed.apk");
|
|
1164
|
-
oldFilePath = join("android", "app", "build", "outputs", "apk", "release", "app-release.apk");
|
|
1165
|
-
} else {
|
|
1166
|
-
oldFilePath = join("android", "app", "build", "outputs", "bundle", "release", "app-release-signed.aab");
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
const checkFullPath = join("AAB", newFileName.replace(/\.aab$/, `.${newExt}`));
|
|
1170
|
-
|
|
1171
|
-
// Rename the output file
|
|
1172
|
-
if (existsSync(oldFilePath)) {
|
|
1173
|
-
renameSync(oldFilePath, checkFullPath);
|
|
1174
|
-
console.log(`✅ Renamed output ${newExt.toUpperCase()} file to: ${path.basename(checkFullPath)}`);
|
|
1175
|
-
syncReleaseToCodeplay(storeName, finalVersionName, finalVersionCode, new Date().toISOString().slice(0, 10));
|
|
1176
|
-
} else {
|
|
1177
|
-
console.error(`❌ ${newExt.toUpperCase()} file not found after build.`);
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
} catch (error) {
|
|
1181
|
-
console.error(`❌ Error during build for Store ID ${id}:`, error);
|
|
1182
|
-
restoreFlowchartStoreApi();
|
|
1183
|
-
setAmazonWindowExtensionsDependency(false, {
|
|
1184
|
-
cleanup: String(id) === '7' && amazon === true,
|
|
1185
|
-
});
|
|
1186
|
-
process.exit(1);
|
|
1187
|
-
} finally {
|
|
1188
|
-
restoreFlowchartStoreApi();
|
|
1189
|
-
setAmazonWindowExtensionsDependency(false, {
|
|
1190
|
-
cleanup: String(id) === '7' && amazon === true,
|
|
1191
|
-
});
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
rl.close(); // Close the readline interface after all operations
|
|
1196
|
-
});
|
|
1197
|
-
});
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
/* function updateAndroidManifest1(store, addPermission) {
|
|
1204
|
-
try {
|
|
1205
|
-
if (!fs.existsSync(androidManifestPath)) {
|
|
1206
|
-
console.error("AndroidManifest.xml file not found!");
|
|
1207
|
-
return;
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
let manifestContent = fs.readFileSync(androidManifestPath, 'utf-8');
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
// Add the required permission if not already present
|
|
1215
|
-
if (!manifestContent.includes(addPermission)) {
|
|
1216
|
-
const manifestLines = manifestContent.split('\n');
|
|
1217
|
-
const insertIndex = manifestLines.findIndex(line => line.trim().startsWith('<application'));
|
|
1218
|
-
if (insertIndex > -1) {
|
|
1219
|
-
manifestLines.splice(insertIndex, 0, ` ${addPermission}`);
|
|
1220
|
-
manifestContent = manifestLines.join('\n');
|
|
1221
|
-
console.log(`Added ${addPermission} to AndroidManifest.xml`);
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
// Write the updated content back to the file
|
|
1226
|
-
fs.writeFileSync(androidManifestPath, manifestContent, 'utf-8');
|
|
1227
|
-
console.log(`AndroidManifest.xml updated successfully for ${store}`);
|
|
1228
|
-
} catch (err) {
|
|
1229
|
-
console.error(`Error updating AndroidManifest.xml for ${store}:`, err);
|
|
1230
|
-
}
|
|
1231
|
-
} */
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1
|
+
/*
|
|
2
|
+
Can be run like
|
|
3
|
+
node finalrelease
|
|
4
|
+
node finalrelease 1
|
|
5
|
+
node finalrelease "1,2,7"
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync, readdirSync, promises, constants, writeFileSync, unlinkSync, mkdirSync, renameSync } from 'fs';
|
|
9
|
+
import { join, resolve } from 'path';
|
|
10
|
+
import { execSync, execFileSync } from 'child_process';
|
|
11
|
+
import { createInterface } from 'readline';
|
|
12
|
+
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
import { dirname } from 'path';
|
|
15
|
+
|
|
16
|
+
/* const path = require('path');
|
|
17
|
+
const fs = require('fs'); */
|
|
18
|
+
|
|
19
|
+
import fs from 'fs';
|
|
20
|
+
import path from 'path';
|
|
21
|
+
|
|
22
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
23
|
+
const __dirname = process.cwd();
|
|
24
|
+
|
|
25
|
+
function stripJavaScriptComments(content) {
|
|
26
|
+
let result = '';
|
|
27
|
+
let state = 'code';
|
|
28
|
+
|
|
29
|
+
for (let index = 0; index < content.length; index++) {
|
|
30
|
+
const character = content[index];
|
|
31
|
+
const nextCharacter = content[index + 1];
|
|
32
|
+
|
|
33
|
+
if (state === 'lineComment') {
|
|
34
|
+
if (character === '\n') {
|
|
35
|
+
result += character;
|
|
36
|
+
state = 'code';
|
|
37
|
+
} else {
|
|
38
|
+
result += ' ';
|
|
39
|
+
}
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (state === 'blockComment') {
|
|
44
|
+
if (character === '*' && nextCharacter === '/') {
|
|
45
|
+
result += ' ';
|
|
46
|
+
index++;
|
|
47
|
+
state = 'code';
|
|
48
|
+
} else {
|
|
49
|
+
result += character === '\n' ? '\n' : ' ';
|
|
50
|
+
}
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (state !== 'code') {
|
|
55
|
+
result += character;
|
|
56
|
+
|
|
57
|
+
if (character === '\\') {
|
|
58
|
+
if (nextCharacter !== undefined) {
|
|
59
|
+
result += nextCharacter;
|
|
60
|
+
index++;
|
|
61
|
+
}
|
|
62
|
+
} else if (
|
|
63
|
+
(state === 'singleQuote' && character === "'") ||
|
|
64
|
+
(state === 'doubleQuote' && character === '"') ||
|
|
65
|
+
(state === 'templateLiteral' && character === '`')
|
|
66
|
+
) {
|
|
67
|
+
state = 'code';
|
|
68
|
+
}
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (character === '/' && nextCharacter === '/') {
|
|
73
|
+
result += ' ';
|
|
74
|
+
index++;
|
|
75
|
+
state = 'lineComment';
|
|
76
|
+
} else if (character === '/' && nextCharacter === '*') {
|
|
77
|
+
result += ' ';
|
|
78
|
+
index++;
|
|
79
|
+
state = 'blockComment';
|
|
80
|
+
} else {
|
|
81
|
+
result += character;
|
|
82
|
+
|
|
83
|
+
if (character === "'") state = 'singleQuote';
|
|
84
|
+
else if (character === '"') state = 'doubleQuote';
|
|
85
|
+
else if (character === '`') state = 'templateLiteral';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function checkProductionAdmobAlias() {
|
|
93
|
+
const capacitorConfig = JSON.parse(readFileSync(resolve(__dirname, 'capacitor.config.json'), 'utf8'));
|
|
94
|
+
if (capacitorConfig.plugins?.AdMob?.ADMOB_ENABLED === false) {
|
|
95
|
+
console.log('ℹ️ AdMob is disabled; production @admob alias validation skipped.');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const possibleConfigFiles = ['vite.config.mjs', 'vite.config.js'];
|
|
100
|
+
const viteConfigPath = possibleConfigFiles
|
|
101
|
+
.map(configFile => resolve(__dirname, configFile))
|
|
102
|
+
.find(filePath => existsSync(filePath));
|
|
103
|
+
|
|
104
|
+
if (!viteConfigPath) {
|
|
105
|
+
console.error('❌ FINAL RELEASE BLOCKED: No vite.config.mjs or vite.config.js file found.');
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const viteConfigContent = stripJavaScriptComments(readFileSync(viteConfigPath, 'utf8'));
|
|
110
|
+
const aliasPattern = /(['"])@admob\1\s*:\s*path\.resolve\(\s*__dirname\s*,\s*(['"])([^'"\r\n]+)\2\s*\)/g;
|
|
111
|
+
const activeAliases = [...viteConfigContent.matchAll(aliasPattern)].map(match => match[3]);
|
|
112
|
+
const productionAliasPattern = /^\.\/src\/js\/Ads\/(?:admob-nextgen-\d+(?:\.\d+)+\/admob-emi-nextgen|admob-emi-nextgen-\d+(?:\.\d+)+)\.js$/;
|
|
113
|
+
|
|
114
|
+
if (activeAliases.length === 0) {
|
|
115
|
+
console.error('❌ FINAL RELEASE BLOCKED: The production @admob alias is commented out or missing in the Vite config.');
|
|
116
|
+
console.error(" Enable: '@admob': path.resolve(__dirname, './src/js/Ads/admob-nextgen-x.x/admob-emi-nextgen.js')");
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (activeAliases.length > 1) {
|
|
121
|
+
console.error('❌ FINAL RELEASE BLOCKED: Multiple active @admob aliases were found in the Vite config.');
|
|
122
|
+
console.error(' Keep exactly one active production @admob alias.');
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const activeAlias = activeAliases[0];
|
|
127
|
+
if (!productionAliasPattern.test(activeAlias)) {
|
|
128
|
+
console.error(`❌ FINAL RELEASE BLOCKED: @admob currently points to '${activeAlias}'.`);
|
|
129
|
+
console.error(" It must point to './src/js/Ads/admob-nextgen-x.x/admob-emi-nextgen.js', not a test AdMob file.");
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const productionAdmobPath = resolve(__dirname, activeAlias);
|
|
134
|
+
if (!existsSync(productionAdmobPath)) {
|
|
135
|
+
console.error(`❌ FINAL RELEASE BLOCKED: The production @admob file does not exist: ${productionAdmobPath}`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log(`✅ Production @admob alias verified: ${activeAlias}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Validate the production alias first when AdMob is enabled.
|
|
143
|
+
checkProductionAdmobAlias();
|
|
144
|
+
|
|
145
|
+
// Define a mapping between store IDs and store names
|
|
146
|
+
/* const storeNames = {
|
|
147
|
+
"1": "PlayStore",
|
|
148
|
+
"2": "SamsungStore",
|
|
149
|
+
"7": "AmazonStore"
|
|
150
|
+
}; */
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
const isOnlyOneAPK = false; // 🔥 change to false for normal behavior
|
|
154
|
+
|
|
155
|
+
const storeNames = {
|
|
156
|
+
"1": "PlayStore",
|
|
157
|
+
"2": "SamsungStore",
|
|
158
|
+
"7": "AmazonStore",
|
|
159
|
+
|
|
160
|
+
"3": "MiStore",
|
|
161
|
+
"4": "HuaweiStore",
|
|
162
|
+
"5": "OppoStore",
|
|
163
|
+
//"6": "iOSStore",
|
|
164
|
+
"8": "VivoStore"
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
//1=> PlayStore 2=> SamsungStore 3=>MiStore 4=>HuaweiStore 5=>OppoStore 6=>iOSStore 7=> AmazonStore 8=> VivoStore
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
let isAdmobFound = false;
|
|
177
|
+
|
|
178
|
+
const amazonMinSdkVersion=24;
|
|
179
|
+
|
|
180
|
+
const androidManifestPath = join("android", "app", "src", "main", "AndroidManifest.xml");
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
const configPath = path.join(process.cwd(), 'capacitor.config.json');
|
|
184
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
185
|
+
|
|
186
|
+
const _appUniqueId = config.android?.APP_UNIQUE_ID;
|
|
187
|
+
const _appName=config.appName;
|
|
188
|
+
const _appPackageId=config.appId;
|
|
189
|
+
|
|
190
|
+
const appReleaseSyncConfigPath = join(process.cwd(), 'buildCodeplay', 'release-sync.config.json');
|
|
191
|
+
const releaseSyncConfigPath = appReleaseSyncConfigPath;
|
|
192
|
+
let releaseSyncConfig = {};
|
|
193
|
+
if (existsSync(releaseSyncConfigPath)) {
|
|
194
|
+
try {
|
|
195
|
+
releaseSyncConfig = JSON.parse(readFileSync(releaseSyncConfigPath, 'utf8'));
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.warn(`⚠️ Could not read ${releaseSyncConfigPath}: ${error.message}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const RELEASE_SYNC_URL = process.env.CODEPLAY_RELEASE_SYNC_URL || releaseSyncConfig.url || 'https://htmlcodeplay.com/codeplay/api/v1/release/sync';
|
|
201
|
+
const RELEASE_SYNC_TOKEN = process.env.CODEPLAY_RELEASE_SYNC_TOKEN || releaseSyncConfig.token || '';
|
|
202
|
+
const RELEASE_PLATFORM_IDS = { PlayStore: 1, SamsungStore: 13, AmazonStore: 17, MiStore: 15, HuaweiStore: 12, OppoStore: 14, VivoStore: 16 };
|
|
203
|
+
|
|
204
|
+
function syncReleaseToCodeplay(storeName, finalVersionName, finalVersionCode, releasedDate) {
|
|
205
|
+
if (!RELEASE_SYNC_TOKEN) {
|
|
206
|
+
console.warn(`⚠️ Codeplay release sync skipped: add a token to ${releaseSyncConfigPath}.`);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const payload = JSON.stringify({
|
|
210
|
+
app_name: _appName,
|
|
211
|
+
package_id: _appPackageId,
|
|
212
|
+
version_name: String(finalVersionName),
|
|
213
|
+
version_code: String(finalVersionCode),
|
|
214
|
+
released_date: releasedDate,
|
|
215
|
+
language_id: 0,
|
|
216
|
+
language_name: 'Capacitor+ Framework 7',
|
|
217
|
+
notes: 'Automated release update',
|
|
218
|
+
status: 1,
|
|
219
|
+
folder_name: path.basename(process.cwd()),
|
|
220
|
+
app_unique_id: String(_appUniqueId || ''),
|
|
221
|
+
platform_id: RELEASE_PLATFORM_IDS[storeName],
|
|
222
|
+
});
|
|
223
|
+
try {
|
|
224
|
+
const response = execFileSync('curl.exe', ['-sS', '--fail-with-body', '-X', 'POST', RELEASE_SYNC_URL,
|
|
225
|
+
'-H', 'Content-Type: application/json', '-H', `X-Release-Token: ${RELEASE_SYNC_TOKEN}`, '--data-binary', payload],
|
|
226
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
227
|
+
console.log(`✅ Codeplay sync (${storeName}): ${response.trim()}`);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.warn(`⚠️ Codeplay sync failed for ${storeName}; continuing release builds: ${error.message}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const FLOWCHART_API_APP_ID = 228;
|
|
234
|
+
const FLOWCHART_SHARED_PATH = join('public', 'src', 'shared.js');
|
|
235
|
+
const FLOWCHART_PLAY_API_BASE = 'https://htmlcodeplay.com/appfiles/128FlowChart';
|
|
236
|
+
const FLOWCHART_OTHER_STORE_API_BASE = 'https://htmlcodeplay.com/appfiles/128FlowChart/otherapi';
|
|
237
|
+
const FLOWCHART_OTHER_STORE_API_HASH = 'd1a3774529b74206ae2117f9c25f258e485a1f7348620e769db19c2796aa1fdf';
|
|
238
|
+
const flowchartSharedOriginal = existsSync(FLOWCHART_SHARED_PATH)
|
|
239
|
+
? readFileSync(FLOWCHART_SHARED_PATH, 'utf8')
|
|
240
|
+
: null;
|
|
241
|
+
|
|
242
|
+
function applyFlowchartStoreApi(storeId) {
|
|
243
|
+
if (String(_appUniqueId) !== String(FLOWCHART_API_APP_ID) || !flowchartSharedOriginal) return false;
|
|
244
|
+
const apiBase = String(storeId) === '1' ? FLOWCHART_PLAY_API_BASE : FLOWCHART_OTHER_STORE_API_BASE;
|
|
245
|
+
const accessHash = String(storeId) === '1' ? '' : FLOWCHART_OTHER_STORE_API_HASH;
|
|
246
|
+
const apiBaseMarker = /const FLOWCHART_API_BASE = "[^"]*";/;
|
|
247
|
+
const accessHashMarker = /const FLOWCHART_API_ACCESS_HASH = "[^"]*";/;
|
|
248
|
+
|
|
249
|
+
if (!apiBaseMarker.test(flowchartSharedOriginal) || !accessHashMarker.test(flowchartSharedOriginal)) {
|
|
250
|
+
throw new Error(`Flowchart API configuration markers were not found in ${FLOWCHART_SHARED_PATH}.`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const updated = flowchartSharedOriginal
|
|
254
|
+
.replace(apiBaseMarker, `const FLOWCHART_API_BASE = "${apiBase}";`)
|
|
255
|
+
.replace(accessHashMarker, `const FLOWCHART_API_ACCESS_HASH = "${accessHash}";`);
|
|
256
|
+
writeFileSync(FLOWCHART_SHARED_PATH, updated, 'utf8');
|
|
257
|
+
console.log(`✅ App 228 API endpoint configured for Store ID ${storeId}.`);
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function restoreFlowchartStoreApi() {
|
|
262
|
+
if (String(_appUniqueId) === String(FLOWCHART_API_APP_ID) && flowchartSharedOriginal) {
|
|
263
|
+
writeFileSync(FLOWCHART_SHARED_PATH, flowchartSharedOriginal, 'utf8');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
function fileExists(filePath) {
|
|
270
|
+
return existsSync(filePath);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
const ffmpegPluginXmlPath = path.join(__dirname, 'node_modules', 'codeplay-fmpg', 'plugin.xml');
|
|
275
|
+
const capacitorConfigPath = join(process.cwd(), 'capacitor.config.json');
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
function isFfmpegUsed() {
|
|
280
|
+
try {
|
|
281
|
+
const xmlContent = fs.readFileSync(ffmpegPluginXmlPath, 'utf8');
|
|
282
|
+
// Simple check: if plugin.xml exists and contains <plugin> tag (or any FFmpeg-specific tag)
|
|
283
|
+
return /<plugin\b/.test(xmlContent);
|
|
284
|
+
} catch (err) {
|
|
285
|
+
console.log('⚠️ FFmpeg plugin not found:', err.message);
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const _isffmpegUsed = isFfmpegUsed();
|
|
291
|
+
|
|
292
|
+
function getAdMobConfig() {
|
|
293
|
+
if (!fileExists(capacitorConfigPath)) {
|
|
294
|
+
throw new Error('❌ capacitor.config.json not found. Ensure this is a Capacitor project.');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const config = JSON.parse(readFileSync(capacitorConfigPath, 'utf8'));
|
|
298
|
+
const admobConfig = config.plugins?.AdMob;
|
|
299
|
+
|
|
300
|
+
//_appPackageId=config.appId;
|
|
301
|
+
|
|
302
|
+
if (!admobConfig) {
|
|
303
|
+
throw new Error('❌ AdMob configuration is missing in capacitor.config.json.');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Default to true if ADMOB_ENABLED is not specified
|
|
307
|
+
const isEnabled = admobConfig.ADMOB_ENABLED !== false;
|
|
308
|
+
|
|
309
|
+
if (!isEnabled) {
|
|
310
|
+
return { ADMOB_ENABLED: false }; // Skip further validation
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (!admobConfig.APP_ID_ANDROID || !admobConfig.APP_ID_IOS) {
|
|
314
|
+
throw new Error(' ❌ AdMob configuration is incomplete. Ensure APP_ID_ANDROID and APP_ID_IOS are defined.');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
ADMOB_ENABLED: true,
|
|
319
|
+
APP_ID_ANDROID: admobConfig.APP_ID_ANDROID,
|
|
320
|
+
APP_ID_IOS: admobConfig.APP_ID_IOS,
|
|
321
|
+
USE_LITE_ADS: admobConfig.USE_LITE_ADS === "lite",
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
const _capacitorConfig = getAdMobConfig();
|
|
328
|
+
|
|
329
|
+
const _isADMOB_ENABLED=_capacitorConfig.ADMOB_ENABLED;
|
|
330
|
+
isAdmobFound = _isADMOB_ENABLED;
|
|
331
|
+
|
|
332
|
+
// Proceed only if ADMOB_ENABLED is true
|
|
333
|
+
//if (_capacitorConfig.ADMOB_ENABLED)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
const admobConfigPath = join('src', 'js','Ads', 'admob-ad-configuration-nextgen.json');
|
|
337
|
+
let admobConfig;
|
|
338
|
+
|
|
339
|
+
if (_isADMOB_ENABLED)
|
|
340
|
+
{
|
|
341
|
+
try
|
|
342
|
+
{
|
|
343
|
+
admobConfig = JSON.parse(readFileSync(admobConfigPath, 'utf8'));
|
|
344
|
+
}
|
|
345
|
+
catch (err)
|
|
346
|
+
{
|
|
347
|
+
console.error("❌ Failed to read admob-ad-configuration.json", err);
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
function updateIOSVersion(versionCode, versionName) {
|
|
357
|
+
|
|
358
|
+
const pbxprojPath = join("ios", "App", "App.xcodeproj", "project.pbxproj");
|
|
359
|
+
|
|
360
|
+
// 🔥 Single check only
|
|
361
|
+
if (!existsSync(pbxprojPath)) {
|
|
362
|
+
console.warn("⚠️ iOS project not found, skipping version update");
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
let content = readFileSync(pbxprojPath, 'utf8');
|
|
368
|
+
|
|
369
|
+
// Update MARKETING_VERSION
|
|
370
|
+
content = content.replace(
|
|
371
|
+
/MARKETING_VERSION = .*?;/g,
|
|
372
|
+
`MARKETING_VERSION = ${versionName};`
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
// Update CURRENT_PROJECT_VERSION
|
|
376
|
+
/* content = content.replace(
|
|
377
|
+
/CURRENT_PROJECT_VERSION = .*?;/g,
|
|
378
|
+
`CURRENT_PROJECT_VERSION = ${versionCode};`
|
|
379
|
+
); */
|
|
380
|
+
|
|
381
|
+
writeFileSync(pbxprojPath, content, 'utf8');
|
|
382
|
+
|
|
383
|
+
console.log(`🍏 iOS updated via Xcode settings → version: ${versionName}, build: ${versionCode}`);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
const checkCommonFileStoreId=()=>{
|
|
407
|
+
const possibleConfigFiles = ['vite.config.mjs', 'vite.config.js'];
|
|
408
|
+
let viteConfigPath;
|
|
409
|
+
for (const configFile of possibleConfigFiles) {
|
|
410
|
+
const fullPath = resolve( configFile);
|
|
411
|
+
if (existsSync(fullPath)) {
|
|
412
|
+
viteConfigPath = fullPath;
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (!viteConfigPath) {
|
|
418
|
+
console.error('❌ Error: No vite.config.mjs or vite.config.js file found.');
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
// Read vite config file
|
|
424
|
+
const viteConfigContent = readFileSync(viteConfigPath, 'utf-8');
|
|
425
|
+
|
|
426
|
+
// Extract @common alias path
|
|
427
|
+
const aliasPattern = /'@common':\s*path\.resolve\(__dirname,\s*'(.+?)'\)/;
|
|
428
|
+
const match = viteConfigContent.match(aliasPattern);
|
|
429
|
+
|
|
430
|
+
if (!match) {
|
|
431
|
+
console.error(`❌ Error: @common alias not found in ${viteConfigPath}`);
|
|
432
|
+
process.exit(1);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const commonFilePath = match[1];
|
|
436
|
+
const resolvedCommonPath = resolve(__dirname, commonFilePath);
|
|
437
|
+
|
|
438
|
+
// Read the common file content
|
|
439
|
+
if (!existsSync(resolvedCommonPath)) {
|
|
440
|
+
console.error(`❌ Error: Resolved common file does not exist: ${resolvedCommonPath}`);
|
|
441
|
+
process.exit(1);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const commonFileContent = readFileSync(resolvedCommonPath, 'utf-8');
|
|
445
|
+
|
|
446
|
+
// Check for the _storeid export line
|
|
447
|
+
const storeIdPattern = /export\s+let\s+_storeid\s*=\s*import\.meta\.env\.VITE_STORE_ID\s*\|\|\s*1\s*;/;
|
|
448
|
+
if (!storeIdPattern.test(commonFileContent)) {
|
|
449
|
+
console.error(`❌ Error: _storeid value is wrong in ${commonFilePath}`);
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
console.log(commonFilePath,'Success - No problem found');
|
|
454
|
+
} catch (error) {
|
|
455
|
+
console.error('❌ Error:', error);
|
|
456
|
+
process.exit(1);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const checkIsTestingInAdmob=()=>{
|
|
462
|
+
|
|
463
|
+
if (admobConfig.config && admobConfig.config.isTesting === true) {
|
|
464
|
+
console.error(`❌ Problem found while generating the AAB file. Please change "isTesting: true" to "isTesting: false" in the "admob-ad-configuration-nextgen.json" file.`);
|
|
465
|
+
process.exit(1); // Exit with an error code to halt the process
|
|
466
|
+
} else {
|
|
467
|
+
console.log('✅ No problem found. "isTesting" is either already false or not defined.');
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const checkIsAdsDisableByReturnStatement=()=>{
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
// Inspect the controller selected by the validated production alias.
|
|
475
|
+
const viteFile = ['vite.config.mjs', 'vite.config.js']
|
|
476
|
+
.map(file => resolve(__dirname, file)).find(file => existsSync(file));
|
|
477
|
+
const config = stripJavaScriptComments(readFileSync(viteFile, 'utf8'));
|
|
478
|
+
const alias = config.match(/(['"])@admob\1\s*:\s*path\.resolve\(\s*__dirname\s*,\s*(['"])([^'"\r\n]+)\2\s*\)/);
|
|
479
|
+
const filePath = resolve(__dirname, alias[3]);
|
|
480
|
+
const admobFile = alias[3];
|
|
481
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
482
|
+
|
|
483
|
+
// Step 2: Extract the adsOnDeviceReady function body
|
|
484
|
+
const functionRegex = /async\s+function\s+adsOnDeviceReady\s*\([^)]*\)\s*{([\s\S]*?)^}/m;
|
|
485
|
+
const match = content.match(functionRegex);
|
|
486
|
+
|
|
487
|
+
if (!match) {
|
|
488
|
+
console.log(`❌ Function 'adsOnDeviceReady' not found in file: ${admobFile}`);
|
|
489
|
+
process.exit(1);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const body = match[1];
|
|
493
|
+
const lines = body.split('\n').map(line => line.trim());
|
|
494
|
+
|
|
495
|
+
// Step 3: Skip blank lines and comments, get the first real code line
|
|
496
|
+
let firstCodeLine = '';
|
|
497
|
+
for (const line of lines) {
|
|
498
|
+
if (line === '' || line.startsWith('//')) continue;
|
|
499
|
+
firstCodeLine = line;
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Step 4: Block if it's any of the unwanted returns
|
|
504
|
+
const badReturnPattern = /^return\s*(true|false)?\s*;?$/;
|
|
505
|
+
|
|
506
|
+
if (badReturnPattern.test(firstCodeLine)) {
|
|
507
|
+
console.log(`❌ BLOCKED in file '${admobFile}': First active line in 'adsOnDeviceReady' is '${firstCodeLine}'`);
|
|
508
|
+
process.exit(2);
|
|
509
|
+
} else {
|
|
510
|
+
console.log(`✅ Safe: No early return (true/false) found in 'adsOnDeviceReady' of file '${admobFile}'.`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
const addPermission_AD_ID=async()=>{
|
|
517
|
+
|
|
518
|
+
if (isAdmobFound) {
|
|
519
|
+
if (existsSync(androidManifestPath)) {
|
|
520
|
+
let manifestContent = readFileSync(androidManifestPath, 'utf8');
|
|
521
|
+
let modified = false;
|
|
522
|
+
|
|
523
|
+
// --- Step 1: Ensure AD_ID permission exists ---
|
|
524
|
+
const adIdPermission = '<uses-permission android:name="com.google.android.gms.permission.AD_ID" />';
|
|
525
|
+
if (!manifestContent.includes(adIdPermission)) {
|
|
526
|
+
console.log("📄 AD_ID permission not found. Adding to AndroidManifest.xml.");
|
|
527
|
+
manifestContent = manifestContent.replace('</manifest>', ` ${adIdPermission}\n</manifest>`);
|
|
528
|
+
console.log("✅ AD_ID permission added successfully.");
|
|
529
|
+
modified = true;
|
|
530
|
+
} else {
|
|
531
|
+
console.log("ℹ️ AD_ID permission already exists in AndroidManifest.xml.");
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// --- Step 2: Ensure OPTIMIZE_AD_LOADING meta-data exists ---
|
|
535
|
+
const optimizeAdMeta = `<meta-data android:name="com.google.android.gms.ads.flag.OPTIMIZE_AD_LOADING" android:value="true" />`;
|
|
536
|
+
|
|
537
|
+
if (!manifestContent.includes(optimizeAdMeta)) {
|
|
538
|
+
console.log("📄 OPTIMIZE_AD_LOADING meta-data not found. Adding to AndroidManifest.xml.");
|
|
539
|
+
|
|
540
|
+
const appTagPattern = /<application[^>]*>/;
|
|
541
|
+
if (appTagPattern.test(manifestContent)) {
|
|
542
|
+
manifestContent = manifestContent.replace(appTagPattern, match => `${match}\n ${optimizeAdMeta}`);
|
|
543
|
+
console.log("✅ OPTIMIZE_AD_LOADING meta-data added successfully.");
|
|
544
|
+
modified = true;
|
|
545
|
+
} else {
|
|
546
|
+
console.error("❌ <application> tag not found in AndroidManifest.xml.");
|
|
547
|
+
}
|
|
548
|
+
} else {
|
|
549
|
+
console.log("ℹ️ OPTIMIZE_AD_LOADING meta-data already exists in AndroidManifest.xml.");
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// --- Step 3: Write only if modified ---
|
|
553
|
+
if (modified) {
|
|
554
|
+
writeFileSync(androidManifestPath, manifestContent, 'utf8');
|
|
555
|
+
console.log("💾 AndroidManifest.xml updated successfully.");
|
|
556
|
+
} else {
|
|
557
|
+
console.log("✅ No changes needed. AndroidManifest.xml is up to date.");
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
} else {
|
|
561
|
+
console.error("❌ AndroidManifest.xml not found at the specified path.");
|
|
562
|
+
}
|
|
563
|
+
} else {
|
|
564
|
+
console.log("\x1b[33m%s\x1b[0m", "⚠️ No AdMob found, so AD_ID permission and meta-data were not added");
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
checkCommonFileStoreId();
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
if (_isADMOB_ENABLED)
|
|
589
|
+
{
|
|
590
|
+
checkIsTestingInAdmob();
|
|
591
|
+
checkIsAdsDisableByReturnStatement()
|
|
592
|
+
|
|
593
|
+
await addPermission_AD_ID()
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
let originalReleaseType, originalSigningType;
|
|
599
|
+
|
|
600
|
+
function readCapacitorConfig() {
|
|
601
|
+
return JSON.parse(fs.readFileSync(capacitorConfigPath, 'utf8'));
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function writeCapacitorConfig(config) {
|
|
605
|
+
fs.writeFileSync(capacitorConfigPath, JSON.stringify(config, null, 2), 'utf8');
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/* function updateCapacitorConfig(releaseType, signingType) {
|
|
609
|
+
const config = readCapacitorConfig();
|
|
610
|
+
|
|
611
|
+
// Save original values once
|
|
612
|
+
if (originalReleaseType === undefined) originalReleaseType = config.android?.buildOptions?.releaseType || '';
|
|
613
|
+
if (originalSigningType === undefined) originalSigningType = config.android?.buildOptions?.signingType || '';
|
|
614
|
+
|
|
615
|
+
// Update values
|
|
616
|
+
config.android = config.android || {};
|
|
617
|
+
config.android.buildOptions = config.android.buildOptions || {};
|
|
618
|
+
config.android.buildOptions.releaseType = releaseType;
|
|
619
|
+
config.android.buildOptions.signingType = signingType;
|
|
620
|
+
|
|
621
|
+
writeCapacitorConfig(config);
|
|
622
|
+
console.log(`ℹ️ capacitor.config.json updated: releaseType=${releaseType}, signingType=${signingType}`);
|
|
623
|
+
} */
|
|
624
|
+
|
|
625
|
+
/* function restoreCapacitorConfig() {
|
|
626
|
+
const config = readCapacitorConfig();
|
|
627
|
+
if (originalReleaseType !== undefined) config.android.buildOptions.releaseType = originalReleaseType;
|
|
628
|
+
if (originalSigningType !== undefined) config.android.buildOptions.signingType = originalSigningType;
|
|
629
|
+
writeCapacitorConfig(config);
|
|
630
|
+
console.log(`✅ capacitor.config.json restored to original values.`);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
process.on('SIGINT', () => {
|
|
634
|
+
console.log('\n⚠️ Detected Ctrl+C, restoring capacitor.config.json...');
|
|
635
|
+
restoreCapacitorConfig();
|
|
636
|
+
process.exit(0);
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
process.on('exit', () => {
|
|
640
|
+
restoreCapacitorConfig();
|
|
641
|
+
}); */
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
const { playstore, samsung, amazon } = (_isADMOB_ENABLED && admobConfig.IAP) ? admobConfig.IAP : { playstore: false, samsung: false, amazon: false };
|
|
646
|
+
console.log(`ℹ️ IAP Configurations - PlayStore: ${playstore}, Samsung: ${samsung}, Amazon: ${amazon}`);
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
// Get the store ID from the command line arguments
|
|
654
|
+
/* const storeIdArg = ["2","3"]//process.argv[2]; // Get the store ID from the command line
|
|
655
|
+
const storeIds = storeIdArg ? storeIdArg : Object.keys(storeNames);//["1", "2", "7"]; // If a specific ID is provided, use it; otherwise, use all store IDs
|
|
656
|
+
|
|
657
|
+
debugger; */
|
|
658
|
+
|
|
659
|
+
let storeIdArg = process.argv[2]; // command-line argument
|
|
660
|
+
let storeIds;
|
|
661
|
+
|
|
662
|
+
if (storeIdArg) {
|
|
663
|
+
try {
|
|
664
|
+
// Try parsing as JSON array first
|
|
665
|
+
const parsed = JSON.parse(storeIdArg);
|
|
666
|
+
if (Array.isArray(parsed)) {
|
|
667
|
+
storeIds = parsed.map(String); // ensure strings
|
|
668
|
+
} else {
|
|
669
|
+
storeIds = [parsed.toString()];
|
|
670
|
+
}
|
|
671
|
+
} catch (e) {
|
|
672
|
+
// Not JSON, assume comma-separated string
|
|
673
|
+
storeIds = storeIdArg.split(",").map(s => s.trim());
|
|
674
|
+
}
|
|
675
|
+
} else {
|
|
676
|
+
// No argument, use all store IDs
|
|
677
|
+
storeIds = Object.keys(storeNames);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
// 🔥 If only one APK mode, override APK storeIds
|
|
682
|
+
/* if (isOnlyOneAPK) {
|
|
683
|
+
console.log("⚡ isOnlyOneAPK enabled → Only MiStore (3) will be used for APK build");
|
|
684
|
+
storeIds = ["3"]; // Only MiStore
|
|
685
|
+
} */
|
|
686
|
+
|
|
687
|
+
// Store the original minSdkVersion globally
|
|
688
|
+
let originalMinSdkVersion;
|
|
689
|
+
|
|
690
|
+
// Remove any existing AAB files before starting the build process
|
|
691
|
+
const aabDirectory = join("android", "app", "build", "outputs", "bundle", "release");
|
|
692
|
+
if (existsSync(aabDirectory)) {
|
|
693
|
+
const files = readdirSync(aabDirectory).filter(file => file.endsWith('.aab'));
|
|
694
|
+
files.forEach(file => {
|
|
695
|
+
const filePath = join(aabDirectory, file);
|
|
696
|
+
unlinkSync(filePath);
|
|
697
|
+
console.log(`ℹ️ Deleted existing AAB file: ${file}`);
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
const aabOutputDir = join("AAB");
|
|
702
|
+
if (!existsSync(aabOutputDir)) {
|
|
703
|
+
mkdirSync(aabOutputDir);
|
|
704
|
+
console.log(`Created directory: ${aabOutputDir}`);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (existsSync(aabOutputDir)) {
|
|
708
|
+
const files = readdirSync(aabOutputDir).filter(
|
|
709
|
+
file => file.endsWith('.aab') || file.endsWith('.apk')
|
|
710
|
+
);
|
|
711
|
+
|
|
712
|
+
files.forEach(file => {
|
|
713
|
+
const filePath = join(aabOutputDir, file);
|
|
714
|
+
unlinkSync(filePath);
|
|
715
|
+
console.log(`Deleted existing build file: ${file}`);
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
// Extract version code and version name from build.gradle
|
|
721
|
+
const gradleFilePath = join("android", "app", "build.gradle");
|
|
722
|
+
const gradleContent = readFileSync(gradleFilePath, 'utf8');
|
|
723
|
+
|
|
724
|
+
// Return the character range of a Gradle block, allowing nested blocks inside it.
|
|
725
|
+
function findGradleBlock(content, headerPattern) {
|
|
726
|
+
const headerMatch = content.match(headerPattern);
|
|
727
|
+
if (!headerMatch) return null;
|
|
728
|
+
const openingBrace = content.indexOf('{', headerMatch.index + headerMatch[0].length);
|
|
729
|
+
if (openingBrace === -1) return null;
|
|
730
|
+
let depth = 0;
|
|
731
|
+
let quote = null;
|
|
732
|
+
for (let index = openingBrace; index < content.length; index += 1) {
|
|
733
|
+
const character = content[index];
|
|
734
|
+
const previous = content[index - 1];
|
|
735
|
+
if (quote) {
|
|
736
|
+
if (character === quote && previous !== '\\') quote = null;
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
739
|
+
if (character === '\"' || character === "'") quote = character;
|
|
740
|
+
else if (character === '{') depth += 1;
|
|
741
|
+
else if (character === '}' && --depth === 0) return { start: openingBrace + 1, end: index };
|
|
742
|
+
}
|
|
743
|
+
return null;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function ensureReleaseCodeOptimization() {
|
|
747
|
+
const currentContent = readFileSync(gradleFilePath, 'utf8');
|
|
748
|
+
const releaseBlock = findGradleBlock(currentContent, /buildTypes\s*\{[\s\S]*?\brelease\s*/);
|
|
749
|
+
if (!releaseBlock) throw new Error('Release build configuration was not found.');
|
|
750
|
+
|
|
751
|
+
const releaseContent = currentContent.slice(releaseBlock.start, releaseBlock.end);
|
|
752
|
+
let optimizedContent = releaseContent.replace(
|
|
753
|
+
/getDefaultProguardFile\(\s*(['"])proguard-android\.txt\1\s*\)/g,
|
|
754
|
+
"getDefaultProguardFile('proguard-android-optimize.txt')"
|
|
755
|
+
);
|
|
756
|
+
if (!/getDefaultProguardFile\(\s*(['"])proguard-android-optimize\.txt\1\s*\)/.test(optimizedContent)) {
|
|
757
|
+
throw new Error('Release build must use the optimisation-enabled default ProGuard rules.');
|
|
758
|
+
}
|
|
759
|
+
const newline = currentContent.includes('\r\n') ? '\r\n' : '\n';
|
|
760
|
+
// Preserve commented settings and explicit false values as app-specific opt-outs.
|
|
761
|
+
const comments = optimizedContent.match(/\/\/[^\r\n]*|\/\*[\s\S]*?\*\//g) || [];
|
|
762
|
+
if (!comments.some(comment => /\bshrinkResources\b/.test(comment))) {
|
|
763
|
+
const activeContent = optimizedContent.replace(/\/\/[^\r\n]*|\/\*[\s\S]*?\*\//g, '');
|
|
764
|
+
const shrinkResources = /^[ \t]*minifyEnabled\s*(?:=\s*)?true\b/m.test(activeContent);
|
|
765
|
+
const shrinkPattern = /^([ \t]*shrinkResources\s*(?:=\s*)?)(true|false)\b/m;
|
|
766
|
+
if (shrinkPattern.test(optimizedContent)) {
|
|
767
|
+
if (!shrinkResources) optimizedContent = optimizedContent.replace(shrinkPattern, '$1false');
|
|
768
|
+
} else {
|
|
769
|
+
optimizedContent = `${newline} shrinkResources ${shrinkResources}${optimizedContent}`;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
if (optimizedContent !== releaseContent) {
|
|
773
|
+
writeFileSync(gradleFilePath, currentContent.slice(0, releaseBlock.start)
|
|
774
|
+
+ optimizedContent + currentContent.slice(releaseBlock.end), 'utf8');
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const propertiesPath = join(__dirname, 'android', 'gradle.properties');
|
|
778
|
+
const properties = readFileSync(propertiesPath, 'utf8');
|
|
779
|
+
const propertyPattern = /^[ \t]*[#!]?[ \t]*android\.r8\.optimizedResourceShrinking(?:[ \t]*[=:]|[ \t]+|[ \t]*$)/m;
|
|
780
|
+
const property = 'android.r8.optimizedResourceShrinking=true';
|
|
781
|
+
const nextProperties = propertyPattern.test(properties)
|
|
782
|
+
? properties
|
|
783
|
+
: `${properties.trimEnd()}${newline}${property}${newline}`;
|
|
784
|
+
if (nextProperties !== properties) writeFileSync(propertiesPath, nextProperties, 'utf8');
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function signReleaseBundleWithSha256() {
|
|
788
|
+
const buildOptions = readCapacitorConfig().android?.buildOptions;
|
|
789
|
+
if (!buildOptions?.keystorePath || !buildOptions.keystorePassword
|
|
790
|
+
|| !buildOptions.keystoreAlias || !buildOptions.keystoreAliasPassword) {
|
|
791
|
+
throw new Error('Android AAB signing requires the existing keystore path, alias and passwords.');
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const keystorePath = resolve(__dirname, 'android', buildOptions.keystorePath);
|
|
795
|
+
const bundleDirectory = join(__dirname, 'android', 'app', 'build', 'outputs', 'bundle', 'release');
|
|
796
|
+
const unsignedBundle = join(bundleDirectory, 'app-release.aab');
|
|
797
|
+
const signedBundle = join(bundleDirectory, 'app-release-signed.aab');
|
|
798
|
+
if (!existsSync(keystorePath) || !existsSync(unsignedBundle)) {
|
|
799
|
+
throw new Error('The configured upload keystore or release AAB was not found.');
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
const jarsigner = process.env.JAVA_HOME
|
|
803
|
+
? join(process.env.JAVA_HOME, 'bin', process.platform === 'win32' ? 'jarsigner.exe' : 'jarsigner')
|
|
804
|
+
: 'jarsigner';
|
|
805
|
+
try {
|
|
806
|
+
// Keep passwords out of command arguments and error output.
|
|
807
|
+
execFileSync(jarsigner, [
|
|
808
|
+
'-sigalg', 'SHA256withRSA', '-digestalg', 'SHA-256',
|
|
809
|
+
'-keystore', keystorePath,
|
|
810
|
+
'-storepass:env', 'CODEPLAY_AAB_STORE_PASSWORD',
|
|
811
|
+
'-keypass:env', 'CODEPLAY_AAB_KEY_PASSWORD',
|
|
812
|
+
'-signedjar', signedBundle, unsignedBundle, buildOptions.keystoreAlias,
|
|
813
|
+
], {
|
|
814
|
+
stdio: 'pipe',
|
|
815
|
+
env: {
|
|
816
|
+
...process.env,
|
|
817
|
+
CODEPLAY_AAB_STORE_PASSWORD: buildOptions.keystorePassword,
|
|
818
|
+
CODEPLAY_AAB_KEY_PASSWORD: buildOptions.keystoreAliasPassword,
|
|
819
|
+
},
|
|
820
|
+
});
|
|
821
|
+
const verification = execFileSync(jarsigner, [
|
|
822
|
+
'-J-Duser.language=en', '-J-Duser.country=US', '-verify', signedBundle,
|
|
823
|
+
], { encoding: 'utf8', stdio: 'pipe' });
|
|
824
|
+
if (!verification.includes('jar verified.') || verification.includes('treated as unsigned')) {
|
|
825
|
+
throw new Error('Signature verification failed.');
|
|
826
|
+
}
|
|
827
|
+
} catch {
|
|
828
|
+
throw new Error('SHA-256 AAB signing or verification failed. Check the configured upload key and JDK; release stopped.');
|
|
829
|
+
}
|
|
830
|
+
console.log('✅ Release AAB signed with SHA-256 using the existing upload key and signature verified.');
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const amazonWindowExtensionsDependency = 'implementation "androidx.window.extensions.core:core:1.0.0"';
|
|
834
|
+
const amazonWindowExtensionsDependencyPattern = /^[ \t]*implementation\s+["']androidx\.window\.extensions\.core:core:[^"']+["'][ \t]*(?:\r?\n)?/gm;
|
|
835
|
+
|
|
836
|
+
function setAmazonWindowExtensionsDependency(enabled, { cleanup = false } = {}) {
|
|
837
|
+
const currentGradleContent = readFileSync(gradleFilePath, 'utf8');
|
|
838
|
+
let nextGradleContent = currentGradleContent.replace(amazonWindowExtensionsDependencyPattern, '');
|
|
839
|
+
|
|
840
|
+
if (enabled) {
|
|
841
|
+
const dependenciesBlock = findGradleBlock(nextGradleContent, /^\s*dependencies\s*/m);
|
|
842
|
+
if (!dependenciesBlock) {
|
|
843
|
+
throw new Error(`The dependencies block was not found in ${gradleFilePath}.`);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const newline = nextGradleContent.includes('\r\n') ? '\r\n' : '\n';
|
|
847
|
+
const contentBeforeClosingBrace = nextGradleContent.slice(0, dependenciesBlock.end);
|
|
848
|
+
const linePrefix = contentBeforeClosingBrace.endsWith('\n') ? '' : newline;
|
|
849
|
+
nextGradleContent = contentBeforeClosingBrace
|
|
850
|
+
+ linePrefix
|
|
851
|
+
+ ` ${amazonWindowExtensionsDependency}${newline}`
|
|
852
|
+
+ nextGradleContent.slice(dependenciesBlock.end);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
if (nextGradleContent !== currentGradleContent) {
|
|
856
|
+
writeFileSync(gradleFilePath, nextGradleContent, 'utf8');
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
console.log(enabled
|
|
860
|
+
? '✅ Added Amazon Fire WebView compatibility dependency for Store ID 7.'
|
|
861
|
+
: cleanup
|
|
862
|
+
? 'ℹ️ Removed temporary Amazon Fire WebView compatibility dependency after build cleanup.'
|
|
863
|
+
: 'ℹ️ Amazon Fire WebView compatibility dependency is not included for this build.');
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const versionCodeMatch = gradleContent.match(/versionCode\s+(\d+)/);
|
|
867
|
+
const versionNameMatch = gradleContent.match(/versionName\s+"([^"]+)"/);
|
|
868
|
+
|
|
869
|
+
const versionCode = versionCodeMatch ? versionCodeMatch[1] : '';
|
|
870
|
+
const versionName = versionNameMatch ? versionNameMatch[1] : '';
|
|
871
|
+
|
|
872
|
+
// Display the current versionCode and versionName
|
|
873
|
+
console.log(`Current versionCode: ${versionCode}`);
|
|
874
|
+
console.log(`Current versionName: ${versionName}`);
|
|
875
|
+
|
|
876
|
+
// Create an interface for user input
|
|
877
|
+
async function checkBaselineProfileBeforeRelease() {
|
|
878
|
+
const profilePaths = [
|
|
879
|
+
join(__dirname, 'baselineProfiles', 'baseline-prof.txt'),
|
|
880
|
+
join(__dirname, 'android', 'app', 'src', 'main', 'generated', 'baselineProfiles', 'baseline-prof.txt'),
|
|
881
|
+
join(__dirname, 'android', 'app', 'src', 'main', 'baseline-prof.txt'),
|
|
882
|
+
join(__dirname, 'android', 'app', 'src', 'main', 'baselineProfiles', 'baseline-prof.txt'),
|
|
883
|
+
];
|
|
884
|
+
const hasProfile = () => profilePaths.some(profilePath =>
|
|
885
|
+
existsSync(profilePath) && readFileSync(profilePath, 'utf8').trim().length > 0);
|
|
886
|
+
if (hasProfile()) return;
|
|
887
|
+
|
|
888
|
+
console.log('⚠️ baseline-prof.txt is missing or empty.');
|
|
889
|
+
console.log('Generation command: npm run baseline-profile:generate');
|
|
890
|
+
console.log('Generation requires an ADB-connected Android device or a configured emulator.');
|
|
891
|
+
const answer = await new Promise(resolveAnswer => {
|
|
892
|
+
rl.question('Generate AAB/APK without a Baseline Profile? [Enter/Y = continue, n = generate profile first]: ', resolveAnswer);
|
|
893
|
+
});
|
|
894
|
+
if (answer.trim().toLowerCase() !== 'n') return;
|
|
895
|
+
|
|
896
|
+
rl.pause();
|
|
897
|
+
try {
|
|
898
|
+
execSync('npm run baseline-profile:generate', { cwd: __dirname, stdio: 'inherit' });
|
|
899
|
+
if (!hasProfile()) {
|
|
900
|
+
throw new Error('Baseline Profile generation did not produce a non-empty baseline-prof.txt; release stopped.');
|
|
901
|
+
}
|
|
902
|
+
// Reuse setup to back up and synchronise the generated profile before packaging.
|
|
903
|
+
execFileSync(process.execPath, [join(__dirname, 'scripts-common', 'setup-baseline-profile.js')], {
|
|
904
|
+
cwd: __dirname,
|
|
905
|
+
stdio: 'inherit',
|
|
906
|
+
});
|
|
907
|
+
} finally {
|
|
908
|
+
rl.resume();
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
const rl = createInterface({
|
|
913
|
+
input: process.stdin,
|
|
914
|
+
output: process.stdout
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
// Ask for new versionCode
|
|
918
|
+
rl.question('Enter new versionCode (press enter to keep current): ', (newVersionCode) => {
|
|
919
|
+
const finalVersionCode = newVersionCode || versionCode; // Use existing if no input
|
|
920
|
+
|
|
921
|
+
// Ask for new versionName
|
|
922
|
+
rl.question('Enter new versionName (press enter to keep current): ', async (newVersionName) => {
|
|
923
|
+
const finalVersionName = newVersionName || versionName; // Use existing if no input
|
|
924
|
+
|
|
925
|
+
// Log the final version details
|
|
926
|
+
console.log(`📦 Final versionCode: ${finalVersionCode}`);
|
|
927
|
+
console.log(`📝 Final versionName: ${finalVersionName}`);
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
updateIOSVersion(finalVersionCode, finalVersionName);
|
|
931
|
+
|
|
932
|
+
// Update build.gradle with the new version details
|
|
933
|
+
let updatedGradleContent = gradleContent
|
|
934
|
+
.replace(/versionCode\s+\d+/, `versionCode ${finalVersionCode}`)
|
|
935
|
+
.replace(/versionName\s+"[^"]+"/, `versionName "${finalVersionName}"`);
|
|
936
|
+
|
|
937
|
+
// Check if resConfigs "en" already exists
|
|
938
|
+
const resConfigsLine = ' resConfigs "en"';
|
|
939
|
+
if (!updatedGradleContent.includes(resConfigsLine)) {
|
|
940
|
+
// Add resConfigs "en" below versionName
|
|
941
|
+
updatedGradleContent = updatedGradleContent.replace(/versionName\s+"[^"]+"/, `versionName "${finalVersionName}"\n${resConfigsLine}`);
|
|
942
|
+
} else {
|
|
943
|
+
console.log('ℹ️ resConfigs "en" already exists in build.gradle.');
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
// List of package IDs for which minify should be false
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
// Determine desired minify value
|
|
952
|
+
const desiredMinify = !_isffmpegUsed;
|
|
953
|
+
|
|
954
|
+
// Limit minification changes to release; debug may have its own test settings.
|
|
955
|
+
const minificationBlock = findGradleBlock(updatedGradleContent, /buildTypes\s*\{[\s\S]*?\brelease\s*/);
|
|
956
|
+
if (!minificationBlock) {
|
|
957
|
+
throw new Error('Release build configuration was not found.');
|
|
958
|
+
}
|
|
959
|
+
let minificationContent = updatedGradleContent.slice(minificationBlock.start, minificationBlock.end);
|
|
960
|
+
if (/\bminifyEnabled\s*(?:=\s*)?(true|false)\b/.test(minificationContent)) {
|
|
961
|
+
// Replace existing value with desired
|
|
962
|
+
minificationContent = minificationContent.replace(/\bminifyEnabled\s*(?:=\s*)?(true|false)\b/, `minifyEnabled ${desiredMinify}`);
|
|
963
|
+
console.log(`Replaced minifyEnabled with ${desiredMinify}.`);
|
|
964
|
+
} else {
|
|
965
|
+
// Insert minifyEnabled if not present
|
|
966
|
+
minificationContent = `\n minifyEnabled ${desiredMinify}${minificationContent}`;
|
|
967
|
+
console.log(`✅ Inserted minifyEnabled ${desiredMinify} into release block.`);
|
|
968
|
+
}
|
|
969
|
+
updatedGradleContent = updatedGradleContent.slice(0, minificationBlock.start)
|
|
970
|
+
+ minificationContent + updatedGradleContent.slice(minificationBlock.end);
|
|
971
|
+
|
|
972
|
+
// Add native debug symbols for release AABs, but leave any existing setting untouched.
|
|
973
|
+
const releaseBlock = findGradleBlock(updatedGradleContent, /buildTypes\s*\{[\s\S]*?\brelease\s*/);
|
|
974
|
+
if (releaseBlock) {
|
|
975
|
+
const releaseContent = updatedGradleContent.slice(releaseBlock.start, releaseBlock.end);
|
|
976
|
+
if (/\bdebugSymbolLevel\b/.test(releaseContent)) {
|
|
977
|
+
console.log('ℹ️ debugSymbolLevel already exists in buildTypes.release; leaving it unchanged.');
|
|
978
|
+
} else {
|
|
979
|
+
const ndkBlock = `\n ndk {\n debugSymbolLevel 'FULL'\n }`;
|
|
980
|
+
updatedGradleContent = updatedGradleContent.slice(0, releaseBlock.end)
|
|
981
|
+
+ ndkBlock
|
|
982
|
+
+ updatedGradleContent.slice(releaseBlock.end);
|
|
983
|
+
console.log("✅ Added debugSymbolLevel 'FULL' to buildTypes.release.");
|
|
984
|
+
}
|
|
985
|
+
} else {
|
|
986
|
+
console.log('⚠️ Warning: buildTypes.release block not found. debugSymbolLevel was not added.');
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
|
|
994
|
+
// Write the updated gradle content back to build.gradle
|
|
995
|
+
writeFileSync(gradleFilePath, updatedGradleContent, 'utf8');
|
|
996
|
+
console.log(`✅ Updated build.gradle with versionCode: ${finalVersionCode}, versionName: ${finalVersionName}, resConfigs "en" and "minifyEnabled true"`);
|
|
997
|
+
|
|
998
|
+
let baselineProfileChecked = false;
|
|
999
|
+
for (const id of storeIds) {
|
|
1000
|
+
console.log(`ℹ️ Building for Store ID ${id}`);
|
|
1001
|
+
|
|
1002
|
+
// Set the environment variable for store ID
|
|
1003
|
+
process.env.VITE_STORE_ID = id;
|
|
1004
|
+
|
|
1005
|
+
// Conditionally set the new file name
|
|
1006
|
+
let newFileName;
|
|
1007
|
+
let storeName = storeNames[id];
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
if (
|
|
1011
|
+
isOnlyOneAPK &&
|
|
1012
|
+
["OppoStore", "VivoStore"].includes(storeName)
|
|
1013
|
+
) {
|
|
1014
|
+
console.log(`⏭ Skipping ${storeName} because isOnlyOneAPK is enabled`);
|
|
1015
|
+
continue; // skip this iteration only
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
// build:storeid runs the shared before-build package/permission preparation.
|
|
1020
|
+
|
|
1021
|
+
// 🔥 Only change filename logic — nothing else touched
|
|
1022
|
+
if (isOnlyOneAPK && ["MiStore","OppoStore","VivoStore"].includes(storeName)) {
|
|
1023
|
+
|
|
1024
|
+
newFileName =
|
|
1025
|
+
`${_appUniqueId}_${_appName.replaceAll(" ","_")}-VI_MI_OPPO-b${finalVersionCode}-v${finalVersionName.replace(/\./g,'_')}.aab`;
|
|
1026
|
+
|
|
1027
|
+
}
|
|
1028
|
+
else if (storeName === "SamsungStore") {
|
|
1029
|
+
|
|
1030
|
+
newFileName =
|
|
1031
|
+
`${_appUniqueId}_${_appName.replaceAll(" ","_")}-${(storeName.toUpperCase()).replace("STORE","")}-b${finalVersionCode}-v${finalVersionName.replace(/\./g,'_')}.aab`;
|
|
1032
|
+
|
|
1033
|
+
}
|
|
1034
|
+
else {
|
|
1035
|
+
|
|
1036
|
+
newFileName =
|
|
1037
|
+
`${_appUniqueId}_${_appName.replaceAll(" ","_")}-${(storeName.toUpperCase()).replace("STORE","")}-b${finalVersionCode}-v${finalVersionName}.aab`;
|
|
1038
|
+
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
//storeName="amazon"
|
|
1042
|
+
const checkFullPath = join("AAB", newFileName); // Update to point to the new AAB directory
|
|
1043
|
+
|
|
1044
|
+
// Modify minSdkVersion in variables.gradle for SamsungStore
|
|
1045
|
+
const variablesGradleFilePath = join("android", "variables.gradle");
|
|
1046
|
+
let variablesGradleContent = readFileSync(variablesGradleFilePath, 'utf8');
|
|
1047
|
+
|
|
1048
|
+
// Extract the current minSdkVersion
|
|
1049
|
+
const minSdkVersionMatch = variablesGradleContent.match(/minSdkVersion\s*=\s*(\d+)/);
|
|
1050
|
+
const currentMinSdkVersion = minSdkVersionMatch ? parseInt(minSdkVersionMatch[1], 10) : null;
|
|
1051
|
+
|
|
1052
|
+
// Store the original minSdkVersion (only on the first iteration)
|
|
1053
|
+
if (!originalMinSdkVersion) {
|
|
1054
|
+
originalMinSdkVersion = currentMinSdkVersion;
|
|
1055
|
+
}
|
|
1056
|
+
try {
|
|
1057
|
+
applyFlowchartStoreApi(id);
|
|
1058
|
+
const shouldIncludeAmazonWindowExtensions = String(id) === '7' && amazon === true;
|
|
1059
|
+
setAmazonWindowExtensionsDependency(shouldIncludeAmazonWindowExtensions);
|
|
1060
|
+
// Modify the minSdkVersion based on the store
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
if(currentMinSdkVersion==23 || currentMinSdkVersion==24)
|
|
1064
|
+
{
|
|
1065
|
+
if (storeName === "SamsungStore" || storeName === "PlayStore" ||
|
|
1066
|
+
//_appPackageId=="vfx.green.editor" || _appPackageId=="audio.music.sound.editor" || _appPackageId=="video.to.gif.maker"
|
|
1067
|
+
|
|
1068
|
+
//ffmpegUsedPackages.includes(_appPackageId)
|
|
1069
|
+
_isffmpegUsed
|
|
1070
|
+
) {
|
|
1071
|
+
if (currentMinSdkVersion !== 24) {
|
|
1072
|
+
variablesGradleContent = variablesGradleContent.replace(/minSdkVersion\s*=\s*\d+/, 'minSdkVersion = 24');
|
|
1073
|
+
console.log('minSdkVersion updated to 24 for SamsungStore & PlayStore');
|
|
1074
|
+
writeFileSync(variablesGradleFilePath, variablesGradleContent);
|
|
1075
|
+
}
|
|
1076
|
+
} else {
|
|
1077
|
+
// For PlayStore and AmazonStore, ensure minSdkVersion is originalMinSdkVersion
|
|
1078
|
+
//if (currentMinSdkVersion !== originalMinSdkVersion) {
|
|
1079
|
+
variablesGradleContent = variablesGradleContent.replace(/minSdkVersion\s*=\s*\d+/, `minSdkVersion = ${amazonMinSdkVersion}`);
|
|
1080
|
+
console.log(`minSdkVersion reverted to ${amazonMinSdkVersion} for ${storeName}`);
|
|
1081
|
+
writeFileSync(variablesGradleFilePath, variablesGradleContent);
|
|
1082
|
+
//}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
|
|
1087
|
+
// Run the Node.js script to modify plugin.xml
|
|
1088
|
+
if (isAdmobFound) {
|
|
1089
|
+
const modifyPluginXmlPath = join('buildCodeplay', 'modify-plugin-xml.js');
|
|
1090
|
+
if (existsSync(modifyPluginXmlPath)) {
|
|
1091
|
+
execSync(`node ${modifyPluginXmlPath}`, { stdio: 'inherit' });
|
|
1092
|
+
} else {
|
|
1093
|
+
console.log("\x1b[33m%s\x1b[0m", "⚠️ modify-plugin-xml.js not found. Skipping old AdMob plugin.xml modification.");
|
|
1094
|
+
}
|
|
1095
|
+
} else {
|
|
1096
|
+
console.log("\x1b[33m%s\x1b[0m", "Seems to Pro Version [No ads found]");
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// Run the Vite build
|
|
1100
|
+
execSync(`npm run build:storeid${id}`, { stdio: 'inherit' });
|
|
1101
|
+
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
|
|
1105
|
+
|
|
1106
|
+
|
|
1107
|
+
// Copy the built files to the appropriate folder
|
|
1108
|
+
const src = join("www");
|
|
1109
|
+
const dest = join("android", "app", "src", "main", "assets", "public");
|
|
1110
|
+
|
|
1111
|
+
// Copy the web build with Node so releases work on every platform.
|
|
1112
|
+
fs.cpSync(src, dest, { recursive: true, force: true });
|
|
1113
|
+
|
|
1114
|
+
// Build Android AAB file
|
|
1115
|
+
//child_process.execSync('cd android && ./gradlew bundleRelease', { stdio: 'inherit' });
|
|
1116
|
+
|
|
1117
|
+
|
|
1118
|
+
// Build Android AAB or APK file based on store
|
|
1119
|
+
console.log(`🏗️ Building release for ${storeName}...`);
|
|
1120
|
+
|
|
1121
|
+
/* let buildType = "AAB";
|
|
1122
|
+
if (["VivoStore", "OppoStore", "MiStore"].includes(storeName)) {
|
|
1123
|
+
buildType = "APK";
|
|
1124
|
+
} */
|
|
1125
|
+
|
|
1126
|
+
|
|
1127
|
+
// Determine build type and signing type per store
|
|
1128
|
+
let buildType = ["VivoStore","OppoStore","MiStore"].includes(storeName) ? "APK" : "AAB";
|
|
1129
|
+
let signingType = buildType === "APK" ? "apksigner" : "jarsigner";
|
|
1130
|
+
|
|
1131
|
+
// Update capacitor.config.json for this store - This is not needed
|
|
1132
|
+
//updateCapacitorConfig(buildType, signingType);
|
|
1133
|
+
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
/* execSync('npx cap sync android', { stdio: 'inherit' });
|
|
1137
|
+
execSync(`npx cap build android --androidreleasetype=${buildType}`, { stdio: 'inherit' }); */
|
|
1138
|
+
|
|
1139
|
+
|
|
1140
|
+
execSync('npx cap sync android', { stdio: 'inherit' });
|
|
1141
|
+
|
|
1142
|
+
if (!baselineProfileChecked) {
|
|
1143
|
+
await checkBaselineProfileBeforeRelease();
|
|
1144
|
+
baselineProfileChecked = true;
|
|
1145
|
+
}
|
|
1146
|
+
ensureReleaseCodeOptimization();
|
|
1147
|
+
|
|
1148
|
+
if (buildType === "APK") {
|
|
1149
|
+
console.log("📦 Using custom Node signing script for APK build...");
|
|
1150
|
+
execFileSync(process.execPath, [join(process.cwd(), 'buildCodeplay', 'apk-store-builder.js')], { stdio: 'inherit' });
|
|
1151
|
+
} else {
|
|
1152
|
+
execSync(`npx cap build android --androidreleasetype=${buildType}`, { stdio: 'inherit' });
|
|
1153
|
+
signReleaseBundleWithSha256();
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
|
|
1157
|
+
|
|
1158
|
+
// Determine output paths
|
|
1159
|
+
let oldFilePath;
|
|
1160
|
+
let newExt = buildType === "APK" ? "apk" : "aab";
|
|
1161
|
+
|
|
1162
|
+
if (buildType === "APK") {
|
|
1163
|
+
//oldFilePath = join("android", "app", "build", "outputs", "apk", "release", "app-release-signed.apk");
|
|
1164
|
+
oldFilePath = join("android", "app", "build", "outputs", "apk", "release", "app-release.apk");
|
|
1165
|
+
} else {
|
|
1166
|
+
oldFilePath = join("android", "app", "build", "outputs", "bundle", "release", "app-release-signed.aab");
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
const checkFullPath = join("AAB", newFileName.replace(/\.aab$/, `.${newExt}`));
|
|
1170
|
+
|
|
1171
|
+
// Rename the output file
|
|
1172
|
+
if (existsSync(oldFilePath)) {
|
|
1173
|
+
renameSync(oldFilePath, checkFullPath);
|
|
1174
|
+
console.log(`✅ Renamed output ${newExt.toUpperCase()} file to: ${path.basename(checkFullPath)}`);
|
|
1175
|
+
syncReleaseToCodeplay(storeName, finalVersionName, finalVersionCode, new Date().toISOString().slice(0, 10));
|
|
1176
|
+
} else {
|
|
1177
|
+
console.error(`❌ ${newExt.toUpperCase()} file not found after build.`);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
} catch (error) {
|
|
1181
|
+
console.error(`❌ Error during build for Store ID ${id}:`, error);
|
|
1182
|
+
restoreFlowchartStoreApi();
|
|
1183
|
+
setAmazonWindowExtensionsDependency(false, {
|
|
1184
|
+
cleanup: String(id) === '7' && amazon === true,
|
|
1185
|
+
});
|
|
1186
|
+
process.exit(1);
|
|
1187
|
+
} finally {
|
|
1188
|
+
restoreFlowchartStoreApi();
|
|
1189
|
+
setAmazonWindowExtensionsDependency(false, {
|
|
1190
|
+
cleanup: String(id) === '7' && amazon === true,
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
rl.close(); // Close the readline interface after all operations
|
|
1196
|
+
});
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
|
|
1200
|
+
|
|
1201
|
+
|
|
1202
|
+
|
|
1203
|
+
/* function updateAndroidManifest1(store, addPermission) {
|
|
1204
|
+
try {
|
|
1205
|
+
if (!fs.existsSync(androidManifestPath)) {
|
|
1206
|
+
console.error("AndroidManifest.xml file not found!");
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
let manifestContent = fs.readFileSync(androidManifestPath, 'utf-8');
|
|
1211
|
+
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
// Add the required permission if not already present
|
|
1215
|
+
if (!manifestContent.includes(addPermission)) {
|
|
1216
|
+
const manifestLines = manifestContent.split('\n');
|
|
1217
|
+
const insertIndex = manifestLines.findIndex(line => line.trim().startsWith('<application'));
|
|
1218
|
+
if (insertIndex > -1) {
|
|
1219
|
+
manifestLines.splice(insertIndex, 0, ` ${addPermission}`);
|
|
1220
|
+
manifestContent = manifestLines.join('\n');
|
|
1221
|
+
console.log(`Added ${addPermission} to AndroidManifest.xml`);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// Write the updated content back to the file
|
|
1226
|
+
fs.writeFileSync(androidManifestPath, manifestContent, 'utf-8');
|
|
1227
|
+
console.log(`AndroidManifest.xml updated successfully for ${store}`);
|
|
1228
|
+
} catch (err) {
|
|
1229
|
+
console.error(`Error updating AndroidManifest.xml for ${store}:`, err);
|
|
1230
|
+
}
|
|
1231
|
+
} */
|
|
1232
|
+
|
|
1233
|
+
|
|
1234
|
+
|
|
1235
|
+
|
|
1236
|
+
|
|
1237
|
+
|
|
1238
|
+
|
|
1239
|
+
|