codeplay-common 4.9.6 → 4.9.9
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/.gitattributes +2 -2
- package/CHANGELOG.md +15 -1
- package/LICENSE +21 -21
- package/README.md +201 -169
- package/RELEASE_NOTES.md +15 -1
- package/files/buildCodeplay/codeplayBeforeBuild-6.4.js +4510 -4510
- package/files/buildCodeplay/codeplayBeforeBuild-6.5.js +4434 -0
- package/files/buildCodeplay/release-sync.config.json +4 -4
- package/files/buildCodeplay/sync-admob-nextgen.js +217 -0
- package/files/buildCodeplay/versions.json +1 -1
- package/files/finalrelease +11 -17
- package/files/generate-ai-promo-video.js +23 -23
- package/files/iap-install-2.js +145 -145
- package/files/ionic.config.json +6 -6
- package/files/scripts/screenshot/background-decorations.js +120 -120
- package/files/scripts/screenshot/frame-samples.js +117 -117
- package/files/scripts/setup-baseline-profile.js +262 -262
- package/files/take-screen-image.js +4304 -4304
- package/files/take-screen-video.js +644 -644
- package/package.json +18 -15
- package/scripts/sync-files.js +114 -106
- package/scripts/uninstall.js +123 -123
|
@@ -0,0 +1,4434 @@
|
|
|
1
|
+
// Version 6.5: migrate AdMob files to ZIP-distributed folder packages.
|
|
2
|
+
// Version 6.5: recreate failed WebViews, defer background recovery, and suppress foreground recovery toasts.
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const plist = require('plist');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const { execSync } = require("child_process");
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
const { readFileSync } = require("fs");
|
|
15
|
+
const readline = require('readline');
|
|
16
|
+
|
|
17
|
+
// Await import confirmation before continuing the remaining build steps.
|
|
18
|
+
(async () => {
|
|
19
|
+
|
|
20
|
+
// Standalone builds default to Android on every host OS.
|
|
21
|
+
// npm run build:ios explicitly selects iOS through CODEPLAY_BUILD_PLATFORM.
|
|
22
|
+
const buildPlatform = String(process.env.CODEPLAY_BUILD_PLATFORM || '').trim().toLowerCase() || 'android';
|
|
23
|
+
|
|
24
|
+
if (!['android', 'ios'].includes(buildPlatform)) {
|
|
25
|
+
console.error(`❌ Unsupported build target: ${buildPlatform}. Expected android or ios.`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const ENABLE_AUTO_UPDATE = true;
|
|
30
|
+
const USE_LIVE_SERVER_VERSION = true;
|
|
31
|
+
|
|
32
|
+
// Reuse directory traversal; refresh after generators or plugin downloads alter src.
|
|
33
|
+
let sourceFileInventory = null;
|
|
34
|
+
function getSourceFiles() {
|
|
35
|
+
if (!sourceFileInventory) {
|
|
36
|
+
const sourceRoot = path.join(process.cwd(), 'src');
|
|
37
|
+
sourceFileInventory = fs.existsSync(sourceRoot) ? walkSync(sourceRoot) : [];
|
|
38
|
+
}
|
|
39
|
+
return sourceFileInventory;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const configPath = path.join(process.cwd(), 'capacitor.config.json');
|
|
43
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
44
|
+
const updateLogFile = path.join(process.cwd(), "", "plugin-update-log.txt");
|
|
45
|
+
const ONESIGNAL_PLUGIN_NAME = "onesignal-cordova-plugin";
|
|
46
|
+
const { syncAdmobNextgen, syncImports: syncAdmobImports, findLatestPackage: findAdmobPackage } = require('./sync-admob-nextgen.js');
|
|
47
|
+
const ADMOB_NEXTGEN_CONFIG = "admob-ad-configuration-nextgen.json";
|
|
48
|
+
const ADMOB_OLD_CONFIG = "admob-ad-configuration.json";
|
|
49
|
+
const AGENT_INSTRUCTIONS_VERSION_KEY = "agents";
|
|
50
|
+
const AGENT_INSTRUCTIONS_ZIP_PREFIX = "agents";
|
|
51
|
+
const AGENT_INSTRUCTIONS_FILE = "AGENTS.md";
|
|
52
|
+
const AGENT_INSTRUCTIONS_DIR = "AGENTS";
|
|
53
|
+
const AGENT_INSTRUCTIONS_VERSION_FILE = ".codeplay-version";
|
|
54
|
+
|
|
55
|
+
// Remove the retired plugin before any validation, patching or package updates.
|
|
56
|
+
removeOneSignalPlugin();
|
|
57
|
+
|
|
58
|
+
const parseVersionParts = (version) => String(version || "0")
|
|
59
|
+
.trim()
|
|
60
|
+
.split(".")
|
|
61
|
+
.map(part => Number(part) || 0);
|
|
62
|
+
|
|
63
|
+
const extractVersionFromFileName = (fileName) => {
|
|
64
|
+
const match = String(fileName).match(/(\d+(?:\.\d+)*)/);
|
|
65
|
+
return match?.[1] || "0";
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const compareVersionStrings = (versionA, versionB) => {
|
|
69
|
+
const aParts = parseVersionParts(versionA);
|
|
70
|
+
const bParts = parseVersionParts(versionB);
|
|
71
|
+
const maxParts = Math.max(aParts.length, bParts.length);
|
|
72
|
+
|
|
73
|
+
for (let i = 0; i < maxParts; i++) {
|
|
74
|
+
const diff = (aParts[i] || 0) - (bParts[i] || 0);
|
|
75
|
+
if (diff !== 0) return diff;
|
|
76
|
+
}
|
|
77
|
+
return 0;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const getLatestVersionedFileName = (dir, pattern) => {
|
|
81
|
+
if (!fs.existsSync(dir)) return null;
|
|
82
|
+
|
|
83
|
+
const matches = fs.readdirSync(dir).filter((name) => pattern.test(name));
|
|
84
|
+
if (!matches.length) return null;
|
|
85
|
+
|
|
86
|
+
return matches.sort((a, b) => {
|
|
87
|
+
const versionA = extractVersionFromFileName(a);
|
|
88
|
+
const versionB = extractVersionFromFileName(b);
|
|
89
|
+
return compareVersionStrings(versionB, versionA);
|
|
90
|
+
})[0];
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
// Expected plugin list with minimum versions
|
|
95
|
+
/* const requiredPlugins = [
|
|
96
|
+
|
|
97
|
+
{ pattern: /backbutton-(\d+\.\d+)\.js$/, minVersion: '2.0', required: true, baseDir: 'js',mandatoryUpdate: true},
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
{ pattern: /common-(\d+\.\d+)(?:-beta-(\d+))?\.js$/, minVersion: '6.0', required: true, baseDir: 'js',mandatoryUpdate: true },
|
|
101
|
+
|
|
102
|
+
{ pattern: /localization_settings-(\d+\.\d+)\.js$/, minVersion: '1.1', required: true, baseDir: 'js',mandatoryUpdate: false },
|
|
103
|
+
{ pattern: /localization-(\d+\.\d+)\.js$/, minVersion: '1.5', required: true, baseDir: 'js',mandatoryUpdate: true },
|
|
104
|
+
{ pattern: /localNotification-(\d+\.\d+)\.js$/, minVersion: '2.2', required: true, baseDir: 'js',mandatoryUpdate: false },
|
|
105
|
+
{ pattern: /localNotification_AppSettings-(\d+\.\d+)\.js$/, minVersion: '1.0', required: true, baseDir: 'js',mandatoryUpdate: false },
|
|
106
|
+
{ pattern: /saveToGalleryAndSaveAnyFile-(\d+\.\d+)(-ios)?\.js$/, minVersion: '3.1', required: true, baseDir: 'js',mandatoryUpdate: true },
|
|
107
|
+
{ pattern: /Ads[\/\\]admob-emi-(\d+\.\d+)\.js$/, minVersion: '3.7', required: true, baseDir: 'js',mandatoryUpdate: true },
|
|
108
|
+
|
|
109
|
+
// New added plugins
|
|
110
|
+
{ pattern: /video-player-(\d+\.\d+)\.js$/, minVersion: '1.5', required: true, baseDir: 'js',mandatoryUpdate: false },
|
|
111
|
+
{ pattern: /image-cropper-(\d+\.\d+)\.js$/, minVersion: '1.1', required: true, baseDir: 'js',mandatoryUpdate: false },
|
|
112
|
+
{ pattern: /common-(\d+\.\d+)\.less$/, minVersion: '1.6', required: true, baseDir: 'assets/css',mandatoryUpdate: false },
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
// New folders
|
|
116
|
+
{ pattern: /IAP-(\d+\.\d+)$/, minVersion: '2.8', isFolder: true , required: true, baseDir: 'js/Ads',mandatoryUpdate: true },
|
|
117
|
+
{ pattern: /editor-(\d+\.\d+)$/, minVersion: '1.9', isFolder: true, required: true, baseDir: 'js',mandatoryUpdate: true },
|
|
118
|
+
{ pattern: /ffmpeg-(\d+\.\d+)$/, minVersion: '1.6', isFolder: true, required: true, baseDir: 'js',mandatoryUpdate: true },
|
|
119
|
+
{ pattern: /theme-(\d+\.\d+)$/, minVersion: '3.3', isFolder: true , required: true, baseDir: 'theme',mandatoryUpdate: true },
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
{ pattern: /certificatejs-(\d+\.\d+)$/, minVersion: '1.6', isFolder: true , required: true, baseDir: 'certificate',mandatoryUpdate: true }
|
|
123
|
+
|
|
124
|
+
]; */
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
const ROOT_DIR = path.join(__dirname, "..", "src");
|
|
128
|
+
|
|
129
|
+
function requireOrInstall(packageName) {
|
|
130
|
+
try {
|
|
131
|
+
return require(packageName);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
|
|
134
|
+
console.log(`📦 "${packageName}" not found. Installing automatically...`);
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
execSync(`npm install ${packageName}`, { stdio: "inherit" });
|
|
138
|
+
console.log(`✅ "${packageName}" installed successfully.`);
|
|
139
|
+
} catch (installErr) {
|
|
140
|
+
console.error(`❌ Failed to install "${packageName}".`);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Try loading again
|
|
145
|
+
return require(packageName);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const AdmZip = requireOrInstall("adm-zip");
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
const pkg = require(path.join(process.cwd(), 'node_modules', 'codeplay-common', 'package.json'));
|
|
154
|
+
|
|
155
|
+
const pluginName = pkg.name;
|
|
156
|
+
const pluginVersion = pkg.version;
|
|
157
|
+
|
|
158
|
+
let updateLogs = [];
|
|
159
|
+
|
|
160
|
+
function writeUpdateLine(message) {
|
|
161
|
+
updateLogs.push(message);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const MAX_LOG_BLOCKS = 50;
|
|
165
|
+
|
|
166
|
+
function saveUpdateLogs() {
|
|
167
|
+
|
|
168
|
+
if (updateLogs.length === 0) return;
|
|
169
|
+
|
|
170
|
+
const logDir = path.dirname(updateLogFile);
|
|
171
|
+
|
|
172
|
+
if (!fs.existsSync(logDir)) {
|
|
173
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const now = new Date();
|
|
177
|
+
|
|
178
|
+
const formattedTime = now.toLocaleString('en-GB', {
|
|
179
|
+
day: '2-digit',
|
|
180
|
+
month: '2-digit',
|
|
181
|
+
year: 'numeric',
|
|
182
|
+
hour: '2-digit',
|
|
183
|
+
minute: '2-digit',
|
|
184
|
+
hour12: true
|
|
185
|
+
}).replace(',', '').replace(/\//g, '-');
|
|
186
|
+
|
|
187
|
+
let newBlock = `${pluginName}: ${pluginVersion}\n[${formattedTime}]\n`;
|
|
188
|
+
|
|
189
|
+
updateLogs.forEach(line => {
|
|
190
|
+
newBlock += `${line}\n`;
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
newBlock += "\n";
|
|
194
|
+
|
|
195
|
+
let existingLog = "";
|
|
196
|
+
|
|
197
|
+
if (fs.existsSync(updateLogFile)) {
|
|
198
|
+
existingLog = fs.readFileSync(updateLogFile, "utf8");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let combinedLog = newBlock + existingLog;
|
|
202
|
+
|
|
203
|
+
// Split blocks by plugin header
|
|
204
|
+
const blocks = combinedLog.split(/\n(?=codeplay-common:)/);
|
|
205
|
+
|
|
206
|
+
// Keep only latest 50
|
|
207
|
+
const trimmed = blocks.slice(0, MAX_LOG_BLOCKS).join("\n");
|
|
208
|
+
|
|
209
|
+
fs.writeFileSync(updateLogFile, trimmed);
|
|
210
|
+
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
const versionsFile = path.join(__dirname, "versions.json");
|
|
218
|
+
|
|
219
|
+
function loadRequiredPlugins() {
|
|
220
|
+
|
|
221
|
+
if (!fs.existsSync(versionsFile)) {
|
|
222
|
+
console.error("❌ versions.json not found");
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const json = JSON.parse(fs.readFileSync(versionsFile, "utf8"));
|
|
227
|
+
|
|
228
|
+
// OneSignal is retired; do not download its legacy helpers again.
|
|
229
|
+
return json.plugins.filter(p =>
|
|
230
|
+
!/^admob-(?:emi-)?nextgen$/i.test(String(p.name || '')) && !/^onesignal(?:-|$)/i.test(String(p.name || '')) && !/onesignal/i.test(String(p.pattern || ''))
|
|
231
|
+
).map(p => ({
|
|
232
|
+
...p,
|
|
233
|
+
pattern: new RegExp(p.pattern)
|
|
234
|
+
}));
|
|
235
|
+
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
let requiredPlugins = loadRequiredPlugins();
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
async function downloadAndExtractZip(url, destFolder) {
|
|
246
|
+
|
|
247
|
+
const zipPath = destFolder + ".zip";
|
|
248
|
+
|
|
249
|
+
await downloadFile(url, zipPath);
|
|
250
|
+
|
|
251
|
+
const zip = new AdmZip(zipPath);
|
|
252
|
+
zip.extractAllTo(destFolder, true);
|
|
253
|
+
|
|
254
|
+
fs.unlinkSync(zipPath);
|
|
255
|
+
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
//Check codeplay-common latest version installed or not Start
|
|
265
|
+
//const { execSync } = require('child_process');
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
function getInstalledVersion(packageName) {
|
|
269
|
+
try {
|
|
270
|
+
const packageJsonPath = path.join(process.cwd(), 'node_modules', packageName, 'package.json');
|
|
271
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
272
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
273
|
+
return packageJson.version;
|
|
274
|
+
}
|
|
275
|
+
} catch (error) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function getLatestVersion(packageName) {
|
|
282
|
+
try {
|
|
283
|
+
return execSync(`npm view ${packageName} version`).toString().trim();
|
|
284
|
+
} catch (error) {
|
|
285
|
+
console.error(`Failed to fetch latest version for ${packageName}`);
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function checkPackageVersion() {
|
|
291
|
+
const packageName = 'codeplay-common';
|
|
292
|
+
const installedVersion = getInstalledVersion(packageName);
|
|
293
|
+
const latestVersion = getLatestVersion(packageName);
|
|
294
|
+
|
|
295
|
+
if (!installedVersion) {
|
|
296
|
+
console.error(`${packageName} is not installed. Installing now...`);
|
|
297
|
+
execSync(`npm install ${packageName}`, { stdio: 'inherit' });
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (installedVersion !== latestVersion) {
|
|
302
|
+
console.log(`${packageName} is outdated (installed: ${installedVersion}, latest: ${latestVersion}). Updating now...`);
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
execSync(`npm uninstall ${packageName}`, { stdio: 'inherit' });
|
|
306
|
+
execSync(`npm install ${packageName}`, { stdio: 'inherit' });
|
|
307
|
+
console.log(`${packageName} successfully updated.`);
|
|
308
|
+
} catch (err) {
|
|
309
|
+
console.error(`Failed to update ${packageName}:`, err.message);
|
|
310
|
+
process.exit(1);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
console.log(`${packageName} is up to date (version ${installedVersion}).`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Run package version check before executing the main script
|
|
320
|
+
try {
|
|
321
|
+
checkPackageVersion();
|
|
322
|
+
} catch (error) {
|
|
323
|
+
console.error(error.message);
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
//Check codeplay-common latest version installed or not END
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
function compareWithBeta(installedVersion, minVersion, isBeta) {
|
|
332
|
+
const baseCompare = compareVersions(installedVersion, minVersion);
|
|
333
|
+
|
|
334
|
+
if (!isBeta) {
|
|
335
|
+
// Stable version → normal compare
|
|
336
|
+
return baseCompare;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Beta version logic
|
|
340
|
+
if (baseCompare > 0) return 1; // 5.3-beta > 5.2
|
|
341
|
+
if (baseCompare < 0) return -1; // 5.1-beta < 5.2
|
|
342
|
+
|
|
343
|
+
// Same version but beta → LOWER than stable
|
|
344
|
+
return -1; // 5.2-beta < 5.2
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
const ensureAnalyticsFlagsInConfig = (config) => {
|
|
351
|
+
const analyticsFlagKey = 'ANALYTICS_ENABLED';
|
|
352
|
+
const legacyAnalyticsFlagKey = ['ANALY', 'STICS_ENABLED'].join('');
|
|
353
|
+
let updated = false;
|
|
354
|
+
|
|
355
|
+
if (!config.android) {
|
|
356
|
+
config.android = {};
|
|
357
|
+
updated = true;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (!config.ios) {
|
|
361
|
+
config.ios = {};
|
|
362
|
+
updated = true;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const syncPlatformFlag = (platformName, platformConfig) => {
|
|
366
|
+
const existingValue = platformConfig[analyticsFlagKey];
|
|
367
|
+
const legacyValue = platformConfig[legacyAnalyticsFlagKey];
|
|
368
|
+
let nextValue = false;
|
|
369
|
+
|
|
370
|
+
if (typeof existingValue === 'boolean') {
|
|
371
|
+
nextValue = existingValue;
|
|
372
|
+
} else if (typeof legacyValue === 'boolean') {
|
|
373
|
+
nextValue = legacyValue;
|
|
374
|
+
} else if (existingValue !== undefined) {
|
|
375
|
+
console.warn(`⚠️ ${platformName}.ANALYTICS_ENABLED should be true or false. Automatically setting it to false.`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (platformConfig[analyticsFlagKey] !== nextValue) {
|
|
379
|
+
platformConfig[analyticsFlagKey] = nextValue;
|
|
380
|
+
updated = true;
|
|
381
|
+
console.warn(`⚠️ ${platformName}.ANALYTICS_ENABLED added/updated as ${nextValue}.`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (legacyAnalyticsFlagKey in platformConfig) {
|
|
385
|
+
delete platformConfig[legacyAnalyticsFlagKey];
|
|
386
|
+
updated = true;
|
|
387
|
+
console.warn(`⚠️ ${platformName}.ANALYTICS_ENABLED spelling corrected.`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (nextValue === false) {
|
|
391
|
+
console.warn(`⚠️ Google Analytics is disabled for ${platformName}. Mostly recommended to enable it when IAP is available.`);
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
syncPlatformFlag('android', config.android);
|
|
396
|
+
syncPlatformFlag('ios', config.ios);
|
|
397
|
+
|
|
398
|
+
return updated;
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const checkAppUniqueId=()=>{
|
|
402
|
+
|
|
403
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
404
|
+
const analyticsFlagsUpdated = ensureAnalyticsFlagsInConfig(config);
|
|
405
|
+
|
|
406
|
+
const appUniqueId = config.android?.APP_UNIQUE_ID;
|
|
407
|
+
const RESIZEABLE_ACTIVITY = config.android?.RESIZEABLE_ACTIVITY;
|
|
408
|
+
const orientation = config.android?.ORIENTATION;
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
let logErrorMessage="";
|
|
412
|
+
|
|
413
|
+
// 1️⃣ Check if it’s missing
|
|
414
|
+
if (RESIZEABLE_ACTIVITY === undefined) {
|
|
415
|
+
logErrorMessage+='❌ Missing android.RESIZEABLE_ACTIVITY option in capacitor.config.json.\n';
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// 2️⃣ Check if it’s not boolean (true/false only)
|
|
419
|
+
else if (typeof RESIZEABLE_ACTIVITY !== 'boolean') {
|
|
420
|
+
logErrorMessage+='❌ Invalid android.RESIZEABLE_ACTIVITY value. Please use only true or false (without quotes).\n';
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
if (!orientation) {
|
|
426
|
+
logErrorMessage+='❌ Missing android.ORIENTATION option in capacitor.config.json.\n';
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
else if(orientation!="portrait" && orientation!="landscape" && orientation!="auto")
|
|
430
|
+
{
|
|
431
|
+
logErrorMessage+='❌ Spelling mistake in android.ORIENTATION option in capacitor.config.json. Please use only ["portrait" "landscape" "auto"]\n';
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
if (!appUniqueId) {
|
|
436
|
+
logErrorMessage+='❌ APP_UNIQUE_ID is missing in capacitor.config.json.';
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
else if (!Number.isInteger(appUniqueId)) {
|
|
440
|
+
logErrorMessage+='❌ APP_UNIQUE_ID must be an integer. Example: 1, 2, 3, etc.';
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if(logErrorMessage!="")
|
|
444
|
+
{
|
|
445
|
+
if (analyticsFlagsUpdated) {
|
|
446
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
447
|
+
console.log('✅ capacitor.config.json analytics flags synced.');
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
console.error(logErrorMessage);
|
|
451
|
+
process.exit(1)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (analyticsFlagsUpdated) {
|
|
455
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
456
|
+
console.log('✅ capacitor.config.json analytics flags synced.');
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
console.log(`✅ APP_UNIQUE_ID is valid: ${appUniqueId}`);
|
|
461
|
+
warnForNonAutoOrientationConfig(orientation, RESIZEABLE_ACTIVITY);
|
|
462
|
+
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function readBuildConfirmationInput(question) {
|
|
466
|
+
process.stdout.write(question);
|
|
467
|
+
|
|
468
|
+
const buffer = Buffer.alloc(1024);
|
|
469
|
+
let answer = "";
|
|
470
|
+
|
|
471
|
+
while (!answer.includes("\n")) {
|
|
472
|
+
const bytesRead = fs.readSync(0, buffer, 0, buffer.length, null);
|
|
473
|
+
|
|
474
|
+
if (bytesRead === 0) {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
answer += buffer.toString("utf8", 0, bytesRead);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return answer.trim().toLowerCase();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function warnForNonAutoOrientationConfig(orientation, RESIZEABLE_ACTIVITY) {
|
|
485
|
+
if (orientation === "auto" && RESIZEABLE_ACTIVITY === true) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
console.warn(`
|
|
490
|
+
⚠️ WARNING: Android orientation/resizable settings are not set to the recommended values.
|
|
491
|
+
|
|
492
|
+
Current:
|
|
493
|
+
android.ORIENTATION: ${orientation}
|
|
494
|
+
android.RESIZEABLE_ACTIVITY: ${RESIZEABLE_ACTIVITY}
|
|
495
|
+
|
|
496
|
+
Recommended:
|
|
497
|
+
android.ORIENTATION: auto
|
|
498
|
+
android.RESIZEABLE_ACTIVITY: true
|
|
499
|
+
|
|
500
|
+
Press Enter, y, or yes to continue.
|
|
501
|
+
Press n, no, or not to stop the build.
|
|
502
|
+
`);
|
|
503
|
+
|
|
504
|
+
while (true) {
|
|
505
|
+
const answer = readBuildConfirmationInput("Continue build? (Y/n): ");
|
|
506
|
+
|
|
507
|
+
if (answer === "" || answer === "y" || answer === "yes") {
|
|
508
|
+
console.log("✅ Continuing build...");
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (answer === null) {
|
|
513
|
+
console.log("❌ Build cancelled because no user input was received.");
|
|
514
|
+
process.exit(1);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (answer === "n" || answer === "no" || answer === "not") {
|
|
518
|
+
console.log("❌ Build cancelled due to orientation/resizable settings.");
|
|
519
|
+
process.exit(1);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
console.log("Please enter y, yes, n, no, not, or press Enter.");
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
checkAppUniqueId();
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
// ======================================================
|
|
534
|
+
// ENSURE IAP STARTS FROM THE HOME PAGE
|
|
535
|
+
// ======================================================
|
|
536
|
+
|
|
537
|
+
const getHomePagePath = () => {
|
|
538
|
+
const pagesDir = path.join(ROOT_DIR, "pages");
|
|
539
|
+
if (!fs.existsSync(pagesDir)) return null;
|
|
540
|
+
|
|
541
|
+
const exactHomePath = path.join(pagesDir, "home.f7");
|
|
542
|
+
if (fs.existsSync(exactHomePath)) return exactHomePath;
|
|
543
|
+
|
|
544
|
+
const pageFiles = fs.readdirSync(pagesDir)
|
|
545
|
+
.filter(fileName => fileName.endsWith(".f7"));
|
|
546
|
+
|
|
547
|
+
return pageFiles
|
|
548
|
+
.map(fileName => path.join(pagesDir, fileName))
|
|
549
|
+
.find(filePath => /data-name\s*=\s*['"]home['"]/.test(fs.readFileSync(filePath, "utf8"))) || null;
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
const getRelativeModuleImport = (sourceFile, targetFile) => {
|
|
553
|
+
let relativePath = path.relative(path.dirname(sourceFile), targetFile).replace(/\\/g, "/");
|
|
554
|
+
if (!relativePath.startsWith(".")) relativePath = `./${relativePath}`;
|
|
555
|
+
return relativePath;
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
const addInitIapToExistingImport = (content) => {
|
|
559
|
+
const iapImportRegex = /import\s*\{([^}]*)\}\s*from\s*(['"])([^'"]*\/IAP-\d+(?:\.\d+)*\/IAP-check-And-LoadAd\.js)\2\s*;?/;
|
|
560
|
+
const match = content.match(iapImportRegex);
|
|
561
|
+
if (!match || /\binitIAP\b/.test(match[1])) return content;
|
|
562
|
+
|
|
563
|
+
const importNames = match[1].trim();
|
|
564
|
+
const nextImportNames = importNames ? `initIAP, ${importNames}` : "initIAP";
|
|
565
|
+
return content.replace(iapImportRegex, `import { ${nextImportNames} } from ${match[2]}${match[3]}${match[2]};`);
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
const addInitIapCall = (content) => {
|
|
569
|
+
if (/\binitIAP\s*\(/.test(content)) return content;
|
|
570
|
+
|
|
571
|
+
const pageInitRegex = /(\$on\(\s*(['"])pageInit\2\s*,\s*(?:async\s*)?\(\)\s*=>\s*\{\s*\r?\n)/;
|
|
572
|
+
if (pageInitRegex.test(content)) {
|
|
573
|
+
return content.replace(pageInitRegex, "$1 initIAP();\n");
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const componentStartRegex = /(export\s+default\s*\([^)]*\)\s*=>\s*\{\s*\r?\n)/;
|
|
577
|
+
if (componentStartRegex.test(content)) {
|
|
578
|
+
return content.replace(componentStartRegex, "$1 initIAP();\n");
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
throw new Error("Unable to find a Framework7 component or pageInit lifecycle for IAP startup.");
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
const ensureIapStartupOnHomePage = () => {
|
|
585
|
+
const iapConfig = getAdmobIapConfig()?.IAP;
|
|
586
|
+
if (!isAnyIapStoreEnabled(iapConfig)) {
|
|
587
|
+
console.log("ℹ️ IAP is disabled for every store. AdMob will use direct startup.");
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const latestIapDir = getLatestIapDir();
|
|
592
|
+
if (!latestIapDir) {
|
|
593
|
+
throw new Error("IAP is enabled but no IAP-x.x folder was found.");
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const homePagePath = getHomePagePath();
|
|
597
|
+
if (!homePagePath) {
|
|
598
|
+
throw new Error("IAP is enabled but a Framework7 Home page could not be found in src/pages.");
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const iapModulePath = path.join(latestIapDir, "IAP-check-And-LoadAd.js");
|
|
602
|
+
if (!fs.existsSync(iapModulePath)) {
|
|
603
|
+
throw new Error(`IAP startup module is missing: ${iapModulePath}`);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
let content = fs.readFileSync(homePagePath, "utf8");
|
|
607
|
+
const originalContent = content;
|
|
608
|
+
const staticInitImportRegex = /import\s*\{[^}]*\binitIAP\b[^}]*\}\s*from\s*['"][^'"]*\/IAP-\d+(?:\.\d+)*\/IAP-check-And-LoadAd\.js['"]\s*;?/;
|
|
609
|
+
|
|
610
|
+
content = addInitIapToExistingImport(content);
|
|
611
|
+
|
|
612
|
+
if (!staticInitImportRegex.test(content)) {
|
|
613
|
+
const relativeImport = getRelativeModuleImport(homePagePath, iapModulePath);
|
|
614
|
+
const scriptStartRegex = /<script>\s*\r?\n/;
|
|
615
|
+
if (!scriptStartRegex.test(content)) {
|
|
616
|
+
throw new Error(`Unable to find the script block in ${homePagePath}`);
|
|
617
|
+
}
|
|
618
|
+
content = content.replace(scriptStartRegex, `$&import { initIAP } from '${relativeImport}';\n`);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
content = addInitIapCall(content);
|
|
622
|
+
|
|
623
|
+
if (content !== originalContent) {
|
|
624
|
+
fs.writeFileSync(homePagePath, content, "utf8");
|
|
625
|
+
console.log(`✅ Added direct IAP startup to ${path.relative(process.cwd(), homePagePath)}.`);
|
|
626
|
+
} else {
|
|
627
|
+
console.log(`ℹ️ Direct IAP startup already configured in ${path.relative(process.cwd(), homePagePath)}.`);
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
// ======================================================
|
|
632
|
+
// ENSURE IAP STARTS FROM THE HOME PAGE END
|
|
633
|
+
// ======================================================
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
//@Codemirror check and install/uninstall the packages START
|
|
646
|
+
//const fs = require("fs");
|
|
647
|
+
//const path = require("path");
|
|
648
|
+
//const { execSync } = require("child_process");
|
|
649
|
+
|
|
650
|
+
const jsDir = path.join(__dirname, "..", "src", "js");
|
|
651
|
+
|
|
652
|
+
// 🔍 Detect OLD structure
|
|
653
|
+
const oldEditorDirs = fs.readdirSync(jsDir)
|
|
654
|
+
.filter(name => /^editor-\d+\.\d+$/.test(name));
|
|
655
|
+
|
|
656
|
+
// 📁 New structure path (optional, not mandatory)
|
|
657
|
+
const newEditorBaseDir = path.join(jsDir, "editor");
|
|
658
|
+
|
|
659
|
+
// ======================================================
|
|
660
|
+
// ❌ CASE 1: OLD STRUCTURE FOUND → STOP
|
|
661
|
+
// ======================================================
|
|
662
|
+
if (oldEditorDirs.length > 0) {
|
|
663
|
+
|
|
664
|
+
console.error(`
|
|
665
|
+
❌ OLD EDITOR STRUCTURE DETECTED
|
|
666
|
+
|
|
667
|
+
You are using outdated folder structure:
|
|
668
|
+
src/js/editor-x.x/
|
|
669
|
+
|
|
670
|
+
🚨 This is no longer supported.
|
|
671
|
+
|
|
672
|
+
📦 Found folders:
|
|
673
|
+
${oldEditorDirs.map(d => " - " + d).join("\n")}
|
|
674
|
+
|
|
675
|
+
👉 Please move them manually:
|
|
676
|
+
|
|
677
|
+
src/js/editor-1.6
|
|
678
|
+
⬇
|
|
679
|
+
src/js/editor/editor-1.6
|
|
680
|
+
|
|
681
|
+
⚠️ Also ensure:
|
|
682
|
+
src/js/editor/configuration.json exists
|
|
683
|
+
|
|
684
|
+
❌ Build stopped.
|
|
685
|
+
`);
|
|
686
|
+
|
|
687
|
+
process.exit(1);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// ======================================================
|
|
691
|
+
// ✅ CASE 2: NEW STRUCTURE EXISTS → VALIDATE
|
|
692
|
+
// ======================================================
|
|
693
|
+
if (fs.existsSync(newEditorBaseDir)) {
|
|
694
|
+
|
|
695
|
+
// 🔍 Find editor-x.x inside new folder
|
|
696
|
+
const editorDirs = fs.readdirSync(newEditorBaseDir)
|
|
697
|
+
.filter(name => /^editor-\d+\.\d+$/.test(name));
|
|
698
|
+
|
|
699
|
+
// 👉 If editor folder exists but no versions → skip safely
|
|
700
|
+
if (editorDirs.length === 0) {
|
|
701
|
+
console.log("ℹ️ No editor-x.x folders found inside src/js/editor/");
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// ======================================================
|
|
706
|
+
// ✅ Validate configuration.json (NEW RULE)
|
|
707
|
+
// ======================================================
|
|
708
|
+
|
|
709
|
+
const editorConfigPath = path.join(newEditorBaseDir, "configuration.json");
|
|
710
|
+
|
|
711
|
+
// ❌ Missing config
|
|
712
|
+
if (!fs.existsSync(editorConfigPath)) {
|
|
713
|
+
console.error(`
|
|
714
|
+
❌ MISSING EDITOR CONFIGURATION FILE
|
|
715
|
+
|
|
716
|
+
Required:
|
|
717
|
+
src/js/editor/configuration.json
|
|
718
|
+
|
|
719
|
+
❌ Build stopped.
|
|
720
|
+
`);
|
|
721
|
+
process.exit(1);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// ❌ Check wrong placement
|
|
725
|
+
const invalidConfigs = [];
|
|
726
|
+
|
|
727
|
+
editorDirs.forEach(dir => {
|
|
728
|
+
const wrongPath = path.join(newEditorBaseDir, dir, "configuration.json");
|
|
729
|
+
if (fs.existsSync(wrongPath)) {
|
|
730
|
+
invalidConfigs.push(`src/js/editor/${dir}/configuration.json`);
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
if (invalidConfigs.length > 0) {
|
|
735
|
+
console.error(`
|
|
736
|
+
❌ INVALID CONFIGURATION LOCATION
|
|
737
|
+
|
|
738
|
+
🚫 configuration.json must NOT be inside version folders.
|
|
739
|
+
|
|
740
|
+
Found:
|
|
741
|
+
${invalidConfigs.map(p => " - " + p).join("\n")}
|
|
742
|
+
|
|
743
|
+
✅ Correct:
|
|
744
|
+
src/js/editor/configuration.json
|
|
745
|
+
|
|
746
|
+
❌ Build stopped.
|
|
747
|
+
`);
|
|
748
|
+
process.exit(1);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
console.log("✅ Editor structure validated.");
|
|
752
|
+
|
|
753
|
+
// ======================================================
|
|
754
|
+
// 🚀 Continue execution (run.js)
|
|
755
|
+
// ======================================================
|
|
756
|
+
|
|
757
|
+
const latestEditorDir = editorDirs.sort((a, b) => {
|
|
758
|
+
const vA = parseFloat(a.split('-')[1]);
|
|
759
|
+
const vB = parseFloat(b.split('-')[1]);
|
|
760
|
+
return vB - vA;
|
|
761
|
+
})[0];
|
|
762
|
+
|
|
763
|
+
const runJsPath = path.join(newEditorBaseDir, latestEditorDir, "run.js");
|
|
764
|
+
|
|
765
|
+
if (!fs.existsSync(runJsPath)) {
|
|
766
|
+
console.error(`❌ run.js not found in ${latestEditorDir}`);
|
|
767
|
+
process.exit(1);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
console.log(`🚀 Executing ${runJsPath}...`);
|
|
771
|
+
execSync(`node "${runJsPath}"`, { stdio: "inherit" });
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// ======================================================
|
|
775
|
+
// ✅ CASE 3: NOTHING EXISTS → DO NOTHING
|
|
776
|
+
// ======================================================
|
|
777
|
+
else {
|
|
778
|
+
console.log("ℹ️ Editor not used in this project. Skipping...");
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
//@Codemirror check and install/uninstall the packages END
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
// Validate saveToGalleryAndSaveAnyFile imports for the build platform START
|
|
792
|
+
|
|
793
|
+
const saveToGalleryAndSaveFileCheck_iOS = async () => {
|
|
794
|
+
|
|
795
|
+
const SOURCE_ROOT = path.resolve(__dirname, '../src');
|
|
796
|
+
const EXCLUDED_DIR = path.join(SOURCE_ROOT, 'js', 'Ads');
|
|
797
|
+
const ANDROID_MANIFEST_PATH = path.resolve(__dirname, '../android/app/src/main/AndroidManifest.xml');
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
// Skip comments and unrelated strings; match static/dynamic imports, re-exports and require.
|
|
801
|
+
const COMMENT_OR_STRING_REGEX = /\/\/[^\r\n]*|\/\*[\s\S]*?\*\/|(['"`])(?:\\[\s\S]|(?!\1)[^\\])*\1/g;
|
|
802
|
+
const IMPORT_REGEX = /(['"`])(?:\\[\s\S]|(?!\1)[^\\])*\1|\b(?:import\s+(?:[^;'"`]*?\s+from\s*)?|export\s+[^;'"`]*?\s+from\s*|(?:import|require)\s*\(\s*)(['"])([^'"\r\n]+)\2/g;
|
|
803
|
+
const SAVE_FILE_REGEX = /^(.*[\/\\])?(saveToGalleryAndSaveAnyFile-\d+(?:\.\d+)*)(-ios)?(\.js)?([?#].*)?$/;
|
|
804
|
+
const pendingChanges = [];
|
|
805
|
+
|
|
806
|
+
const ALLOWED_EXTENSIONS = ['.js', '.mjs', '.ts', '.f7'];
|
|
807
|
+
const referencedVersions = new Set();
|
|
808
|
+
let aliasImportFound = false;
|
|
809
|
+
let hasForbiddenEditorImport = false;
|
|
810
|
+
const FORBIDDEN_REGEX = /editor\/editor-\d+(\.\d+)+/;
|
|
811
|
+
const ERROR_MESSAGE = `const ERROR_MESSAGE = ❌ Invalid import detected!
|
|
812
|
+
|
|
813
|
+
You are using a direct version-based path like: editor/editor-x.x/editor.js
|
|
814
|
+
|
|
815
|
+
🚫 This is NOT allowed.
|
|
816
|
+
|
|
817
|
+
👉 Please use the proper alias or updated import method.
|
|
818
|
+
Example: import { ... } from '@editor'
|
|
819
|
+
|
|
820
|
+
⚠️ Do not use version-based paths in imports.
|
|
821
|
+
👉 Please add this manually in vite.config.js:
|
|
822
|
+
|
|
823
|
+
alias: {
|
|
824
|
+
'@editor': path.resolve(__dirname, './src/js/editor/editor-x.x')
|
|
825
|
+
}`
|
|
826
|
+
|
|
827
|
+
const isIOSBuild = buildPlatform === 'ios';
|
|
828
|
+
|
|
829
|
+
let iosImportFound = false;
|
|
830
|
+
let androidImportFound = false;
|
|
831
|
+
|
|
832
|
+
// Files to skip completely (full or partial match)
|
|
833
|
+
const SKIP_FILES = [
|
|
834
|
+
'pdf-3.11.174.min.js',
|
|
835
|
+
'pdf.worker-3.11.174.min.js'
|
|
836
|
+
,'index.browser.js'
|
|
837
|
+
];
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
const STATUSBAR_FILE = path.resolve(__dirname, '../src/js/common-configuration.js');
|
|
842
|
+
const STATUSBAR_REGEX = /^(?!\s*\/\/).*Capacitor\.Plugins\.StatusBar\.setStyle/m;
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
function scanSourceFile(dir) {
|
|
846
|
+
/*
|
|
847
|
+
//######################### DO NOT DELETE THIS - START [Appid base validation] #####################################
|
|
848
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
849
|
+
const appUniqueId = config.android?.APP_UNIQUE_ID;
|
|
850
|
+
if (appUniqueId == "206") return;
|
|
851
|
+
//######################### DO NOT DELETE THIS - END [Appid base validation] #####################################
|
|
852
|
+
*/
|
|
853
|
+
|
|
854
|
+
const baseName = path.basename(dir);
|
|
855
|
+
const isGalleryScope = ['certificate', 'pages', 'js'].some(folder =>
|
|
856
|
+
dir.startsWith(path.join(SOURCE_ROOT, folder) + path.sep)
|
|
857
|
+
) || dir === path.join(SOURCE_ROOT, 'app.f7');
|
|
858
|
+
const shouldCheckGallery = isGalleryScope && !dir.startsWith(EXCLUDED_DIR + path.sep)
|
|
859
|
+
&& !dir.split(path.sep).includes('node_modules') && !SKIP_FILES.includes(baseName)
|
|
860
|
+
&& ALLOWED_EXTENSIONS.some(ext => dir.endsWith(ext));
|
|
861
|
+
const shouldCheckEditor = /\.(js|ts|f7)$/.test(dir);
|
|
862
|
+
const shouldCheckOneSignal = /\.(js|mjs|cjs|ts|tsx|jsx|f7)$/.test(dir);
|
|
863
|
+
if (!shouldCheckGallery && !shouldCheckEditor && !shouldCheckOneSignal) return;
|
|
864
|
+
|
|
865
|
+
const content = fs.readFileSync(dir, 'utf8');
|
|
866
|
+
if (shouldCheckEditor) {
|
|
867
|
+
content.split('\n').forEach((line, index) => {
|
|
868
|
+
if (!FORBIDDEN_REGEX.test(line)) return;
|
|
869
|
+
console.error(`❌ Forbidden import found:\nFile: ${dir}\nLine: ${index + 1}\nCode: ${line.trim()}\n`, ERROR_MESSAGE);
|
|
870
|
+
hasForbiddenEditorImport = true;
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
if (dir === STATUSBAR_FILE) {
|
|
874
|
+
const contentWithoutComments = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
875
|
+
if (contentWithoutComments.includes('Capacitor.Plugins.StatusBar.setStyle')) {
|
|
876
|
+
throw new Error(`❌ StatusBar.setStyle should NOT be used in common-configuration.js. File: ${dir}`);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
const code = content.replace(COMMENT_OR_STRING_REGEX, (match, quote) =>
|
|
881
|
+
quote ? match : match.replace(/[^\r\n]/g, ' ')
|
|
882
|
+
);
|
|
883
|
+
const replacements = [];
|
|
884
|
+
IMPORT_REGEX.lastIndex = 0;
|
|
885
|
+
let imported;
|
|
886
|
+
while ((imported = IMPORT_REGEX.exec(code)) !== null) {
|
|
887
|
+
if (!imported[3]) continue;
|
|
888
|
+
const importPath = imported[3];
|
|
889
|
+
if (importPath === ONESIGNAL_PLUGIN_NAME || importPath.startsWith(ONESIGNAL_PLUGIN_NAME + '/')) {
|
|
890
|
+
throw new Error(`❌ OneSignal was removed, but ${dir} still imports ${importPath}. Remove that import and its initialization before building.`);
|
|
891
|
+
}
|
|
892
|
+
if (!shouldCheckGallery) continue;
|
|
893
|
+
if (/^@saveGallery[?#]/.test(importPath)) {
|
|
894
|
+
throw new Error(`❌ ${importPath} in ${dir} requires a dedicated Vite alias for its query/hash suffix.`);
|
|
895
|
+
}
|
|
896
|
+
if (importPath === '@saveGallery') {
|
|
897
|
+
aliasImportFound = true;
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
const fileMatch = importPath.match(SAVE_FILE_REGEX);
|
|
901
|
+
if (!fileMatch) continue;
|
|
902
|
+
if (fileMatch[5]) {
|
|
903
|
+
throw new Error(`❌ Cannot migrate ${importPath} in ${dir} to a plain @saveGallery alias: its query/hash suffix requires a dedicated Vite alias.`);
|
|
904
|
+
}
|
|
905
|
+
referencedVersions.add(fileMatch[2]);
|
|
906
|
+
const nextImportPath = '@saveGallery';
|
|
907
|
+
const offset = imported.index + imported[0].lastIndexOf(imported[2] + importPath + imported[2]) + 1;
|
|
908
|
+
replacements.push({ offset, importPath, nextImportPath });
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
if (replacements.length) {
|
|
912
|
+
let nextContent = content;
|
|
913
|
+
for (const replacement of [...replacements].reverse()) {
|
|
914
|
+
nextContent = nextContent.slice(0, replacement.offset) + replacement.nextImportPath
|
|
915
|
+
+ nextContent.slice(replacement.offset + replacement.importPath.length);
|
|
916
|
+
}
|
|
917
|
+
pendingChanges.push({ filePath: dir, content, nextContent, replacements });
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// One source content pass handles gallery migration, retired OneSignal imports, StatusBar and editor validation.
|
|
922
|
+
for (const sourceFile of getSourceFiles()) scanSourceFile(sourceFile);
|
|
923
|
+
if (hasForbiddenEditorImport) throw new Error('🚫 Build failed due to forbidden editor imports.');
|
|
924
|
+
console.log('✅ No forbidden editor imports found.');
|
|
925
|
+
|
|
926
|
+
if (referencedVersions.size || aliasImportFound) {
|
|
927
|
+
const viteConfigPath = getViteConfigPath();
|
|
928
|
+
if (!viteConfigPath) throw new Error('❌ Cannot configure @saveGallery: vite.config.js or vite.config.mjs is missing.');
|
|
929
|
+
const viteContent = fs.readFileSync(viteConfigPath, 'utf8');
|
|
930
|
+
const viteCode = viteContent.replace(COMMENT_OR_STRING_REGEX, (match, quote) =>
|
|
931
|
+
quote ? match : match.replace(/[^\r\n]/g, ' ')
|
|
932
|
+
);
|
|
933
|
+
const aliasRegex = /(['"])@saveGallery\1\s*:\s*path\.resolve\(\s*__dirname\s*,\s*(['"])([^'"]+)\2\s*\)/g;
|
|
934
|
+
const aliasMatches = [...viteCode.matchAll(aliasRegex)];
|
|
935
|
+
if (aliasMatches.length > 1) throw new Error('❌ Multiple active @saveGallery alias entries found. Keep one entry.');
|
|
936
|
+
const aliasMatch = aliasMatches[0];
|
|
937
|
+
if (!aliasMatch && /['"]@saveGallery['"]\s*:/.test(viteCode)) {
|
|
938
|
+
throw new Error('❌ Unsupported @saveGallery alias format. Use path.resolve(__dirname, a literal file path).');
|
|
939
|
+
}
|
|
940
|
+
if (aliasImportFound && aliasMatch) {
|
|
941
|
+
const existingName = path.basename(aliasMatch[3]).match(SAVE_FILE_REGEX);
|
|
942
|
+
if (!existingName) throw new Error('❌ @saveGallery must point to a versioned saveToGalleryAndSaveAnyFile helper.');
|
|
943
|
+
referencedVersions.add(existingName[2]);
|
|
944
|
+
}
|
|
945
|
+
if (!referencedVersions.size) {
|
|
946
|
+
const jsDir = path.join(SOURCE_ROOT, 'js');
|
|
947
|
+
const pattern = isIOSBuild ? /^saveToGalleryAndSaveAnyFile-\d+(?:\.\d+)*-ios\.js$/ : /^saveToGalleryAndSaveAnyFile-\d+(?:\.\d+)*\.js$/;
|
|
948
|
+
const latestFile = getLatestVersionedFileName(jsDir, pattern);
|
|
949
|
+
if (!latestFile) throw new Error(`❌ No ${buildPlatform} save-gallery helper found in src/js.`);
|
|
950
|
+
referencedVersions.add(latestFile.match(SAVE_FILE_REGEX)[2]);
|
|
951
|
+
}
|
|
952
|
+
if (referencedVersions.size !== 1) {
|
|
953
|
+
throw new Error(`❌ @saveGallery cannot represent multiple helper versions: ${[...referencedVersions].join(', ')}. Align them before building.`);
|
|
954
|
+
}
|
|
955
|
+
const baseName = [...referencedVersions][0];
|
|
956
|
+
const targetRelativePath = `./src/js/${baseName}${isIOSBuild ? '-ios' : ''}.js`;
|
|
957
|
+
const targetPath = path.resolve(path.dirname(viteConfigPath), targetRelativePath);
|
|
958
|
+
if (!fs.existsSync(targetPath)) throw new Error(`❌ Cannot configure @saveGallery: target file does not exist: ${targetPath}`);
|
|
959
|
+
const aliasEntry = `'@saveGallery': path.resolve(__dirname, '${targetRelativePath}')`;
|
|
960
|
+
let nextViteContent = viteContent;
|
|
961
|
+
if (aliasMatch) {
|
|
962
|
+
if (path.resolve(path.dirname(viteConfigPath), aliasMatch[3]) !== targetPath) {
|
|
963
|
+
nextViteContent = viteContent.slice(0, aliasMatch.index) + aliasEntry + viteContent.slice(aliasMatch.index + aliasMatch[0].length);
|
|
964
|
+
}
|
|
965
|
+
} else {
|
|
966
|
+
const aliasHeader = /\balias\s*:\s*\{/g.exec(viteCode);
|
|
967
|
+
const resolveHeader = /\bresolve\s*:\s*\{/g.exec(viteCode);
|
|
968
|
+
const returnHeader = /\breturn\s*\{/g.exec(viteCode);
|
|
969
|
+
const newline = viteContent.includes('\r\n') ? '\r\n' : '\n';
|
|
970
|
+
const header = aliasHeader || resolveHeader || returnHeader;
|
|
971
|
+
if (!header || (!aliasHeader && /\balias\s*:/.test(viteCode))) {
|
|
972
|
+
throw new Error('❌ Cannot safely insert @saveGallery in this Vite config. Add a resolve.alias object first.');
|
|
973
|
+
}
|
|
974
|
+
const insert = aliasHeader ? `${newline} ${aliasEntry},`
|
|
975
|
+
: resolveHeader ? `${newline} alias: { ${aliasEntry} },`
|
|
976
|
+
: `${newline} resolve: { alias: { ${aliasEntry} } },`;
|
|
977
|
+
const offset = header.index + header[0].length;
|
|
978
|
+
nextViteContent = viteContent.slice(0, offset) + insert + viteContent.slice(offset);
|
|
979
|
+
}
|
|
980
|
+
nextViteContent = ensureImportOrRequire(nextViteContent, 'path', "import path from 'path';", "const path = require('path');");
|
|
981
|
+
if (nextViteContent !== viteContent) {
|
|
982
|
+
pendingChanges.unshift({ filePath: viteConfigPath, content: viteContent, nextContent: nextViteContent,
|
|
983
|
+
replacements: [{ importPath: aliasMatch ? aliasMatch[3] : '(missing @saveGallery alias)', nextImportPath: targetRelativePath }] });
|
|
984
|
+
}
|
|
985
|
+
if (isIOSBuild) iosImportFound = true;
|
|
986
|
+
else androidImportFound = true;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
if (pendingChanges.length) {
|
|
990
|
+
console.warn(`\n⚠️ WARNING: This build is considered ${isIOSBuild ? 'iOS' : 'Android'}.`);
|
|
991
|
+
console.warn('The following source imports and Vite alias targets will be changed:');
|
|
992
|
+
for (const change of pendingChanges) {
|
|
993
|
+
console.warn(`\n📄 ${change.filePath}`);
|
|
994
|
+
for (const replacement of change.replacements) {
|
|
995
|
+
console.warn(` ${replacement.importPath} → ${replacement.nextImportPath}`);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
const confirmed = await new Promise(resolve => {
|
|
1000
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1001
|
+
rl.on('close', () => resolve(false));
|
|
1002
|
+
rl.on('SIGINT', () => { resolve(false); rl.close(); });
|
|
1003
|
+
rl.question('\nPress Enter or type y to apply these changes and continue; any other answer cancels: ', answer => {
|
|
1004
|
+
const normalizedAnswer = answer.trim().toLowerCase();
|
|
1005
|
+
resolve(normalizedAnswer === '' || normalizedAnswer === 'y');
|
|
1006
|
+
rl.close();
|
|
1007
|
+
});
|
|
1008
|
+
});
|
|
1009
|
+
if (!confirmed) throw new Error('❌ Build cancelled. No import filenames were changed.');
|
|
1010
|
+
|
|
1011
|
+
// Check every source again before writing so edits made while awaiting confirmation are preserved.
|
|
1012
|
+
for (const change of pendingChanges) {
|
|
1013
|
+
if (fs.readFileSync(change.filePath, 'utf8') !== change.content) {
|
|
1014
|
+
throw new Error(`❌ Source changed while awaiting confirmation: ${change.filePath}. Build cancelled; run it again.`);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
for (const change of pendingChanges) {
|
|
1018
|
+
fs.writeFileSync(change.filePath, change.nextContent, 'utf8');
|
|
1019
|
+
console.log(`✅ Updated platform imports in: ${change.filePath}`);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// The alias selects exactly one helper for the target platform.
|
|
1024
|
+
if (iosImportFound) {
|
|
1025
|
+
console.log('✅ iOS import detected (valid for iOS build).');
|
|
1026
|
+
} else if (androidImportFound) {
|
|
1027
|
+
console.log('✅ Android import detected (valid for Android build).');
|
|
1028
|
+
} else {
|
|
1029
|
+
console.log('✅ No platform-specific saveToGalleryAndSaveAnyFile imports found.');
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
|
|
1033
|
+
// Android Checks
|
|
1034
|
+
if (buildPlatform === 'android' && androidImportFound) {
|
|
1035
|
+
console.log("📱 Android version of saveToGalleryAndSaveAnyFile detected. Checking AndroidManifest.xml...");
|
|
1036
|
+
|
|
1037
|
+
if (!fs.existsSync(ANDROID_MANIFEST_PATH)) {
|
|
1038
|
+
console.error("❌ AndroidManifest.xml not found. Cannot add requestLegacyExternalStorage attribute.");
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
let manifestContent = fs.readFileSync(ANDROID_MANIFEST_PATH, 'utf8');
|
|
1043
|
+
|
|
1044
|
+
if (!manifestContent.includes('android:requestLegacyExternalStorage="true"')) {
|
|
1045
|
+
console.log("Adding android:requestLegacyExternalStorage=\"true\" to <application> tag...");
|
|
1046
|
+
|
|
1047
|
+
manifestContent = manifestContent.replace(
|
|
1048
|
+
/<application([^>]*)>/,
|
|
1049
|
+
(match, attrs) => {
|
|
1050
|
+
if (attrs.includes('android:requestLegacyExternalStorage')) return match;
|
|
1051
|
+
return `<application${attrs} android:requestLegacyExternalStorage="true">`;
|
|
1052
|
+
}
|
|
1053
|
+
);
|
|
1054
|
+
|
|
1055
|
+
fs.writeFileSync(ANDROID_MANIFEST_PATH, manifestContent, 'utf8');
|
|
1056
|
+
console.log("✅ android:requestLegacyExternalStorage=\"true\" added successfully.");
|
|
1057
|
+
} else {
|
|
1058
|
+
console.log("ℹ️ android:requestLegacyExternalStorage already exists in AndroidManifest.xml.");
|
|
1059
|
+
}
|
|
1060
|
+
} else {
|
|
1061
|
+
console.log("✅ No Android saveToGalleryAndSaveAnyFile imports detected.");
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
|
|
1065
|
+
await saveToGalleryAndSaveFileCheck_iOS();
|
|
1066
|
+
// Validate saveToGalleryAndSaveAnyFile imports for the build platform END
|
|
1067
|
+
|
|
1068
|
+
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
/*
|
|
1081
|
+
// Clean up AppleDouble files (._*) created by macOS START
|
|
1082
|
+
if (process.platform === 'darwin') {
|
|
1083
|
+
try {
|
|
1084
|
+
console.log('🧹 Cleaning up AppleDouble files (._*)...');
|
|
1085
|
+
execSync(`find . -name '._*' -delete`);
|
|
1086
|
+
console.log('✅ AppleDouble files removed.');
|
|
1087
|
+
} catch (err) {
|
|
1088
|
+
console.warn('⚠️ Failed to remove AppleDouble files:', err.message);
|
|
1089
|
+
}
|
|
1090
|
+
} else {
|
|
1091
|
+
console.log('ℹ️ Skipping AppleDouble cleanup — not a macOS machine.');
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// Clean up AppleDouble files (._*) created by macOS END
|
|
1095
|
+
*/
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
|
|
1099
|
+
|
|
1100
|
+
|
|
1101
|
+
|
|
1102
|
+
//In routes.js file check static import START
|
|
1103
|
+
|
|
1104
|
+
const routesPath = path.join(process.cwd(), 'src', 'js', 'routes.js');
|
|
1105
|
+
const routesContent = fs.readFileSync(routesPath, 'utf-8');
|
|
1106
|
+
|
|
1107
|
+
let inBlockComment = false;
|
|
1108
|
+
const lines = routesContent.split('\n');
|
|
1109
|
+
|
|
1110
|
+
const allowedImport = `import HomePage from '../pages/home.f7';`;
|
|
1111
|
+
const badImportRegex = /^[ \t]*import\s+[\w{}*,\s]*\s+from\s+['"].+\.f7['"]\s*;/;
|
|
1112
|
+
const badImports = [];
|
|
1113
|
+
|
|
1114
|
+
lines.forEach((line, index) => {
|
|
1115
|
+
const trimmed = line.trim();
|
|
1116
|
+
|
|
1117
|
+
// Handle block comment start and end
|
|
1118
|
+
if (trimmed.startsWith('/*')) inBlockComment = true;
|
|
1119
|
+
if (inBlockComment && trimmed.endsWith('*/')) {
|
|
1120
|
+
inBlockComment = false;
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// Skip if inside block comment or line comment
|
|
1125
|
+
if (inBlockComment || trimmed.startsWith('//')) return;
|
|
1126
|
+
|
|
1127
|
+
// Match static .f7 import
|
|
1128
|
+
if (badImportRegex.test(trimmed) && trimmed !== allowedImport) {
|
|
1129
|
+
badImports.push({ line: trimmed, number: index + 1 });
|
|
1130
|
+
}
|
|
1131
|
+
});
|
|
1132
|
+
|
|
1133
|
+
if (badImports.length > 0) {
|
|
1134
|
+
console.error('\n❌ ERROR: Detected disallowed static imports of .f7 files in routes.js\n');
|
|
1135
|
+
console.error(`⚠️ Only this static import is allowed:\n ${allowedImport}\n`);
|
|
1136
|
+
console.error(`🔧 Please convert other imports to async dynamic imports like this:\n`);
|
|
1137
|
+
console.error(`
|
|
1138
|
+
|
|
1139
|
+
import HomePage from '../pages/home.f7';
|
|
1140
|
+
|
|
1141
|
+
const routes = [
|
|
1142
|
+
{
|
|
1143
|
+
path: '/',
|
|
1144
|
+
component:HomePage,
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
path: '/ProfilePage/',
|
|
1148
|
+
async async({ resolve }) {
|
|
1149
|
+
const page = await import('../pages/profile.f7');
|
|
1150
|
+
resolve({ component: page.default });
|
|
1151
|
+
},
|
|
1152
|
+
}]
|
|
1153
|
+
`);
|
|
1154
|
+
|
|
1155
|
+
badImports.forEach(({ line, number }) => {
|
|
1156
|
+
console.error(`${number}: ${line}`);
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
process.exit(1);
|
|
1160
|
+
} else {
|
|
1161
|
+
console.log('✅ routes.js passed the .f7 import check.');
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
//In routes.js file check static import END
|
|
1165
|
+
|
|
1166
|
+
|
|
1167
|
+
|
|
1168
|
+
|
|
1169
|
+
|
|
1170
|
+
|
|
1171
|
+
|
|
1172
|
+
|
|
1173
|
+
|
|
1174
|
+
|
|
1175
|
+
|
|
1176
|
+
|
|
1177
|
+
|
|
1178
|
+
// Check and change the "BridgeWebViewClient.java" file START
|
|
1179
|
+
/*
|
|
1180
|
+
For crash issue due to low memory problem, we need to modify the onRenderProcessGone method in BridgeWebViewClient.java.
|
|
1181
|
+
*/
|
|
1182
|
+
|
|
1183
|
+
|
|
1184
|
+
const bridgeWebViewClientFilePath = path.join(process.cwd(), 'node_modules', '@capacitor/android/capacitor/src/main/java/com/getcapacitor', 'BridgeWebViewClient.java');
|
|
1185
|
+
|
|
1186
|
+
// Read the file
|
|
1187
|
+
if (!fs.existsSync(bridgeWebViewClientFilePath)) {
|
|
1188
|
+
console.error('❌ Error: BridgeWebViewClient.java not found.');
|
|
1189
|
+
process.exit(1);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
let fileContent = fs.readFileSync(bridgeWebViewClientFilePath, 'utf8');
|
|
1193
|
+
const bridgeWebViewLineEnding = fileContent.includes('\r\n') ? '\r\n' : '\n';
|
|
1194
|
+
fileContent = fileContent.replace(/\r\n/g, '\n');
|
|
1195
|
+
|
|
1196
|
+
// Define old and new code
|
|
1197
|
+
const oldCodeStart = `@Override
|
|
1198
|
+
public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) {
|
|
1199
|
+
super.onRenderProcessGone(view, detail);
|
|
1200
|
+
boolean result = false;
|
|
1201
|
+
|
|
1202
|
+
List<WebViewListener> webViewListeners = bridge.getWebViewListeners();
|
|
1203
|
+
if (webViewListeners != null) {
|
|
1204
|
+
for (WebViewListener listener : bridge.getWebViewListeners()) {
|
|
1205
|
+
result = listener.onRenderProcessGone(view, detail) || result;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
return result;
|
|
1210
|
+
}`;
|
|
1211
|
+
|
|
1212
|
+
// Match the previous patch so existing projects migrate without reinstalling Capacitor.
|
|
1213
|
+
const legacyReloadCode = `@Override
|
|
1214
|
+
public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) {
|
|
1215
|
+
super.onRenderProcessGone(view, detail);
|
|
1216
|
+
|
|
1217
|
+
boolean result = false;
|
|
1218
|
+
|
|
1219
|
+
List<WebViewListener> webViewListeners = bridge.getWebViewListeners();
|
|
1220
|
+
if (webViewListeners != null) {
|
|
1221
|
+
for (WebViewListener listener : bridge.getWebViewListeners()) {
|
|
1222
|
+
result = listener.onRenderProcessGone(view, detail) || result;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
if (!result) {
|
|
1227
|
+
// If no one handled it, handle it ourselves!
|
|
1228
|
+
|
|
1229
|
+
/*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
1230
|
+
if (detail.didCrash()) {
|
|
1231
|
+
//Log.e("CapacitorWebView", "WebView crashed internally!");
|
|
1232
|
+
} else {
|
|
1233
|
+
//Log.w("CapacitorWebView", "WebView was killed by system (low memory) internally!");
|
|
1234
|
+
}
|
|
1235
|
+
}*/
|
|
1236
|
+
|
|
1237
|
+
view.post(() -> {
|
|
1238
|
+
Toast.makeText(view.getContext(), "Reloading due to low memory issue", Toast.LENGTH_SHORT).show();
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
view.reload(); // Safely reload WebView
|
|
1242
|
+
|
|
1243
|
+
return true; // We handled it
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
return result;
|
|
1247
|
+
}`;
|
|
1248
|
+
|
|
1249
|
+
const newCode = `// Codeplay WebView renderer recovery (before-build 6.5).
|
|
1250
|
+
private boolean codeplayRendererRecoveryPending = false;
|
|
1251
|
+
private static long codeplayRendererRecoveryWindowStart = -1L;
|
|
1252
|
+
private static int codeplayRendererRecoveryCount = 0;
|
|
1253
|
+
|
|
1254
|
+
@Override
|
|
1255
|
+
public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) {
|
|
1256
|
+
if (codeplayRendererRecoveryPending) return true;
|
|
1257
|
+
|
|
1258
|
+
boolean handled = false;
|
|
1259
|
+
List<WebViewListener> webViewListeners = bridge.getWebViewListeners();
|
|
1260
|
+
if (webViewListeners != null) {
|
|
1261
|
+
for (WebViewListener listener : webViewListeners) {
|
|
1262
|
+
handled = listener.onRenderProcessGone(view, detail) || handled;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
if (handled) return true;
|
|
1266
|
+
|
|
1267
|
+
codeplayRendererRecoveryPending = true;
|
|
1268
|
+
final androidx.appcompat.app.AppCompatActivity activity = bridge.getActivity();
|
|
1269
|
+
final boolean wasInForeground = activity != null && activity.getLifecycle()
|
|
1270
|
+
.getCurrentState().isAtLeast(androidx.lifecycle.Lifecycle.State.STARTED);
|
|
1271
|
+
|
|
1272
|
+
// A terminated renderer's WebView must never be loaded or reloaded again.
|
|
1273
|
+
if (view.getParent() instanceof android.view.ViewGroup) {
|
|
1274
|
+
((android.view.ViewGroup) view.getParent()).removeView(view);
|
|
1275
|
+
}
|
|
1276
|
+
view.destroy();
|
|
1277
|
+
|
|
1278
|
+
if (activity == null || activity.isFinishing() || activity.isDestroyed()) return true;
|
|
1279
|
+
|
|
1280
|
+
final android.os.Handler mainHandler = new android.os.Handler(android.os.Looper.getMainLooper());
|
|
1281
|
+
final Runnable recover = () -> {
|
|
1282
|
+
if (activity.isFinishing() || activity.isDestroyed()) return;
|
|
1283
|
+
|
|
1284
|
+
// Static counters survive Activity recreation and prevent an automatic restart loop.
|
|
1285
|
+
long now = android.os.SystemClock.elapsedRealtime();
|
|
1286
|
+
if (codeplayRendererRecoveryWindowStart < 0L ||
|
|
1287
|
+
now - codeplayRendererRecoveryWindowStart >= 60000L) {
|
|
1288
|
+
codeplayRendererRecoveryWindowStart = now;
|
|
1289
|
+
codeplayRendererRecoveryCount = 0;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
if (codeplayRendererRecoveryCount >= 3) {
|
|
1293
|
+
new android.app.AlertDialog.Builder(activity)
|
|
1294
|
+
.setTitle("Unable to restore the app")
|
|
1295
|
+
.setMessage("The app could not recover automatically. Please try again.")
|
|
1296
|
+
.setCancelable(false)
|
|
1297
|
+
.setPositiveButton("Try again", (dialog, which) -> {
|
|
1298
|
+
codeplayRendererRecoveryWindowStart = -1L;
|
|
1299
|
+
codeplayRendererRecoveryCount = 0;
|
|
1300
|
+
if (!activity.isFinishing() && !activity.isDestroyed()) activity.recreate();
|
|
1301
|
+
})
|
|
1302
|
+
.setNegativeButton("Close", (dialog, which) -> activity.finish())
|
|
1303
|
+
.show();
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
codeplayRendererRecoveryCount++;
|
|
1308
|
+
if (!wasInForeground) {
|
|
1309
|
+
Toast.makeText(activity.getApplicationContext(), "Restoring your app…", Toast.LENGTH_SHORT).show();
|
|
1310
|
+
}
|
|
1311
|
+
activity.recreate();
|
|
1312
|
+
};
|
|
1313
|
+
|
|
1314
|
+
if (wasInForeground) {
|
|
1315
|
+
mainHandler.post(recover);
|
|
1316
|
+
} else {
|
|
1317
|
+
// Do not allocate another renderer while the app is hidden and memory may still be low.
|
|
1318
|
+
activity.getLifecycle().addObserver(new androidx.lifecycle.LifecycleEventObserver() {
|
|
1319
|
+
@Override
|
|
1320
|
+
public void onStateChanged(
|
|
1321
|
+
androidx.lifecycle.LifecycleOwner source,
|
|
1322
|
+
androidx.lifecycle.Lifecycle.Event event
|
|
1323
|
+
) {
|
|
1324
|
+
if (event == androidx.lifecycle.Lifecycle.Event.ON_RESUME) {
|
|
1325
|
+
source.getLifecycle().removeObserver(this);
|
|
1326
|
+
mainHandler.post(recover);
|
|
1327
|
+
} else if (event == androidx.lifecycle.Lifecycle.Event.ON_DESTROY) {
|
|
1328
|
+
source.getLifecycle().removeObserver(this);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
return true;
|
|
1334
|
+
}`;
|
|
1335
|
+
|
|
1336
|
+
// Step 1: Update the stock method or migrate the previous reload-based patch.
|
|
1337
|
+
let updated = false;
|
|
1338
|
+
|
|
1339
|
+
if (fileContent.includes(newCode)) {
|
|
1340
|
+
console.log('ℹ️ Method already updated. No changes needed in "BridgeWebViewClient.java".');
|
|
1341
|
+
} else if (fileContent.includes(legacyReloadCode)) {
|
|
1342
|
+
fileContent = fileContent.replace(legacyReloadCode, newCode);
|
|
1343
|
+
updated = true;
|
|
1344
|
+
} else if (fileContent.includes(oldCodeStart)) {
|
|
1345
|
+
fileContent = fileContent.replace(oldCodeStart, newCode);
|
|
1346
|
+
updated = true;
|
|
1347
|
+
} else {
|
|
1348
|
+
console.error('❌ Error: Neither old nor new code found. Unexpected content.');
|
|
1349
|
+
process.exit(1);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// Step 2: Check and add import if missing
|
|
1353
|
+
const importToast = 'import android.widget.Toast;';
|
|
1354
|
+
if (!fileContent.includes(importToast)) {
|
|
1355
|
+
console.log('✅ Adding missing import for Toast...');
|
|
1356
|
+
const importRegex = /import\s+[^;]+;/g;
|
|
1357
|
+
const matches = [...fileContent.matchAll(importRegex)];
|
|
1358
|
+
|
|
1359
|
+
if (matches.length > 0) {
|
|
1360
|
+
const lastImport = matches[matches.length - 1];
|
|
1361
|
+
const insertPosition = lastImport.index + lastImport[0].length;
|
|
1362
|
+
fileContent = fileContent.slice(0, insertPosition) + `\n${importToast}` + fileContent.slice(insertPosition);
|
|
1363
|
+
updated = true;
|
|
1364
|
+
} else {
|
|
1365
|
+
console.error('❌ Error: No import section found in file.');
|
|
1366
|
+
process.exit(1);
|
|
1367
|
+
}
|
|
1368
|
+
} else {
|
|
1369
|
+
console.log('ℹ️ Import for Toast already exists. No changes needed.');
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
// Step 3: Save if updated
|
|
1373
|
+
if (updated) {
|
|
1374
|
+
fs.writeFileSync(bridgeWebViewClientFilePath, fileContent.replace(/\n/g, bridgeWebViewLineEnding), 'utf8');
|
|
1375
|
+
console.log('✅ File updated successfully.');
|
|
1376
|
+
} else {
|
|
1377
|
+
console.log('ℹ️ No changes needed.');
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
|
|
1381
|
+
|
|
1382
|
+
|
|
1383
|
+
// Check and change the "BridgeWebViewClient.java" file END
|
|
1384
|
+
|
|
1385
|
+
|
|
1386
|
+
|
|
1387
|
+
|
|
1388
|
+
|
|
1389
|
+
|
|
1390
|
+
|
|
1391
|
+
|
|
1392
|
+
/*
|
|
1393
|
+
// To resolve the kotlin version issue, we need to update the kotlin version in the build.gradle file START
|
|
1394
|
+
|
|
1395
|
+
// Build the path dynamically like you requested
|
|
1396
|
+
const gradlePath = path.join(
|
|
1397
|
+
process.cwd(),
|
|
1398
|
+
'android',
|
|
1399
|
+
'build.gradle'
|
|
1400
|
+
);
|
|
1401
|
+
|
|
1402
|
+
// Read the existing build.gradle
|
|
1403
|
+
let gradleContent = fs.readFileSync(gradlePath, 'utf8');
|
|
1404
|
+
|
|
1405
|
+
// Add `ext.kotlin_version` if it's not already there
|
|
1406
|
+
if (!gradleContent.includes('ext.kotlin_version')) {
|
|
1407
|
+
gradleContent = gradleContent.replace(
|
|
1408
|
+
/buildscript\s*{/,
|
|
1409
|
+
`buildscript {\n ext.kotlin_version = '2.1.0'`
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// Add Kotlin classpath if it's not already there
|
|
1414
|
+
if (!gradleContent.includes('org.jetbrains.kotlin:kotlin-gradle-plugin')) {
|
|
1415
|
+
gradleContent = gradleContent.replace(
|
|
1416
|
+
/dependencies\s*{([\s\S]*?)classpath 'com.android.tools.build:gradle:8.7.2'/,
|
|
1417
|
+
`dependencies {\n classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")\n$1classpath 'com.android.tools.build:gradle:8.7.2'`
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// Write back the modified content
|
|
1422
|
+
fs.writeFileSync(gradlePath, gradleContent, 'utf8');
|
|
1423
|
+
|
|
1424
|
+
console.log('✅ Kotlin version updated in build.gradle.');
|
|
1425
|
+
|
|
1426
|
+
// To resolve the kotlin version issue, we need to update the kotlin version in the build.gradle file END
|
|
1427
|
+
*/
|
|
1428
|
+
|
|
1429
|
+
|
|
1430
|
+
|
|
1431
|
+
|
|
1432
|
+
|
|
1433
|
+
|
|
1434
|
+
|
|
1435
|
+
|
|
1436
|
+
let _admobConfig;
|
|
1437
|
+
|
|
1438
|
+
|
|
1439
|
+
|
|
1440
|
+
const androidPlatformPath = path.join(process.cwd(), 'android');
|
|
1441
|
+
const iosPlatformPath = path.join(process.cwd(), 'ios');
|
|
1442
|
+
const androidManifestPath = path.join(process.cwd(), 'android', 'app', 'src', 'main', 'AndroidManifest.xml');
|
|
1443
|
+
const androidAppBuildGradlePath = path.join(process.cwd(), 'android', 'app', 'build.gradle');
|
|
1444
|
+
const AMAZON_WINDOW_EXTENSIONS_DEPENDENCY = 'implementation "androidx.window.extensions.core:core:1.0.0"';
|
|
1445
|
+
const AMAZON_WINDOW_EXTENSIONS_DEPENDENCY_PATTERN = /^[ \t]*implementation\s+["']androidx\.window\.extensions\.core:core:[^"']+["'][ \t]*(?:\r?\n)?/gm;
|
|
1446
|
+
const infoPlistPath = path.join(process.cwd(), 'ios', 'App', 'App', 'Info.plist');
|
|
1447
|
+
const iosSceneDelegatePath = path.join(process.cwd(), 'ios', 'App', 'App', 'SceneDelegate.swift');
|
|
1448
|
+
const resourcesPath = path.join(process.cwd(), 'resources', 'res');
|
|
1449
|
+
const androidResPath = path.join(process.cwd(), 'android', 'app', 'src', 'main', 'res');
|
|
1450
|
+
const localNotificationsPluginPath = path.join(process.cwd(), 'node_modules', '@capacitor', 'local-notifications');
|
|
1451
|
+
|
|
1452
|
+
function fileExists(filePath) {
|
|
1453
|
+
return fs.existsSync(filePath);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
function copyFolderSync(source, target) {
|
|
1457
|
+
if (!fs.existsSync(target)) {
|
|
1458
|
+
fs.mkdirSync(target, { recursive: true });
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
fs.readdirSync(source).forEach(file => {
|
|
1462
|
+
const sourceFile = path.join(source, file);
|
|
1463
|
+
const targetFile = path.join(target, file);
|
|
1464
|
+
|
|
1465
|
+
if (fs.lstatSync(sourceFile).isDirectory()) {
|
|
1466
|
+
copyFolderSync(sourceFile, targetFile);
|
|
1467
|
+
} else {
|
|
1468
|
+
fs.copyFileSync(sourceFile, targetFile);
|
|
1469
|
+
}
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
function checkAndCopyResources() {
|
|
1474
|
+
if (fileExists(resourcesPath)) {
|
|
1475
|
+
copyFolderSync(resourcesPath, androidResPath);
|
|
1476
|
+
console.log('✅ Successfully copied resources/res to android/app/src/main/res.');
|
|
1477
|
+
} else {
|
|
1478
|
+
console.log('resources/res folder not found.');
|
|
1479
|
+
|
|
1480
|
+
if (fileExists(localNotificationsPluginPath)) {
|
|
1481
|
+
throw new Error('❌ resources/res is required for @capacitor/local-notifications. Stopping execution.');
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
|
|
1487
|
+
|
|
1488
|
+
|
|
1489
|
+
|
|
1490
|
+
|
|
1491
|
+
|
|
1492
|
+
|
|
1493
|
+
function getAdMobConfig() {
|
|
1494
|
+
if (!fileExists(configPath)) {
|
|
1495
|
+
throw new Error('❌ capacitor.config.json not found. Ensure this is a Capacitor project.');
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
const capacitorConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
1499
|
+
const packageJson = fileExists(packageJsonPath)
|
|
1500
|
+
? JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
1501
|
+
: {};
|
|
1502
|
+
|
|
1503
|
+
const legacyAdmobConfig = capacitorConfig.plugins?.AdMob || {};
|
|
1504
|
+
const nextGenAdmobConfig = packageJson.admob || {};
|
|
1505
|
+
const admobJsonAppIds = getAdmobJsonAppIds();
|
|
1506
|
+
|
|
1507
|
+
if (!legacyAdmobConfig && !nextGenAdmobConfig) {
|
|
1508
|
+
throw new Error('❌ AdMob configuration is missing in package.json or capacitor.config.json.');
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
const isEnabled =
|
|
1512
|
+
nextGenAdmobConfig.enabled !== false &&
|
|
1513
|
+
legacyAdmobConfig.ADMOB_ENABLED !== false;
|
|
1514
|
+
|
|
1515
|
+
if (!isEnabled) {
|
|
1516
|
+
return { ADMOB_ENABLED: false };
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
const androidAppId = admobJsonAppIds.androidAppId || nextGenAdmobConfig.androidAppId || legacyAdmobConfig.APP_ID_ANDROID;
|
|
1520
|
+
const iosAppId = admobJsonAppIds.iosAppId || nextGenAdmobConfig.iosAppId || legacyAdmobConfig.APP_ID_IOS;
|
|
1521
|
+
const userTrackingDescription =
|
|
1522
|
+
nextGenAdmobConfig.userTrackingDescription ||
|
|
1523
|
+
legacyAdmobConfig.USER_TRACKING_DESCRIPTION ||
|
|
1524
|
+
'This identifier will be used to deliver personalized ads to you.';
|
|
1525
|
+
|
|
1526
|
+
if (!androidAppId || !iosAppId) {
|
|
1527
|
+
throw new Error(' ❌ AdMob configuration is incomplete. Ensure androidAppId/iosAppId or APP_ID_ANDROID/APP_ID_IOS are defined.');
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
return {
|
|
1531
|
+
ADMOB_ENABLED: true,
|
|
1532
|
+
APP_ID_ANDROID: androidAppId,
|
|
1533
|
+
APP_ID_IOS: iosAppId,
|
|
1534
|
+
USER_TRACKING_DESCRIPTION: userTrackingDescription,
|
|
1535
|
+
USE_LITE_ADS: legacyAdmobConfig.USE_LITE_ADS === "lite",
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
function syncNextGenAdmobPackageJsonFromCapacitorConfig() {
|
|
1540
|
+
if (!fileExists(configPath) || !fileExists(packageJsonPath)) {
|
|
1541
|
+
return;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
const capacitorConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
1545
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
1546
|
+
|
|
1547
|
+
const legacyAdmobConfig = capacitorConfig.plugins?.AdMob || {};
|
|
1548
|
+
const admobJsonAppIds = getAdmobJsonAppIds();
|
|
1549
|
+
const androidAppId = admobJsonAppIds.androidAppId || legacyAdmobConfig.APP_ID_ANDROID;
|
|
1550
|
+
const iosAppId = admobJsonAppIds.iosAppId || legacyAdmobConfig.APP_ID_IOS;
|
|
1551
|
+
|
|
1552
|
+
if (!androidAppId || !iosAppId) {
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
const desiredAdmobConfig = {
|
|
1557
|
+
androidAppId,
|
|
1558
|
+
iosAppId,
|
|
1559
|
+
enableNativeAds: {
|
|
1560
|
+
ios: packageJson.admob?.enableNativeAds?.ios ?? true,
|
|
1561
|
+
android: packageJson.admob?.enableNativeAds?.android ?? true,
|
|
1562
|
+
},
|
|
1563
|
+
userTrackingDescription:
|
|
1564
|
+
packageJson.admob?.userTrackingDescription ||
|
|
1565
|
+
'This identifier will be used to deliver personalized ads to you.',
|
|
1566
|
+
};
|
|
1567
|
+
|
|
1568
|
+
const existingAdmobConfig = packageJson.admob || {};
|
|
1569
|
+
const mergedAdmobConfig = { ...existingAdmobConfig };
|
|
1570
|
+
let updated = false;
|
|
1571
|
+
|
|
1572
|
+
const syncKeyByKey = (target, source) => {
|
|
1573
|
+
for (const [key, value] of Object.entries(source)) {
|
|
1574
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
1575
|
+
if (!target[key] || typeof target[key] !== 'object' || Array.isArray(target[key])) {
|
|
1576
|
+
target[key] = {};
|
|
1577
|
+
updated = true;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
syncKeyByKey(target[key], value);
|
|
1581
|
+
continue;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
if (target[key] !== value) {
|
|
1585
|
+
target[key] = value;
|
|
1586
|
+
updated = true;
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
};
|
|
1590
|
+
|
|
1591
|
+
syncKeyByKey(mergedAdmobConfig, desiredAdmobConfig);
|
|
1592
|
+
|
|
1593
|
+
if (!updated) {
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
packageJson.admob = mergedAdmobConfig;
|
|
1598
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
|
|
1599
|
+
console.log(admobJsonAppIds.androidAppId || admobJsonAppIds.iosAppId
|
|
1600
|
+
? '✅ package.json AdMob settings synced from AdMob configuration JSON'
|
|
1601
|
+
: '✅ package.json AdMob settings synced from capacitor.config.json');
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
function validateAndroidBuildOptions() {
|
|
1605
|
+
|
|
1606
|
+
|
|
1607
|
+
if (!fileExists(configPath)) {
|
|
1608
|
+
console.log('❌ capacitor.config.json not found. Ensure this is a Capacitor project.');
|
|
1609
|
+
process.exit(1);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
1613
|
+
|
|
1614
|
+
const targetAppId=config.appId
|
|
1615
|
+
|
|
1616
|
+
const buildOptions = config.android?.buildOptions;
|
|
1617
|
+
|
|
1618
|
+
if (!buildOptions) {
|
|
1619
|
+
console.log('❌ Missing android.buildOptions in capacitor.config.json.');
|
|
1620
|
+
process.exit(1);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
const requiredProps = [
|
|
1624
|
+
'keystorePath',
|
|
1625
|
+
'keystorePassword',
|
|
1626
|
+
'keystoreAlias',
|
|
1627
|
+
'keystoreAliasPassword',
|
|
1628
|
+
'releaseType',
|
|
1629
|
+
'signingType'
|
|
1630
|
+
];
|
|
1631
|
+
|
|
1632
|
+
const missing = requiredProps.filter(prop => !buildOptions[prop]);
|
|
1633
|
+
|
|
1634
|
+
if (missing.length > 0) {
|
|
1635
|
+
console.log('❌ Missing properties android.buildOptions in capacitor.config.json.');
|
|
1636
|
+
process.exit(1);
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
|
|
1640
|
+
const keystorePath=buildOptions.keystorePath
|
|
1641
|
+
const keyFileName = path.basename(keystorePath);
|
|
1642
|
+
|
|
1643
|
+
|
|
1644
|
+
|
|
1645
|
+
const keystoreMap = {
|
|
1646
|
+
"gameskey.jks": [
|
|
1647
|
+
"com.cube.blaster",
|
|
1648
|
+
],
|
|
1649
|
+
"htmleditorkeystoke.jks": [
|
|
1650
|
+
"com.HTML.AngularJS.Codeplay",
|
|
1651
|
+
"com.html.codeplay.pro",
|
|
1652
|
+
"com.bootstrap.code.play",
|
|
1653
|
+
"com.kids.learning.master",
|
|
1654
|
+
"com.Simple.Barcode.Scanner",
|
|
1655
|
+
"com.FAShtmlcssjs.editor"
|
|
1656
|
+
]
|
|
1657
|
+
};
|
|
1658
|
+
|
|
1659
|
+
// find which keystore is required for the given targetAppId
|
|
1660
|
+
let requiredKey = "newappskey.jks"; // default
|
|
1661
|
+
for (const [keyFile, appIds] of Object.entries(keystoreMap)) {
|
|
1662
|
+
if (appIds.includes(targetAppId)) {
|
|
1663
|
+
requiredKey = keyFile;
|
|
1664
|
+
break;
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
// validate
|
|
1669
|
+
if (keyFileName !== requiredKey) {
|
|
1670
|
+
console.log(`❌ The keystore path is mismatched. Expected ${requiredKey} for ${targetAppId}, but got ${keyFileName}`);
|
|
1671
|
+
process.exit(1);
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
|
|
1675
|
+
|
|
1676
|
+
|
|
1677
|
+
|
|
1678
|
+
// optionally return them
|
|
1679
|
+
//return buildOptions;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
function updateAndroidManifest(admobConfig) {
|
|
1683
|
+
if (!fileExists(androidManifestPath)) {
|
|
1684
|
+
console.error(' ❌ AndroidManifest.xml not found. Ensure the Android platform is added.');
|
|
1685
|
+
return;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
let manifestContent = fs.readFileSync(androidManifestPath, 'utf8');
|
|
1689
|
+
const metaDataTag = `<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="${admobConfig.APP_ID_ANDROID}" />`;
|
|
1690
|
+
|
|
1691
|
+
if (/com\.google\.android\.gms\.ads\.APPLICATION_ID/.test(manifestContent)) {
|
|
1692
|
+
manifestContent = manifestContent.replace(
|
|
1693
|
+
/<meta-data\s+android:name="com\.google\.android\.gms\.ads\.APPLICATION_ID"\s+android:value=".*?"\s*\/>/,
|
|
1694
|
+
metaDataTag
|
|
1695
|
+
);
|
|
1696
|
+
} else {
|
|
1697
|
+
manifestContent = manifestContent.replace(
|
|
1698
|
+
/<application\b([^>]*)>/,
|
|
1699
|
+
`<application$1>\n ${metaDataTag}`
|
|
1700
|
+
);
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
fs.writeFileSync(androidManifestPath, manifestContent, 'utf8');
|
|
1704
|
+
console.log('✅ AdMob Android App ID successfully updated in AndroidManifest.xml');
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
function updateInfoPlist(admobConfig) {
|
|
1708
|
+
if (!fileExists(infoPlistPath)) {
|
|
1709
|
+
console.error('Info.plist not found. Ensure you have built the iOS project.');
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
const plistContent = fs.readFileSync(infoPlistPath, 'utf8');
|
|
1714
|
+
const plistData = plist.parse(plistContent);
|
|
1715
|
+
|
|
1716
|
+
plistData.GADApplicationIdentifier = admobConfig.APP_ID_IOS;
|
|
1717
|
+
if (isKidsAdmobApp()) delete plistData.NSUserTrackingUsageDescription;
|
|
1718
|
+
else plistData.NSUserTrackingUsageDescription = admobConfig.USER_TRACKING_DESCRIPTION;
|
|
1719
|
+
plistData.GADDelayAppMeasurementInit = true;
|
|
1720
|
+
|
|
1721
|
+
// https://developers.google.com/admob/ios/quick-start
|
|
1722
|
+
plistData.SKAdNetworkItems = [
|
|
1723
|
+
{ SKAdNetworkIdentifier: 'cstr6suwn9.skadnetwork' }, // Google
|
|
1724
|
+
{ SKAdNetworkIdentifier: '4fzdc2evr5.skadnetwork' }, // Aarki
|
|
1725
|
+
{ SKAdNetworkIdentifier: '2fnua5tdw4.skadnetwork' }, // Adform
|
|
1726
|
+
{ SKAdNetworkIdentifier: 'ydx93a7ass.skadnetwork' }, // Adikteev
|
|
1727
|
+
{ SKAdNetworkIdentifier: 'p78axxw29g.skadnetwork' }, // Amazon
|
|
1728
|
+
{ SKAdNetworkIdentifier: 'v72qych5uu.skadnetwork' }, // Appier
|
|
1729
|
+
{ SKAdNetworkIdentifier: 'ludvb6z3bs.skadnetwork' }, // Applovin
|
|
1730
|
+
{ SKAdNetworkIdentifier: 'cp8zw746q7.skadnetwork' }, // Arpeely
|
|
1731
|
+
{ SKAdNetworkIdentifier: '3sh42y64q3.skadnetwork' }, // Basis
|
|
1732
|
+
{ SKAdNetworkIdentifier: 'c6k4g5qg8m.skadnetwork' }, // Beeswax.io
|
|
1733
|
+
{ SKAdNetworkIdentifier: 's39g8k73mm.skadnetwork' }, // Bidease
|
|
1734
|
+
{ SKAdNetworkIdentifier: '3qy4746246.skadnetwork' }, // Bigabid
|
|
1735
|
+
{ SKAdNetworkIdentifier: 'hs6bdukanm.skadnetwork' }, // Criteo
|
|
1736
|
+
{ SKAdNetworkIdentifier: 'mlmmfzh3r3.skadnetwork' }, // Digital Turbine DSP
|
|
1737
|
+
{ SKAdNetworkIdentifier: 'v4nxqhlyqp.skadnetwork' }, // i-mobile
|
|
1738
|
+
{ SKAdNetworkIdentifier: 'wzmmz9fp6w.skadnetwork' }, // InMobi
|
|
1739
|
+
{ SKAdNetworkIdentifier: 'su67r6k2v3.skadnetwork' }, // ironSource Ads
|
|
1740
|
+
{ SKAdNetworkIdentifier: 'yclnxrl5pm.skadnetwork' }, // Jampp
|
|
1741
|
+
{ SKAdNetworkIdentifier: '7ug5zh24hu.skadnetwork' }, // Liftoff
|
|
1742
|
+
{ SKAdNetworkIdentifier: 'gta9lk7p23.skadnetwork' }, // Liftoff Monetize
|
|
1743
|
+
{ SKAdNetworkIdentifier: 'vutu7akeur.skadnetwork' }, // LINE
|
|
1744
|
+
{ SKAdNetworkIdentifier: 'y5ghdn5j9k.skadnetwork' }, // Mediaforce
|
|
1745
|
+
{ SKAdNetworkIdentifier: 'v9wttpbfk9.skadnetwork' }, // Meta (1 of 2)
|
|
1746
|
+
{ SKAdNetworkIdentifier: 'n38lu8286q.skadnetwork' }, // Meta (2 of 2)
|
|
1747
|
+
{ SKAdNetworkIdentifier: '47vhws6wlr.skadnetwork' }, // MicroAd
|
|
1748
|
+
{ SKAdNetworkIdentifier: 'kbd757ywx3.skadnetwork' }, // Mintegral / Mobvista
|
|
1749
|
+
{ SKAdNetworkIdentifier: '9t245vhmpl.skadnetwork' }, // Moloco
|
|
1750
|
+
{ SKAdNetworkIdentifier: 'a2p9lx4jpn.skadnetwork' }, // Opera
|
|
1751
|
+
{ SKAdNetworkIdentifier: '22mmun2rn5.skadnetwork' }, // Pangle
|
|
1752
|
+
{ SKAdNetworkIdentifier: '4468km3ulz.skadnetwork' }, // Realtime Technologies GmbH
|
|
1753
|
+
{ SKAdNetworkIdentifier: '2u9pt9hc89.skadnetwork' }, // Remerge
|
|
1754
|
+
{ SKAdNetworkIdentifier: '8s468mfl3y.skadnetwork' }, // RTB House
|
|
1755
|
+
{ SKAdNetworkIdentifier: 'ppxm28t8ap.skadnetwork' }, // Smadex
|
|
1756
|
+
{ SKAdNetworkIdentifier: 'uw77j35x4d.skadnetwork' }, // The Trade Desk
|
|
1757
|
+
{ SKAdNetworkIdentifier: 'pwa73g5rt2.skadnetwork' }, // Tremor
|
|
1758
|
+
{ SKAdNetworkIdentifier: '578prtvx9j.skadnetwork' }, // Unicorn
|
|
1759
|
+
{ SKAdNetworkIdentifier: '4dzt52r2t5.skadnetwork' }, // Unity Ads
|
|
1760
|
+
{ SKAdNetworkIdentifier: 'tl55sbb4fm.skadnetwork' }, // Verve
|
|
1761
|
+
{ SKAdNetworkIdentifier: 'e5fvkxwrpn.skadnetwork' }, // Yahoo!
|
|
1762
|
+
{ SKAdNetworkIdentifier: '8c4e2ghe7u.skadnetwork' }, // Yahoo! Japan Ads
|
|
1763
|
+
{ SKAdNetworkIdentifier: '3rd42ekr43.skadnetwork' }, // YouAppi
|
|
1764
|
+
{ SKAdNetworkIdentifier: '3qcr597p9d.skadnetwork' }, // Zucks
|
|
1765
|
+
];
|
|
1766
|
+
|
|
1767
|
+
const updatedPlistContent = plist.build(plistData);
|
|
1768
|
+
fs.writeFileSync(infoPlistPath, updatedPlistContent, 'utf8');
|
|
1769
|
+
console.log('AdMob IDs and additional configurations successfully updated in Info.plist');
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
function updateIOSSceneDelegateForATT() {
|
|
1773
|
+
if (!fileExists(iosSceneDelegatePath)) {
|
|
1774
|
+
console.warn('SceneDelegate.swift not found. Skipping the iOS ATT scene update.');
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
let content = fs.readFileSync(iosSceneDelegatePath, 'utf8');
|
|
1779
|
+
const newline = content.includes('\r\n') ? '\r\n' : '\n';
|
|
1780
|
+
const requestBody = [
|
|
1781
|
+
' if ATTrackingManager.trackingAuthorizationStatus == .notDetermined &&',
|
|
1782
|
+
' !isTrackingAuthorizationRequestScheduled {',
|
|
1783
|
+
' isTrackingAuthorizationRequestScheduled = true',
|
|
1784
|
+
'',
|
|
1785
|
+
' DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in',
|
|
1786
|
+
' self?.isTrackingAuthorizationRequestScheduled = false',
|
|
1787
|
+
'',
|
|
1788
|
+
' guard UIApplication.shared.applicationState == .active,',
|
|
1789
|
+
' ATTrackingManager.trackingAuthorizationStatus == .notDetermined else { return }',
|
|
1790
|
+
'',
|
|
1791
|
+
' ATTrackingManager.requestTrackingAuthorization { _ in }',
|
|
1792
|
+
' }',
|
|
1793
|
+
' }',
|
|
1794
|
+
].join(newline);
|
|
1795
|
+
|
|
1796
|
+
// ATT is owned by the ad bootstrap after UMP consent. Remove the block
|
|
1797
|
+
// injected by earlier builds so scene activation cannot race that flow.
|
|
1798
|
+
const originalContent = content;
|
|
1799
|
+
content = content.split(requestBody).join('');
|
|
1800
|
+
if (content.includes('ATTrackingManager.requestTrackingAuthorization')) {
|
|
1801
|
+
throw new Error('SceneDelegate.swift still contains a custom ATT request that can precede UMP consent. Remove that request before building.');
|
|
1802
|
+
}
|
|
1803
|
+
content = content.replace(/^[ \t]*private var isTrackingAuthorizationRequestScheduled = false\r?\n/gm, '');
|
|
1804
|
+
if (!content.includes('ATTrackingManager')) {
|
|
1805
|
+
content = content.replace(/^import AppTrackingTransparency\r?\n/gm, '');
|
|
1806
|
+
}
|
|
1807
|
+
if (content !== originalContent) fs.writeFileSync(iosSceneDelegatePath, content, 'utf8');
|
|
1808
|
+
console.log('iOS startup ATT prompt disabled; the ad consent flow controls ATT.');
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
|
|
1812
|
+
try {
|
|
1813
|
+
if (!fileExists(configPath)) {
|
|
1814
|
+
throw new Error(' ❌ capacitor.config.json not found. Skipping setup.');
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
if (!fileExists(androidPlatformPath) && !fileExists(iosPlatformPath)) {
|
|
1818
|
+
throw new Error('Neither Android nor iOS platforms are found. Ensure platforms are added to your Capacitor project.');
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
checkAndCopyResources();
|
|
1822
|
+
|
|
1823
|
+
syncNextGenAdmobPackageJsonFromCapacitorConfig();
|
|
1824
|
+
|
|
1825
|
+
|
|
1826
|
+
_admobConfig = getAdMobConfig();
|
|
1827
|
+
|
|
1828
|
+
|
|
1829
|
+
|
|
1830
|
+
|
|
1831
|
+
|
|
1832
|
+
// Proceed only if ADMOB_ENABLED is true
|
|
1833
|
+
if (_admobConfig.ADMOB_ENABLED) {
|
|
1834
|
+
if (fileExists(androidPlatformPath)) {
|
|
1835
|
+
updateAndroidManifest(_admobConfig);
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
if (fileExists(iosPlatformPath)) {
|
|
1839
|
+
updateInfoPlist(_admobConfig);
|
|
1840
|
+
updateIOSSceneDelegateForATT();
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
|
|
1845
|
+
} catch (error) {
|
|
1846
|
+
console.error(error.message);
|
|
1847
|
+
process.exit(1); // Stop execution if there's a critical error
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
|
|
1851
|
+
|
|
1852
|
+
validateAndroidBuildOptions();
|
|
1853
|
+
|
|
1854
|
+
|
|
1855
|
+
|
|
1856
|
+
|
|
1857
|
+
|
|
1858
|
+
|
|
1859
|
+
// Check all the codeplays plugins version START
|
|
1860
|
+
|
|
1861
|
+
|
|
1862
|
+
|
|
1863
|
+
|
|
1864
|
+
//const srcDir = path.join(__dirname, 'src');
|
|
1865
|
+
const srcDir = path.join(process.cwd(), 'src');
|
|
1866
|
+
let outdatedPlugins = [];
|
|
1867
|
+
|
|
1868
|
+
function parseVersion(ver) {
|
|
1869
|
+
return ver.split('.').map(n => parseInt(n, 10));
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
function compareVersions(v1, v2) {
|
|
1873
|
+
const [a1, b1] = parseVersion(v1);
|
|
1874
|
+
const [a2, b2] = parseVersion(v2);
|
|
1875
|
+
if (a1 !== a2) return a1 - a2;
|
|
1876
|
+
return b1 - b2;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
function walkSync(dir, filelist = []) {
|
|
1880
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
1881
|
+
const fullPath = path.join(dir, entry.name);
|
|
1882
|
+
const isDirectory = entry.isDirectory() || (entry.isSymbolicLink() && fs.statSync(fullPath).isDirectory());
|
|
1883
|
+
if (isDirectory) walkSync(fullPath, filelist);
|
|
1884
|
+
else filelist.push(fullPath);
|
|
1885
|
+
}
|
|
1886
|
+
return filelist;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
function getSearchRoot(plugin) {
|
|
1890
|
+
return path.join(srcDir, plugin.baseDir || 'js');
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
|
|
1894
|
+
|
|
1895
|
+
|
|
1896
|
+
|
|
1897
|
+
|
|
1898
|
+
|
|
1899
|
+
|
|
1900
|
+
|
|
1901
|
+
|
|
1902
|
+
|
|
1903
|
+
|
|
1904
|
+
|
|
1905
|
+
|
|
1906
|
+
|
|
1907
|
+
|
|
1908
|
+
|
|
1909
|
+
|
|
1910
|
+
|
|
1911
|
+
|
|
1912
|
+
/*############################################## AUTO DOWNLOAD FROM SERVER START #####################################*/
|
|
1913
|
+
|
|
1914
|
+
// ============================================================
|
|
1915
|
+
// 🔥 AUTO PLUGIN UPDATE SYSTEM (MANDATORY UPDATES)
|
|
1916
|
+
// ============================================================
|
|
1917
|
+
|
|
1918
|
+
const https = require("https");
|
|
1919
|
+
|
|
1920
|
+
/**
|
|
1921
|
+
* Check if file exists on server using HEAD request
|
|
1922
|
+
*/
|
|
1923
|
+
function urlExists(url) {
|
|
1924
|
+
return new Promise(resolve => {
|
|
1925
|
+
const req = https.request(url, { method: "HEAD" }, res => {
|
|
1926
|
+
res.resume();
|
|
1927
|
+
resolve(res.statusCode === 200);
|
|
1928
|
+
});
|
|
1929
|
+
|
|
1930
|
+
req.on("error", () => resolve(false));
|
|
1931
|
+
req.setTimeout(8000, () => {
|
|
1932
|
+
req.destroy();
|
|
1933
|
+
resolve(false);
|
|
1934
|
+
});
|
|
1935
|
+
req.end();
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
/**
|
|
1940
|
+
* Download file from server
|
|
1941
|
+
*/
|
|
1942
|
+
function downloadFile(url, dest) {
|
|
1943
|
+
if (path.resolve(dest).startsWith(path.join(process.cwd(), 'src') + path.sep)) sourceFileInventory = null;
|
|
1944
|
+
return new Promise((resolve, reject) => {
|
|
1945
|
+
const file = fs.createWriteStream(dest);
|
|
1946
|
+
|
|
1947
|
+
const req = https.get(url, response => {
|
|
1948
|
+
if (response.statusCode !== 200) {
|
|
1949
|
+
response.resume();
|
|
1950
|
+
file.close(() => fs.unlink(dest, () => {}));
|
|
1951
|
+
reject("Download failed");
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
response.pipe(file);
|
|
1956
|
+
|
|
1957
|
+
file.on("finish", () => {
|
|
1958
|
+
file.close(resolve);
|
|
1959
|
+
});
|
|
1960
|
+
}).on("error", err => {
|
|
1961
|
+
fs.unlink(dest, () => {});
|
|
1962
|
+
reject(err);
|
|
1963
|
+
});
|
|
1964
|
+
|
|
1965
|
+
req.setTimeout(30000, () => {
|
|
1966
|
+
req.destroy();
|
|
1967
|
+
file.close(() => fs.unlink(dest, () => {}));
|
|
1968
|
+
reject(new Error(`Download timed out: ${url}`));
|
|
1969
|
+
});
|
|
1970
|
+
});
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
/**
|
|
1974
|
+
* Update imports across src folder
|
|
1975
|
+
* Replaces old filename → new filename
|
|
1976
|
+
*/
|
|
1977
|
+
/**
|
|
1978
|
+
* Update imports across project
|
|
1979
|
+
* Replaces old filename → new filename
|
|
1980
|
+
*/
|
|
1981
|
+
|
|
1982
|
+
const VITE_ALIAS_ONLY = [
|
|
1983
|
+
"common",
|
|
1984
|
+
"admob-emi",
|
|
1985
|
+
"admob-emi-nextgen",
|
|
1986
|
+
"localization",
|
|
1987
|
+
"theme",
|
|
1988
|
+
"certificatejs",
|
|
1989
|
+
"ffmpeg"
|
|
1990
|
+
];
|
|
1991
|
+
|
|
1992
|
+
function updateImports(oldName, newName) {
|
|
1993
|
+
|
|
1994
|
+
const projectRoot = process.cwd();
|
|
1995
|
+
const oldSaveHelper = oldName.match(/^(saveToGalleryAndSaveAnyFile-\d+(?:\.\d+)*)(?:-ios)?\.js$/);
|
|
1996
|
+
const newSaveHelper = newName.match(/^(saveToGalleryAndSaveAnyFile-\d+(?:\.\d+)*)(?:-ios)?\.js$/);
|
|
1997
|
+
|
|
1998
|
+
const filesToScan = [
|
|
1999
|
+
path.join(projectRoot, "vite.config.js"),
|
|
2000
|
+
path.join(projectRoot, "vite.config.mjs")
|
|
2001
|
+
];
|
|
2002
|
+
|
|
2003
|
+
const srcDir = path.join(projectRoot, "src");
|
|
2004
|
+
|
|
2005
|
+
// scan vite config
|
|
2006
|
+
filesToScan.forEach(file => {
|
|
2007
|
+
|
|
2008
|
+
if (!fs.existsSync(file)) return;
|
|
2009
|
+
|
|
2010
|
+
let content = fs.readFileSync(file, "utf8");
|
|
2011
|
+
|
|
2012
|
+
if (content.includes(oldName) || (oldSaveHelper && newSaveHelper && content.includes('@saveGallery'))) {
|
|
2013
|
+
|
|
2014
|
+
content = content.split(oldName).join(newName);
|
|
2015
|
+
// Plugin updates can replace an iOS filename with the generic new filename.
|
|
2016
|
+
// Keep the centralized save-gallery alias aligned with the selected build.
|
|
2017
|
+
content = content.replace(/(['"]@saveGallery['"]\s*:\s*path\.resolve\(\s*__dirname\s*,\s*['"])([^'"]+)(['"]\s*\))/g,
|
|
2018
|
+
(match, prefix, target, suffix) => {
|
|
2019
|
+
if (!/saveToGalleryAndSaveAnyFile-\d+(?:\.\d+)*(?:-ios)?\.js$/.test(target)) return match;
|
|
2020
|
+
if (oldSaveHelper && newSaveHelper && target.includes(oldSaveHelper[1])) {
|
|
2021
|
+
target = target.replace(oldSaveHelper[1], newSaveHelper[1]);
|
|
2022
|
+
}
|
|
2023
|
+
const nextTarget = target.replace(/(?:-ios)?\.js$/, buildPlatform === 'ios' ? '-ios.js' : '.js');
|
|
2024
|
+
if (!fs.existsSync(path.resolve(path.dirname(file), nextTarget))) {
|
|
2025
|
+
throw new Error(`❌ Updated @saveGallery target is missing: ${nextTarget}`);
|
|
2026
|
+
}
|
|
2027
|
+
return prefix + nextTarget + suffix;
|
|
2028
|
+
});
|
|
2029
|
+
|
|
2030
|
+
fs.writeFileSync(file, content);
|
|
2031
|
+
|
|
2032
|
+
console.log(`✏️ Updated alias in ${path.basename(file)}`);
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
});
|
|
2036
|
+
|
|
2037
|
+
// Downloads invalidate the inventory; content-only edits reuse it.
|
|
2038
|
+
for (const full of getSourceFiles()) {
|
|
2039
|
+
const relativePath = path.relative(srcDir, full);
|
|
2040
|
+
if (relativePath.split(path.sep).some(part => ['node_modules', 'android', 'ios', 'dist', '.git'].includes(part))) continue;
|
|
2041
|
+
if (!/\.(js|f7|mjs)$/.test(full) || full.endsWith('.min.js')) continue;
|
|
2042
|
+
let content = fs.readFileSync(full, 'utf8');
|
|
2043
|
+
if (!content.includes(oldName)) continue;
|
|
2044
|
+
content = content.split(oldName).join(newName);
|
|
2045
|
+
fs.writeFileSync(full, content);
|
|
2046
|
+
console.log(`✏️ Updated import in ${path.relative(projectRoot, full)}`);
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
|
|
2052
|
+
/**
|
|
2053
|
+
* Auto-update a plugin file
|
|
2054
|
+
* Returns TRUE if success
|
|
2055
|
+
* Returns FALSE if fallback to manual needed
|
|
2056
|
+
*/
|
|
2057
|
+
|
|
2058
|
+
|
|
2059
|
+
let _serverVersions = null;
|
|
2060
|
+
async function fetchVersions() {
|
|
2061
|
+
|
|
2062
|
+
if (_serverVersions) return _serverVersions;
|
|
2063
|
+
|
|
2064
|
+
return new Promise((resolve) => {
|
|
2065
|
+
|
|
2066
|
+
https.get(
|
|
2067
|
+
"https://htmlcodeplay.com/code-play-plugin/versions.json",
|
|
2068
|
+
{ timeout: 5000 },
|
|
2069
|
+
res => {
|
|
2070
|
+
|
|
2071
|
+
let data = "";
|
|
2072
|
+
|
|
2073
|
+
res.on("data", chunk => data += chunk);
|
|
2074
|
+
|
|
2075
|
+
res.on("end", () => {
|
|
2076
|
+
|
|
2077
|
+
try {
|
|
2078
|
+
|
|
2079
|
+
_serverVersions = JSON.parse(data);
|
|
2080
|
+
|
|
2081
|
+
resolve(_serverVersions);
|
|
2082
|
+
|
|
2083
|
+
} catch {
|
|
2084
|
+
|
|
2085
|
+
resolve(null);
|
|
2086
|
+
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
});
|
|
2090
|
+
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
).on("error", () => resolve(null));
|
|
2094
|
+
|
|
2095
|
+
});
|
|
2096
|
+
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
function getAgentInstructionsZipLayout(zip) {
|
|
2100
|
+
const entries = zip.getEntries().filter(entry => !entry.isDirectory);
|
|
2101
|
+
|
|
2102
|
+
for (const entry of entries) {
|
|
2103
|
+
const normalizedName = entry.entryName.replace(/\\/g, "/");
|
|
2104
|
+
if (
|
|
2105
|
+
normalizedName.startsWith("/") ||
|
|
2106
|
+
normalizedName.split("/").includes("..") ||
|
|
2107
|
+
path.isAbsolute(entry.entryName)
|
|
2108
|
+
) {
|
|
2109
|
+
throw new Error(`Unsafe path in agent instructions ZIP: ${entry.entryName}`);
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
const normalizedEntries = entries.map(entry => ({
|
|
2114
|
+
entry,
|
|
2115
|
+
name: entry.entryName.replace(/\\/g, "/")
|
|
2116
|
+
}));
|
|
2117
|
+
const agentFile = normalizedEntries.find(({ name }) =>
|
|
2118
|
+
name.toLowerCase() === AGENT_INSTRUCTIONS_FILE.toLowerCase()
|
|
2119
|
+
);
|
|
2120
|
+
const supportFiles = normalizedEntries
|
|
2121
|
+
.filter(({ name }) => name.toLowerCase() !== AGENT_INSTRUCTIONS_FILE.toLowerCase())
|
|
2122
|
+
.map(({ entry, name }) => {
|
|
2123
|
+
const agentDirectoryPrefix = `${AGENT_INSTRUCTIONS_DIR.toLowerCase()}/`;
|
|
2124
|
+
const relativePath = name.toLowerCase().startsWith(agentDirectoryPrefix)
|
|
2125
|
+
? name.slice(name.indexOf("/") + 1)
|
|
2126
|
+
: name;
|
|
2127
|
+
|
|
2128
|
+
return { entry, relativePath };
|
|
2129
|
+
})
|
|
2130
|
+
.filter(({ relativePath }) =>
|
|
2131
|
+
relativePath &&
|
|
2132
|
+
path.basename(relativePath).toLowerCase() !== AGENT_INSTRUCTIONS_VERSION_FILE.toLowerCase()
|
|
2133
|
+
);
|
|
2134
|
+
|
|
2135
|
+
if (!agentFile || !supportFiles.length) {
|
|
2136
|
+
throw new Error(
|
|
2137
|
+
`Agent instructions ZIP must contain ${AGENT_INSTRUCTIONS_FILE} (case-insensitive) and at least one supporting file.`
|
|
2138
|
+
);
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
return { agentFile: agentFile.entry, supportFiles };
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
function readInstalledAgentInstructionsVersion(projectRoot) {
|
|
2145
|
+
const versionFile = path.join(projectRoot, AGENT_INSTRUCTIONS_DIR, AGENT_INSTRUCTIONS_VERSION_FILE);
|
|
2146
|
+
if (!fs.existsSync(versionFile)) return "0";
|
|
2147
|
+
|
|
2148
|
+
const installedVersion = String(fs.readFileSync(versionFile, "utf8")).trim();
|
|
2149
|
+
return /^\d+(?:\.\d+)*$/.test(installedVersion) ? installedVersion : "0";
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
async function syncAgentInstructions() {
|
|
2153
|
+
const projectRoot = process.cwd();
|
|
2154
|
+
const targetAgentFile = path.join(projectRoot, AGENT_INSTRUCTIONS_FILE);
|
|
2155
|
+
const targetAgentDir = path.join(projectRoot, AGENT_INSTRUCTIONS_DIR);
|
|
2156
|
+
const localVersion = readInstalledAgentInstructionsVersion(projectRoot);
|
|
2157
|
+
|
|
2158
|
+
const versions = await fetchVersions();
|
|
2159
|
+
const remoteVersion = String(versions?.[AGENT_INSTRUCTIONS_VERSION_KEY] || "").trim();
|
|
2160
|
+
|
|
2161
|
+
if (!/^\d+(?:\.\d+)*$/.test(remoteVersion)) {
|
|
2162
|
+
throw new Error(`Required agent instructions could not be resolved from the server.`);
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
const remoteIsNewer = compareVersionStrings(remoteVersion, localVersion) > 0;
|
|
2166
|
+
if (!remoteIsNewer) {
|
|
2167
|
+
console.log(`✅ Agent instructions do not need an update (local ${localVersion}, online ${remoteVersion}).`);
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
const zipName = `${AGENT_INSTRUCTIONS_ZIP_PREFIX}-${remoteVersion}.zip`;
|
|
2172
|
+
const url = `https://htmlcodeplay.com/code-play-plugin/${zipName}`;
|
|
2173
|
+
const tempRoot = fs.mkdtempSync(path.join(require("os").tmpdir(), "codeplay-agents-"));
|
|
2174
|
+
const zipPath = path.join(tempRoot, zipName);
|
|
2175
|
+
const extractPath = path.join(tempRoot, "extracted");
|
|
2176
|
+
|
|
2177
|
+
try {
|
|
2178
|
+
console.log(`🔍 Checking latest agent instructions: ${zipName}`);
|
|
2179
|
+
if (!(await urlExists(url))) {
|
|
2180
|
+
throw new Error(`Required agent instructions archive not found: ${url}`);
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
await downloadFile(url, zipPath);
|
|
2184
|
+
console.log(`⬇️ Downloaded agent instructions archive: ${zipName}`);
|
|
2185
|
+
const zip = new AdmZip(zipPath);
|
|
2186
|
+
const archiveLayout = getAgentInstructionsZipLayout(zip);
|
|
2187
|
+
|
|
2188
|
+
const stagedAgentFile = path.join(extractPath, AGENT_INSTRUCTIONS_FILE);
|
|
2189
|
+
const stagedAgentDir = path.join(extractPath, AGENT_INSTRUCTIONS_DIR);
|
|
2190
|
+
fs.mkdirSync(stagedAgentDir, { recursive: true });
|
|
2191
|
+
fs.writeFileSync(stagedAgentFile, archiveLayout.agentFile.getData());
|
|
2192
|
+
archiveLayout.supportFiles.forEach(({ entry, relativePath }) => {
|
|
2193
|
+
const stagedSupportFile = path.join(stagedAgentDir, relativePath);
|
|
2194
|
+
fs.mkdirSync(path.dirname(stagedSupportFile), { recursive: true });
|
|
2195
|
+
fs.writeFileSync(stagedSupportFile, entry.getData());
|
|
2196
|
+
});
|
|
2197
|
+
|
|
2198
|
+
fs.rmSync(targetAgentDir, { recursive: true, force: true });
|
|
2199
|
+
fs.mkdirSync(targetAgentDir, { recursive: true });
|
|
2200
|
+
fs.copyFileSync(stagedAgentFile, targetAgentFile);
|
|
2201
|
+
fs.cpSync(stagedAgentDir, targetAgentDir, { recursive: true, force: true });
|
|
2202
|
+
fs.writeFileSync(
|
|
2203
|
+
path.join(targetAgentDir, AGENT_INSTRUCTIONS_VERSION_FILE),
|
|
2204
|
+
`${remoteVersion}\n`,
|
|
2205
|
+
"utf8"
|
|
2206
|
+
);
|
|
2207
|
+
|
|
2208
|
+
writeUpdateLine(`Agent instructions ${localVersion} -> ${remoteVersion}`);
|
|
2209
|
+
console.log(`✅ Agent instructions replaced in the project root → v${remoteVersion}.`);
|
|
2210
|
+
} catch (error) {
|
|
2211
|
+
throw new Error(`Required agent instructions installation failed: ${error?.message || error}`);
|
|
2212
|
+
} finally {
|
|
2213
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
|
|
2218
|
+
|
|
2219
|
+
|
|
2220
|
+
|
|
2221
|
+
async function autoUpdatePlugin(pluginDef, pluginInfo) {
|
|
2222
|
+
|
|
2223
|
+
const versions = await fetchVersions();
|
|
2224
|
+
|
|
2225
|
+
if (!versions) {
|
|
2226
|
+
console.log("⚠️ versions.json not reachable");
|
|
2227
|
+
return false;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
const oldFullPath = path.join(srcDir, pluginInfo.name);
|
|
2231
|
+
const oldFileName = path.basename(oldFullPath);
|
|
2232
|
+
|
|
2233
|
+
//const oldFileName = path.basename(oldFullPath);
|
|
2234
|
+
const oldVersionFile = oldFileName;
|
|
2235
|
+
|
|
2236
|
+
|
|
2237
|
+
const ext = path.extname(oldFileName); // .js or .less
|
|
2238
|
+
//const baseName = oldFileName.replace(/-\d+\.\d+.*$/, "");
|
|
2239
|
+
const baseName = oldFileName.replace(/-\d+\.\d+.*$/, '').replace(/\.(js|less)$/, '');
|
|
2240
|
+
|
|
2241
|
+
// version lookup key
|
|
2242
|
+
let pluginKey = baseName;
|
|
2243
|
+
|
|
2244
|
+
|
|
2245
|
+
// Only common plugin has js and less variants
|
|
2246
|
+
if (baseName === "common") {
|
|
2247
|
+
if (ext === ".js") pluginKey = "common-js";
|
|
2248
|
+
if (ext === ".less") pluginKey = "common-less";
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
const latestVersion = versions[pluginKey];
|
|
2252
|
+
|
|
2253
|
+
if (!latestVersion) {
|
|
2254
|
+
console.log(`❌ No version entry for ${baseName}`);
|
|
2255
|
+
return false;
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
// ===============================
|
|
2259
|
+
// FOLDER PLUGIN UPDATE
|
|
2260
|
+
// ===============================
|
|
2261
|
+
if (pluginDef.isFolder) {
|
|
2262
|
+
|
|
2263
|
+
const zipName = `${baseName}-${latestVersion}.zip`;
|
|
2264
|
+
const url = `https://htmlcodeplay.com/code-play-plugin/${zipName}`;
|
|
2265
|
+
|
|
2266
|
+
const destRoot = path.join(srcDir, pluginDef.destDir || pluginDef.baseDir || '');
|
|
2267
|
+
const oldPath = path.join(destRoot, pluginInfo.name);
|
|
2268
|
+
const newPath = path.join(destRoot, `${baseName}-${latestVersion}`);
|
|
2269
|
+
|
|
2270
|
+
if (!(await urlExists(url))) return false;
|
|
2271
|
+
|
|
2272
|
+
fs.rmSync(oldPath, { recursive: true, force: true });
|
|
2273
|
+
|
|
2274
|
+
await downloadAndExtractZip(url, newPath);
|
|
2275
|
+
|
|
2276
|
+
updateImports(pluginInfo.name, `${baseName}-${latestVersion}`);
|
|
2277
|
+
|
|
2278
|
+
// ✅ ADD THIS
|
|
2279
|
+
writeUpdateLine(`${pluginInfo.name} -> ${baseName}-${latestVersion}`);
|
|
2280
|
+
|
|
2281
|
+
console.log(`✅ Folder updated → ${baseName}-${latestVersion}`);
|
|
2282
|
+
|
|
2283
|
+
return true;
|
|
2284
|
+
}
|
|
2285
|
+
// ===============================
|
|
2286
|
+
// FILE PLUGIN UPDATE
|
|
2287
|
+
// ===============================
|
|
2288
|
+
|
|
2289
|
+
const pluginDir = path.dirname(oldFullPath);
|
|
2290
|
+
|
|
2291
|
+
// Only this plugin has ios variant
|
|
2292
|
+
const IOS_VARIANT_PLUGINS = [
|
|
2293
|
+
"saveToGalleryAndSaveAnyFile"
|
|
2294
|
+
];
|
|
2295
|
+
|
|
2296
|
+
/* let variants = [
|
|
2297
|
+
`${baseName}-${latestVersion}.js`
|
|
2298
|
+
]; */
|
|
2299
|
+
|
|
2300
|
+
//const ext = path.extname(oldFileName);
|
|
2301
|
+
const variants = [`${baseName.replace(ext,'')}-${latestVersion}${ext}`];
|
|
2302
|
+
|
|
2303
|
+
|
|
2304
|
+
if (IOS_VARIANT_PLUGINS.includes(baseName)) {
|
|
2305
|
+
variants.push(`${baseName}-${latestVersion}-ios.js`);
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
let downloaded = [];
|
|
2309
|
+
|
|
2310
|
+
// Download files
|
|
2311
|
+
for (const fileName of variants) {
|
|
2312
|
+
|
|
2313
|
+
const url = `https://htmlcodeplay.com/code-play-plugin/${fileName}`;
|
|
2314
|
+
|
|
2315
|
+
console.log(`🔍 Checking latest: ${fileName}`);
|
|
2316
|
+
|
|
2317
|
+
if (await urlExists(url)) {
|
|
2318
|
+
|
|
2319
|
+
const destPath = path.join(pluginDir, fileName);
|
|
2320
|
+
|
|
2321
|
+
await downloadFile(url, destPath);
|
|
2322
|
+
|
|
2323
|
+
downloaded.push(fileName);
|
|
2324
|
+
|
|
2325
|
+
console.log(`⬇ Downloaded → ${fileName}`);
|
|
2326
|
+
|
|
2327
|
+
//writeUpdateLine(`Downloaded: ${fileName}`);
|
|
2328
|
+
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
if (downloaded.length === 0) {
|
|
2333
|
+
console.log(`❌ No files downloaded for ${baseName}`);
|
|
2334
|
+
return false;
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
// Remove ONLY versioned files (safe)
|
|
2338
|
+
//const versionPattern = new RegExp(`^${baseName}-\\d+\\.\\d+(-ios)?\\.js$`);
|
|
2339
|
+
const versionPattern = new RegExp(`^${baseName}-\\d+\\.\\d+(-ios)?\\${ext}$`);
|
|
2340
|
+
|
|
2341
|
+
const existingFiles = fs.readdirSync(pluginDir);
|
|
2342
|
+
|
|
2343
|
+
existingFiles.forEach(file => {
|
|
2344
|
+
|
|
2345
|
+
if (
|
|
2346
|
+
versionPattern.test(file) &&
|
|
2347
|
+
!downloaded.includes(file)
|
|
2348
|
+
) {
|
|
2349
|
+
|
|
2350
|
+
const oldPath = path.join(pluginDir, file);
|
|
2351
|
+
|
|
2352
|
+
fs.unlinkSync(oldPath);
|
|
2353
|
+
|
|
2354
|
+
console.log(`🗑 Removed old file → ${file}`);
|
|
2355
|
+
//writeUpdateLine(`Removed old file: ${file}`);
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
});
|
|
2359
|
+
|
|
2360
|
+
//const newFileName = `${baseName}-${latestVersion}.js`;
|
|
2361
|
+
const newFileName = `${baseName}-${latestVersion}${ext}`;
|
|
2362
|
+
|
|
2363
|
+
writeUpdateLine(`${oldVersionFile} -> ${newFileName}`);
|
|
2364
|
+
|
|
2365
|
+
updateImports(oldFileName, newFileName);
|
|
2366
|
+
//updateImports(baseName, `${baseName}-${latestVersion}.js`);
|
|
2367
|
+
//updateImports(pluginInfo.name, `${baseName}-${latestVersion}`);
|
|
2368
|
+
|
|
2369
|
+
//console.log(`✅ Updated → ${newFileName}`);
|
|
2370
|
+
//console.log(`✅ Updated → ${baseName}-${latestVersion}`);
|
|
2371
|
+
|
|
2372
|
+
return true;
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
|
|
2376
|
+
|
|
2377
|
+
|
|
2378
|
+
/*############################################## AUTO DOWNLOAD FROM SERVER END #####################################*/
|
|
2379
|
+
|
|
2380
|
+
|
|
2381
|
+
|
|
2382
|
+
|
|
2383
|
+
|
|
2384
|
+
|
|
2385
|
+
|
|
2386
|
+
|
|
2387
|
+
|
|
2388
|
+
|
|
2389
|
+
|
|
2390
|
+
|
|
2391
|
+
|
|
2392
|
+
|
|
2393
|
+
|
|
2394
|
+
|
|
2395
|
+
|
|
2396
|
+
|
|
2397
|
+
async function loadPluginVersions() {
|
|
2398
|
+
|
|
2399
|
+
if (!USE_LIVE_SERVER_VERSION) {
|
|
2400
|
+
console.log("ℹ️ Using local plugin versions (offline mode).");
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
console.log("🌐 Fetching plugin versions from server...");
|
|
2405
|
+
|
|
2406
|
+
const versions = await fetchVersions();
|
|
2407
|
+
|
|
2408
|
+
if (!versions || typeof versions !== "object") {
|
|
2409
|
+
console.log("⚠️ Server unavailable or invalid versions.json. Falling back to local versions.");
|
|
2410
|
+
return;
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
requiredPlugins.forEach(plugin => {
|
|
2414
|
+
|
|
2415
|
+
if (!plugin.name) return;
|
|
2416
|
+
|
|
2417
|
+
if (versions[plugin.name]) {
|
|
2418
|
+
plugin.minVersion = versions[plugin.name];
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
});
|
|
2422
|
+
|
|
2423
|
+
console.log("✅ Plugin versions loaded from server.");
|
|
2424
|
+
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
|
|
2428
|
+
|
|
2429
|
+
let hasMandatoryUpdate = false;
|
|
2430
|
+
function checkPlugins() {
|
|
2431
|
+
return new Promise(async (resolve, reject) => {
|
|
2432
|
+
const files = getSourceFiles();
|
|
2433
|
+
const outdatedPlugins = [];
|
|
2434
|
+
let hasMandatoryUpdate = false;
|
|
2435
|
+
|
|
2436
|
+
for (const plugin of requiredPlugins) {
|
|
2437
|
+
const searchRoot = getSearchRoot(plugin);
|
|
2438
|
+
|
|
2439
|
+
// ---------- Folder plugins ----------
|
|
2440
|
+
if (plugin.isFolder) {
|
|
2441
|
+
if (!fs.existsSync(searchRoot)) continue;
|
|
2442
|
+
|
|
2443
|
+
const subDirs = fs.readdirSync(searchRoot)
|
|
2444
|
+
.map(name => path.join(searchRoot, name))
|
|
2445
|
+
.filter(p => fs.statSync(p).isDirectory());
|
|
2446
|
+
|
|
2447
|
+
for (const dir of subDirs) {
|
|
2448
|
+
const relativePath = path.relative(searchRoot, dir).replace(/\\/g, '/');
|
|
2449
|
+
const match = plugin.pattern.exec(relativePath);
|
|
2450
|
+
|
|
2451
|
+
if (match) {
|
|
2452
|
+
const currentVersion = match[1];
|
|
2453
|
+
|
|
2454
|
+
if (compareVersions(currentVersion, plugin.minVersion) < 0) {
|
|
2455
|
+
outdatedPlugins.push({
|
|
2456
|
+
name: relativePath,
|
|
2457
|
+
currentVersion,
|
|
2458
|
+
requiredVersion: plugin.minVersion,
|
|
2459
|
+
mandatoryUpdate: plugin.mandatoryUpdate === true
|
|
2460
|
+
});
|
|
2461
|
+
|
|
2462
|
+
if (plugin.mandatoryUpdate) {
|
|
2463
|
+
hasMandatoryUpdate = true;
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
continue;
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
// ---------- File plugins ----------
|
|
2472
|
+
const matchedFile = files.find(file =>
|
|
2473
|
+
file.startsWith(searchRoot) && plugin.pattern.test(file)
|
|
2474
|
+
);
|
|
2475
|
+
|
|
2476
|
+
if (matchedFile) {
|
|
2477
|
+
const match = plugin.pattern.exec(matchedFile);
|
|
2478
|
+
if (match) {
|
|
2479
|
+
const currentVersion = match[1];
|
|
2480
|
+
const isBeta = !!match[2];
|
|
2481
|
+
|
|
2482
|
+
const cmp = plugin.pattern.source.includes('beta')
|
|
2483
|
+
? compareWithBeta(currentVersion, plugin.minVersion, isBeta)
|
|
2484
|
+
: compareVersions(currentVersion, plugin.minVersion);
|
|
2485
|
+
|
|
2486
|
+
if (cmp < 0) {
|
|
2487
|
+
outdatedPlugins.push({
|
|
2488
|
+
name: path.relative(srcDir, matchedFile),
|
|
2489
|
+
currentVersion: isBeta ? `${currentVersion}-beta` : currentVersion,
|
|
2490
|
+
requiredVersion: plugin.minVersion,
|
|
2491
|
+
mandatoryUpdate: plugin.mandatoryUpdate === true
|
|
2492
|
+
});
|
|
2493
|
+
|
|
2494
|
+
if (plugin.mandatoryUpdate) {
|
|
2495
|
+
hasMandatoryUpdate = true;
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
// ---------- Result handling ----------
|
|
2503
|
+
if (outdatedPlugins.length > 0) {
|
|
2504
|
+
console.log('\n❗ The following plugins are outdated:\n');
|
|
2505
|
+
|
|
2506
|
+
outdatedPlugins.forEach( p => {
|
|
2507
|
+
const tag = p.mandatoryUpdate ? '🔥 MANDATORY' : '';
|
|
2508
|
+
console.log(
|
|
2509
|
+
` ⚠️ - ${p.name} (Current: ${p.currentVersion}, Required: ${p.requiredVersion}) ${tag}`
|
|
2510
|
+
);
|
|
2511
|
+
});
|
|
2512
|
+
|
|
2513
|
+
// 🚨 Mandatory update → stop build
|
|
2514
|
+
/* if (hasMandatoryUpdate) {
|
|
2515
|
+
console.log('\n🚫 One or more plugins require a mandatory update.');
|
|
2516
|
+
console.log('❌ Build cancelled. Please update mandatory plugins and try again.');
|
|
2517
|
+
process.exit(1);
|
|
2518
|
+
} */
|
|
2519
|
+
|
|
2520
|
+
|
|
2521
|
+
|
|
2522
|
+
|
|
2523
|
+
|
|
2524
|
+
if (hasMandatoryUpdate) {
|
|
2525
|
+
|
|
2526
|
+
//--------------------------------------------------
|
|
2527
|
+
// 🚫 AUTO UPDATE DISABLED
|
|
2528
|
+
//--------------------------------------------------
|
|
2529
|
+
if (!ENABLE_AUTO_UPDATE) {
|
|
2530
|
+
console.log("\n🚫 Auto-update disabled.");
|
|
2531
|
+
console.log("❌ Manual update required.");
|
|
2532
|
+
process.exit(1);
|
|
2533
|
+
}
|
|
2534
|
+
|
|
2535
|
+
//--------------------------------------------------
|
|
2536
|
+
// 🔥 AUTO UPDATE ENABLED
|
|
2537
|
+
//--------------------------------------------------
|
|
2538
|
+
console.log("\n🔥 Mandatory plugins outdated. Trying auto-update...\n");
|
|
2539
|
+
|
|
2540
|
+
|
|
2541
|
+
|
|
2542
|
+
let autoFailed = false;
|
|
2543
|
+
|
|
2544
|
+
for (const p of outdatedPlugins.filter(x => x.mandatoryUpdate)) {
|
|
2545
|
+
|
|
2546
|
+
const pluginDef = requiredPlugins.find(def =>
|
|
2547
|
+
def.pattern.test(p.name)
|
|
2548
|
+
);
|
|
2549
|
+
|
|
2550
|
+
if (!pluginDef) continue;
|
|
2551
|
+
|
|
2552
|
+
const success = await autoUpdatePlugin(
|
|
2553
|
+
pluginDef,
|
|
2554
|
+
p
|
|
2555
|
+
);
|
|
2556
|
+
|
|
2557
|
+
if (!success) {
|
|
2558
|
+
autoFailed = true;
|
|
2559
|
+
|
|
2560
|
+
const pluginDef = requiredPlugins.find(def =>
|
|
2561
|
+
def.pattern.test(p.name)
|
|
2562
|
+
);
|
|
2563
|
+
|
|
2564
|
+
console.log(`❌ Manual update required for ${p.name}`);
|
|
2565
|
+
|
|
2566
|
+
if (pluginDef) {
|
|
2567
|
+
console.log(`👉 Required minimum version: ${pluginDef.minVersion}`);
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
// 🚨 Fallback to manual if any failed
|
|
2573
|
+
if (autoFailed) {
|
|
2574
|
+
console.log('\n🚫 One or more plugins require manual update.');
|
|
2575
|
+
console.log('❌ Build cancelled. Please update mandatory plugins.');
|
|
2576
|
+
process.exit(1);
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
console.log('\n🎉 All mandatory plugins auto-updated! Skipping recheck for speed.\n');
|
|
2580
|
+
|
|
2581
|
+
saveUpdateLogs();
|
|
2582
|
+
resolve();
|
|
2583
|
+
return;
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
|
|
2587
|
+
|
|
2588
|
+
|
|
2589
|
+
|
|
2590
|
+
|
|
2591
|
+
// Optional updates → ask user
|
|
2592
|
+
const rl = readline.createInterface({
|
|
2593
|
+
input: process.stdin,
|
|
2594
|
+
output: process.stdout
|
|
2595
|
+
});
|
|
2596
|
+
|
|
2597
|
+
rl.question(
|
|
2598
|
+
'\nAre you sure you want to continue without updating these plugins? (y/n): ',
|
|
2599
|
+
answer => {
|
|
2600
|
+
rl.close();
|
|
2601
|
+
|
|
2602
|
+
if (answer.toLowerCase() !== 'y') {
|
|
2603
|
+
console.log('\n❌ Build cancelled due to outdated plugins.');
|
|
2604
|
+
process.exit(1);
|
|
2605
|
+
} else {
|
|
2606
|
+
console.log('\n✅ Continuing build...');
|
|
2607
|
+
resolve();
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
);
|
|
2611
|
+
} else {
|
|
2612
|
+
console.log('✅ All plugin versions are up to date.');
|
|
2613
|
+
saveUpdateLogs();
|
|
2614
|
+
resolve();
|
|
2615
|
+
}
|
|
2616
|
+
});
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
|
|
2620
|
+
|
|
2621
|
+
|
|
2622
|
+
|
|
2623
|
+
|
|
2624
|
+
|
|
2625
|
+
|
|
2626
|
+
|
|
2627
|
+
|
|
2628
|
+
const localizationBaseDir = path.join(__dirname, "..", "src", "js", "localization");
|
|
2629
|
+
|
|
2630
|
+
// ======================================================
|
|
2631
|
+
// 🌐 LOCALIZATION CHECK (FULLY DYNAMIC)
|
|
2632
|
+
// ======================================================
|
|
2633
|
+
|
|
2634
|
+
|
|
2635
|
+
const jsRootDir = path.join(__dirname, "..", "src", "js");
|
|
2636
|
+
|
|
2637
|
+
// 🔍 Detect OLD localization files in root js/
|
|
2638
|
+
const oldLocalizationFiles = fs.readdirSync(jsRootDir)
|
|
2639
|
+
.filter(name =>
|
|
2640
|
+
/^localization-\d+(\.\d+)*\.js$/.test(name) ||
|
|
2641
|
+
/^localization_settings-\d+(\.\d+)*\.js$/.test(name)
|
|
2642
|
+
);
|
|
2643
|
+
|
|
2644
|
+
// ❌ If old structure found → STOP
|
|
2645
|
+
if (oldLocalizationFiles.length > 0) {
|
|
2646
|
+
|
|
2647
|
+
console.error(`
|
|
2648
|
+
❌ OLD LOCALIZATION STRUCTURE DETECTED
|
|
2649
|
+
|
|
2650
|
+
You are using outdated file structure:
|
|
2651
|
+
src/js/localization-x.x.js
|
|
2652
|
+
src/js/localization_settings-x.x.js
|
|
2653
|
+
|
|
2654
|
+
🚨 This is no longer supported.
|
|
2655
|
+
|
|
2656
|
+
📦 Found files:
|
|
2657
|
+
${oldLocalizationFiles.map(f => " - " + f).join("\n")}
|
|
2658
|
+
|
|
2659
|
+
👉 Please move them to new structure:
|
|
2660
|
+
|
|
2661
|
+
src/js/localization/localization_settings-x.x.js
|
|
2662
|
+
src/js/localization/localization-x.x/
|
|
2663
|
+
|
|
2664
|
+
⚠️ Example:
|
|
2665
|
+
|
|
2666
|
+
OLD:
|
|
2667
|
+
src/js/localization-x.x.js
|
|
2668
|
+
src/js/localization_settings-x.x.js
|
|
2669
|
+
|
|
2670
|
+
NEW:
|
|
2671
|
+
src/js/localization/localization_settings-1.1.js
|
|
2672
|
+
src/js/localization/localization-x.x/index.js
|
|
2673
|
+
|
|
2674
|
+
in vite.config.json
|
|
2675
|
+
'@translate': path.resolve(__dirname, './src/js/localization/localization-x.x'),
|
|
2676
|
+
|
|
2677
|
+
❌ Build stopped.
|
|
2678
|
+
`);
|
|
2679
|
+
|
|
2680
|
+
process.exit(1);
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
|
|
2684
|
+
|
|
2685
|
+
|
|
2686
|
+
if (fs.existsSync(localizationBaseDir)) {
|
|
2687
|
+
|
|
2688
|
+
// ❌ Block wrong files
|
|
2689
|
+
const invalidFiles = fs.readdirSync(localizationBaseDir)
|
|
2690
|
+
.filter(name => /^localization-\d+(\.\d+)*\.js$/.test(name));
|
|
2691
|
+
|
|
2692
|
+
if (invalidFiles.length > 0) {
|
|
2693
|
+
console.error(`
|
|
2694
|
+
❌ INVALID LOCALIZATION FILE LOCATION
|
|
2695
|
+
|
|
2696
|
+
🚫 localization-x.x.js must NOT be directly inside:
|
|
2697
|
+
src/js/localization/
|
|
2698
|
+
|
|
2699
|
+
📦 Found:
|
|
2700
|
+
${invalidFiles.map(f => " - " + f).join("\n")}
|
|
2701
|
+
|
|
2702
|
+
❌ Build stopped.
|
|
2703
|
+
`);
|
|
2704
|
+
process.exit(1);
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
// 🔍 Find localization-x.x folders
|
|
2708
|
+
const localizationVersions = fs.readdirSync(localizationBaseDir)
|
|
2709
|
+
.filter(name => /^localization-\d+(\.\d+)+$/.test(name));
|
|
2710
|
+
|
|
2711
|
+
if (localizationVersions.length === 0) {
|
|
2712
|
+
console.log("ℹ️ No localization-x.x folder found. Skipping...");
|
|
2713
|
+
} else {
|
|
2714
|
+
|
|
2715
|
+
// ✅ Get latest version (same logic as editor)
|
|
2716
|
+
const latestLocalizationDir = localizationVersions.sort((a, b) => {
|
|
2717
|
+
const vA = parseFloat(a.split('-')[1]);
|
|
2718
|
+
const vB = parseFloat(b.split('-')[1]);
|
|
2719
|
+
return vB - vA;
|
|
2720
|
+
})[0];
|
|
2721
|
+
|
|
2722
|
+
const localizationPath = path.join(localizationBaseDir, latestLocalizationDir);
|
|
2723
|
+
|
|
2724
|
+
// ======================================================
|
|
2725
|
+
// ✅ CHECK localization-x.x.js exists
|
|
2726
|
+
// ======================================================
|
|
2727
|
+
|
|
2728
|
+
const localizationFilePath = path.join(localizationPath, "index.js");
|
|
2729
|
+
|
|
2730
|
+
if (!fs.existsSync(localizationFilePath)) {
|
|
2731
|
+
console.error(`
|
|
2732
|
+
❌ localization entry file missing
|
|
2733
|
+
|
|
2734
|
+
Expected:
|
|
2735
|
+
${localizationFilePath}
|
|
2736
|
+
|
|
2737
|
+
👉 Each localization version folder must contain:
|
|
2738
|
+
index.js
|
|
2739
|
+
|
|
2740
|
+
⚠️ Example:
|
|
2741
|
+
|
|
2742
|
+
src/js/localization/localization-x.x/index.js
|
|
2743
|
+
|
|
2744
|
+
❌ Build stopped.
|
|
2745
|
+
`);
|
|
2746
|
+
process.exit(1);
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
// ======================================================
|
|
2750
|
+
// ✅ CHECK run.js exists
|
|
2751
|
+
// ======================================================
|
|
2752
|
+
|
|
2753
|
+
const runJsPath = path.join(localizationPath, "run.js");
|
|
2754
|
+
|
|
2755
|
+
if (!fs.existsSync(runJsPath)) {
|
|
2756
|
+
console.error(`❌ run.js not found in ${latestLocalizationDir}`);
|
|
2757
|
+
process.exit(1);
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
// ======================================================
|
|
2761
|
+
// 🚀 EXECUTE run.js
|
|
2762
|
+
// ======================================================
|
|
2763
|
+
|
|
2764
|
+
console.log(`🌐 Localization detected: ${latestLocalizationDir}`);
|
|
2765
|
+
console.log(`🚀 Executing ${runJsPath}...`);
|
|
2766
|
+
|
|
2767
|
+
execSync(`node "${runJsPath}"`, { stdio: "inherit" });
|
|
2768
|
+
sourceFileInventory = null;
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
} else {
|
|
2772
|
+
console.log("ℹ️ Localization not used in this project. Skipping...");
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
|
|
2776
|
+
|
|
2777
|
+
|
|
2778
|
+
|
|
2779
|
+
|
|
2780
|
+
|
|
2781
|
+
|
|
2782
|
+
|
|
2783
|
+
|
|
2784
|
+
|
|
2785
|
+
|
|
2786
|
+
|
|
2787
|
+
|
|
2788
|
+
|
|
2789
|
+
|
|
2790
|
+
|
|
2791
|
+
|
|
2792
|
+
|
|
2793
|
+
const checkAndupdateDropInViteConfig = () => {
|
|
2794
|
+
|
|
2795
|
+
const possibleFiles = [
|
|
2796
|
+
"vite.config.js",
|
|
2797
|
+
"vite.config.mjs"
|
|
2798
|
+
];
|
|
2799
|
+
|
|
2800
|
+
// Detect existing config file
|
|
2801
|
+
const viteConfigPath = possibleFiles
|
|
2802
|
+
.map(file => path.join(process.cwd(), file))
|
|
2803
|
+
.find(filePath => fs.existsSync(filePath));
|
|
2804
|
+
|
|
2805
|
+
if (!viteConfigPath) {
|
|
2806
|
+
console.warn("⚠️ No vite config found. Skipping.");
|
|
2807
|
+
return;
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
//console.log("📄 Using:", viteConfigPath.split("/").pop());
|
|
2811
|
+
|
|
2812
|
+
let viteContent = fs.readFileSync(viteConfigPath, "utf8");
|
|
2813
|
+
|
|
2814
|
+
// Skip if already exists
|
|
2815
|
+
if (/drop\s*:\s*\[.*['"]console['"].*\]/.test(viteContent)) {
|
|
2816
|
+
console.log("ℹ️ vite.config.(m)js already Updated. Skipping...");
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2820
|
+
console.log("🔧 Adding esbuild.drop ...");
|
|
2821
|
+
|
|
2822
|
+
// If esbuild block exists
|
|
2823
|
+
if (/esbuild\s*:\s*{/.test(viteContent)) {
|
|
2824
|
+
viteContent = viteContent.replace(
|
|
2825
|
+
/esbuild\s*:\s*{([\s\S]*?)(^ {0,8})}/m,
|
|
2826
|
+
(full, inner, indent) => {
|
|
2827
|
+
|
|
2828
|
+
let lines = inner
|
|
2829
|
+
.split("\n")
|
|
2830
|
+
.map(l => l.trim())
|
|
2831
|
+
.filter(Boolean);
|
|
2832
|
+
|
|
2833
|
+
// Fix last comma
|
|
2834
|
+
if (lines.length > 0) {
|
|
2835
|
+
lines[lines.length - 1] =
|
|
2836
|
+
lines[lines.length - 1].replace(/,+$/, "") + ",";
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
// Re-indent
|
|
2840
|
+
lines = lines.map(l => indent + " " + l);
|
|
2841
|
+
|
|
2842
|
+
// Add drop
|
|
2843
|
+
lines.push(`${indent} drop: ['console','debugger'],`);
|
|
2844
|
+
|
|
2845
|
+
return `esbuild: {\n${lines.join("\n")}\n${indent}}`;
|
|
2846
|
+
}
|
|
2847
|
+
);
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
// If esbuild missing
|
|
2851
|
+
else {
|
|
2852
|
+
viteContent = viteContent.replace(
|
|
2853
|
+
/export default defineConfig\s*\(\s*{/,
|
|
2854
|
+
m => `${m}\n esbuild: {\n drop: ['console','debugger'],\n },`
|
|
2855
|
+
);
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
fs.writeFileSync(viteConfigPath, viteContent, "utf8");
|
|
2859
|
+
console.log("✅ vite.config.(m)js Updated successfully.");
|
|
2860
|
+
};
|
|
2861
|
+
|
|
2862
|
+
const syncAdmobAliasInViteConfig = () => {
|
|
2863
|
+
const latest = findAdmobPackage(path.join(process.cwd(), 'src', 'js', 'Ads'));
|
|
2864
|
+
if (latest) syncAdmobImports(process.cwd(), latest);
|
|
2865
|
+
};
|
|
2866
|
+
|
|
2867
|
+
|
|
2868
|
+
function getViteConfigPath() {
|
|
2869
|
+
const possibleFiles = [
|
|
2870
|
+
"vite.config.mjs",
|
|
2871
|
+
"vite.config.js"
|
|
2872
|
+
];
|
|
2873
|
+
|
|
2874
|
+
return possibleFiles
|
|
2875
|
+
.map(file => path.join(process.cwd(), file))
|
|
2876
|
+
.find(filePath => fs.existsSync(filePath));
|
|
2877
|
+
};
|
|
2878
|
+
|
|
2879
|
+
const getLatestIapDir = () => {
|
|
2880
|
+
const adsDir = path.join(process.cwd(), "src", "js", "Ads");
|
|
2881
|
+
|
|
2882
|
+
if (!fs.existsSync(adsDir)) return null;
|
|
2883
|
+
|
|
2884
|
+
const iapDirs = fs.readdirSync(adsDir, { withFileTypes: true })
|
|
2885
|
+
.filter(entry => entry.isDirectory() && /^IAP-\d+(\.\d+)*$/.test(entry.name))
|
|
2886
|
+
.map(entry => entry.name)
|
|
2887
|
+
.sort((a, b) => {
|
|
2888
|
+
const versionA = a.match(/(\d+(?:\.\d+)*)/)[1].split(".").map(Number);
|
|
2889
|
+
const versionB = b.match(/(\d+(?:\.\d+)*)/)[1].split(".").map(Number);
|
|
2890
|
+
|
|
2891
|
+
for (let i = 0; i < Math.max(versionA.length, versionB.length); i++) {
|
|
2892
|
+
const diff = (versionB[i] || 0) - (versionA[i] || 0);
|
|
2893
|
+
if (diff !== 0) return diff;
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
return 0;
|
|
2897
|
+
});
|
|
2898
|
+
|
|
2899
|
+
return iapDirs.length > 0 ? path.join(adsDir, iapDirs[0]) : null;
|
|
2900
|
+
};
|
|
2901
|
+
|
|
2902
|
+
const getAnalyticsConfig = () => {
|
|
2903
|
+
const config = fs.existsSync(configPath)
|
|
2904
|
+
? JSON.parse(fs.readFileSync(configPath, "utf8"))
|
|
2905
|
+
: {};
|
|
2906
|
+
|
|
2907
|
+
const analyticsFlagKey = "ANALYTICS_ENABLED";
|
|
2908
|
+
const androidEnabled = config.android?.[analyticsFlagKey] ?? false;
|
|
2909
|
+
const iosEnabled = config.ios?.[analyticsFlagKey] ?? false;
|
|
2910
|
+
|
|
2911
|
+
return {
|
|
2912
|
+
androidEnabled: androidEnabled === true,
|
|
2913
|
+
iosEnabled: iosEnabled === true,
|
|
2914
|
+
enabled: androidEnabled === true || iosEnabled === true
|
|
2915
|
+
};
|
|
2916
|
+
};
|
|
2917
|
+
|
|
2918
|
+
function isPackageAvailable(packageName) {
|
|
2919
|
+
if (!fs.existsSync(packageJsonPath)) return false;
|
|
2920
|
+
|
|
2921
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
2922
|
+
|
|
2923
|
+
return Boolean(
|
|
2924
|
+
packageJson.dependencies?.[packageName] ||
|
|
2925
|
+
packageJson.devDependencies?.[packageName] ||
|
|
2926
|
+
packageJson.optionalDependencies?.[packageName] ||
|
|
2927
|
+
packageJson.peerDependencies?.[packageName] ||
|
|
2928
|
+
fs.existsSync(path.join(process.cwd(), "node_modules", packageName, "package.json"))
|
|
2929
|
+
);
|
|
2930
|
+
};
|
|
2931
|
+
|
|
2932
|
+
const sourceImportsPackage = (packageName, ignoredFilePath) => {
|
|
2933
|
+
const escapedPackage = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2934
|
+
const importRegex = new RegExp(`(?:from\\s*['"]${escapedPackage}(?:/[^'"]*)?['"]|import\\s*['"]${escapedPackage}(?:/[^'"]*)?['"]|import\\s*\\(\\s*['"]${escapedPackage}(?:/[^'"]*)?['"]\\s*\\)|require\\s*\\(\\s*['"]${escapedPackage}(?:/[^'"]*)?['"]\\s*\\))`);
|
|
2935
|
+
|
|
2936
|
+
return getSourceFiles().some(fullPath => {
|
|
2937
|
+
if (ignoredFilePath && path.resolve(fullPath) === path.resolve(ignoredFilePath)) return false;
|
|
2938
|
+
if (!/\.(js|mjs|cjs|ts|tsx|jsx|f7)$/.test(fullPath)) return false;
|
|
2939
|
+
return importRegex.test(fs.readFileSync(fullPath, 'utf8'));
|
|
2940
|
+
});
|
|
2941
|
+
};
|
|
2942
|
+
|
|
2943
|
+
function runNpmCommand(command) {
|
|
2944
|
+
console.log(`🚀 ${command}`);
|
|
2945
|
+
execSync(command, { stdio: "inherit" });
|
|
2946
|
+
};
|
|
2947
|
+
|
|
2948
|
+
function removeOneSignalPlugin() {
|
|
2949
|
+
if (!isPackageAvailable(ONESIGNAL_PLUGIN_NAME)) {
|
|
2950
|
+
console.log('ℹ️ OneSignal is not installed. Skipping removal.');
|
|
2951
|
+
return;
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
console.warn(`⚠️ WARNING: ${ONESIGNAL_PLUGIN_NAME} was found. OneSignal is retired and will be uninstalled automatically.`);
|
|
2955
|
+
runNpmCommand(`npm uninstall ${ONESIGNAL_PLUGIN_NAME}`);
|
|
2956
|
+
if (isPackageAvailable(ONESIGNAL_PLUGIN_NAME)) {
|
|
2957
|
+
throw new Error(`❌ ${ONESIGNAL_PLUGIN_NAME} is still declared or installed after npm uninstall. Build stopped; check dependent packages.`);
|
|
2958
|
+
}
|
|
2959
|
+
sourceFileInventory = null;
|
|
2960
|
+
console.log(`✅ Uninstalled ${ONESIGNAL_PLUGIN_NAME}.`);
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
const CODEPLAY_SYSTEM_BARS_PLUGIN = "capacitor-codeplay-system-bars";
|
|
2964
|
+
|
|
2965
|
+
function ensureMandatoryCapacitorPlugins() {
|
|
2966
|
+
const requiredPackages = [
|
|
2967
|
+
"@capacitor/device",
|
|
2968
|
+
"@capacitor-community/in-app-review",
|
|
2969
|
+
"@capawesome/capacitor-app-update"
|
|
2970
|
+
];
|
|
2971
|
+
|
|
2972
|
+
for (const packageName of requiredPackages) {
|
|
2973
|
+
const installedPackagePath = path.join(process.cwd(), "node_modules", packageName, "package.json");
|
|
2974
|
+
if (isPackageAvailable(packageName) && fs.existsSync(installedPackagePath)) {
|
|
2975
|
+
console.log(`ℹ️ ${packageName} is already installed.`);
|
|
2976
|
+
continue;
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
runNpmCommand(`npm install ${packageName}`);
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
|
|
2983
|
+
const syncCodeplaySystemBarsPackage = () => {
|
|
2984
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
2985
|
+
console.warn(`⚠️ package.json not found. Skipping ${CODEPLAY_SYSTEM_BARS_PLUGIN} package sync.`);
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
2990
|
+
const declaredVersion = packageJson.dependencies?.[CODEPLAY_SYSTEM_BARS_PLUGIN];
|
|
2991
|
+
const usesLocalReference = typeof declaredVersion === "string" && declaredVersion.startsWith("file:");
|
|
2992
|
+
|
|
2993
|
+
if (usesLocalReference) {
|
|
2994
|
+
console.log(`ℹ️ Replacing the local ${CODEPLAY_SYSTEM_BARS_PLUGIN} reference with the npm package.`);
|
|
2995
|
+
runNpmCommand(`npm install ${CODEPLAY_SYSTEM_BARS_PLUGIN}`);
|
|
2996
|
+
return;
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
if (!isPackageAvailable(CODEPLAY_SYSTEM_BARS_PLUGIN)) {
|
|
3000
|
+
runNpmCommand(`npm install ${CODEPLAY_SYSTEM_BARS_PLUGIN}`);
|
|
3001
|
+
return;
|
|
3002
|
+
}
|
|
3003
|
+
|
|
3004
|
+
console.log(`ℹ️ ${CODEPLAY_SYSTEM_BARS_PLUGIN} is already installed.`);
|
|
3005
|
+
};
|
|
3006
|
+
|
|
3007
|
+
const ADMOB_NEXTGEN_DEFAULTS = {
|
|
3008
|
+
VERSION: "1.6",
|
|
3009
|
+
ios: { nativeid: "" },
|
|
3010
|
+
android: { nativeid: "" },
|
|
3011
|
+
config: {
|
|
3012
|
+
bannerStartupDelayMs: "",
|
|
3013
|
+
interstitialStartupDelayMs: "",
|
|
3014
|
+
appOpenLoadOnColdStart: "",
|
|
3015
|
+
appOpenStartupDelayMs: "",
|
|
3016
|
+
useAndroidPreloadAds: true,
|
|
3017
|
+
useAndroidBannerPreloadAds: false,
|
|
3018
|
+
preloadBufferSize: 1,
|
|
3019
|
+
consentDebug: false,
|
|
3020
|
+
isKidsApp: false,
|
|
3021
|
+
isTesting: false,
|
|
3022
|
+
consentDebug: false,
|
|
3023
|
+
isConsoleLogEnabled: false,
|
|
3024
|
+
bannerEnabled: true,
|
|
3025
|
+
bannerDisabledPages: [],
|
|
3026
|
+
interstitialEnabled: true,
|
|
3027
|
+
appOpenEnabled: true,
|
|
3028
|
+
rewardVideoEnabled: false,
|
|
3029
|
+
rewardInterstitialEnabled: false,
|
|
3030
|
+
collapsibleEnabled: "",
|
|
3031
|
+
isLandScape: "",
|
|
3032
|
+
isOverlappingEnable: "",
|
|
3033
|
+
overlappingHeight: "",
|
|
3034
|
+
bannerTypeAndroid: "",
|
|
3035
|
+
bannerTypeAndroidLandscape: "",
|
|
3036
|
+
bannerTypeiOS: "",
|
|
3037
|
+
bannerTypeiOSLandscape: "",
|
|
3038
|
+
bannerTopSpaceColor: "",
|
|
3039
|
+
interstitialLoadScreenTextColor: "",
|
|
3040
|
+
interstitialLoadScreenBackgroundColor: "",
|
|
3041
|
+
beforeBannerSpace: "",
|
|
3042
|
+
whenShow: "",
|
|
3043
|
+
minimumClick: "",
|
|
3044
|
+
interstitialTimeOut: "",
|
|
3045
|
+
interstitialFirstTimeOut: "",
|
|
3046
|
+
appOpenAdsTimeOut: "",
|
|
3047
|
+
maxRetryCount: "",
|
|
3048
|
+
retrySecondsAr: "",
|
|
3049
|
+
appOpenPerSession: "",
|
|
3050
|
+
interstitialPerSession: "",
|
|
3051
|
+
appOpenFirstTimeOut: ""
|
|
3052
|
+
},
|
|
3053
|
+
testid: {
|
|
3054
|
+
nativeid: "",
|
|
3055
|
+
nativeid_android: "ca-app-pub-3940256099942544/2247696110",
|
|
3056
|
+
nativeid_ios: "ca-app-pub-3940256099942544/3986624511"
|
|
3057
|
+
}
|
|
3058
|
+
};
|
|
3059
|
+
|
|
3060
|
+
function getAdmobJsonPath() {
|
|
3061
|
+
const adsDir = path.join(process.cwd(), "src", "js", "Ads");
|
|
3062
|
+
const possiblePaths = [
|
|
3063
|
+
path.join(adsDir, ADMOB_NEXTGEN_CONFIG),
|
|
3064
|
+
path.join(adsDir, ADMOB_OLD_CONFIG)
|
|
3065
|
+
];
|
|
3066
|
+
|
|
3067
|
+
return possiblePaths.find(fileExists);
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
function readJsonSafe(filePath) {
|
|
3071
|
+
try {
|
|
3072
|
+
return filePath && fileExists(filePath)
|
|
3073
|
+
? JSON.parse(fs.readFileSync(filePath, "utf8"))
|
|
3074
|
+
: null;
|
|
3075
|
+
} catch {
|
|
3076
|
+
return null;
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
function isKidsAdmobApp() {
|
|
3081
|
+
return readJsonSafe(getAdmobJsonPath())?.config?.isKidsApp === true;
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
function pickFirstValue(...values) {
|
|
3085
|
+
return values.find(value => typeof value === "string" && value.trim() !== "") || "";
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
function getAdmobJsonAppIds(config = readJsonSafe(getAdmobJsonPath())) {
|
|
3089
|
+
return {
|
|
3090
|
+
androidAppId: pickFirstValue(
|
|
3091
|
+
config?.androidAppId,
|
|
3092
|
+
config?.android_app_id,
|
|
3093
|
+
config?.android?.appId,
|
|
3094
|
+
config?.android?.appid,
|
|
3095
|
+
config?.android?.applicationId
|
|
3096
|
+
),
|
|
3097
|
+
iosAppId: pickFirstValue(
|
|
3098
|
+
config?.iosAppId,
|
|
3099
|
+
config?.ios_app_id,
|
|
3100
|
+
config?.ios?.appId,
|
|
3101
|
+
config?.ios?.appid,
|
|
3102
|
+
config?.ios?.applicationId
|
|
3103
|
+
)
|
|
3104
|
+
};
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3107
|
+
const mergeObjects = (template, source) => {
|
|
3108
|
+
if (!template || typeof template !== "object" || Array.isArray(template)) {
|
|
3109
|
+
return source !== undefined ? source : template;
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
const output = { ...template };
|
|
3113
|
+
|
|
3114
|
+
if (!source || typeof source !== "object" || Array.isArray(source)) {
|
|
3115
|
+
return output;
|
|
3116
|
+
}
|
|
3117
|
+
|
|
3118
|
+
Object.keys(source).forEach(key => {
|
|
3119
|
+
if (
|
|
3120
|
+
template[key] &&
|
|
3121
|
+
typeof template[key] === "object" &&
|
|
3122
|
+
!Array.isArray(template[key]) &&
|
|
3123
|
+
source[key] &&
|
|
3124
|
+
typeof source[key] === "object" &&
|
|
3125
|
+
!Array.isArray(source[key])
|
|
3126
|
+
) {
|
|
3127
|
+
output[key] = mergeObjects(template[key], source[key]);
|
|
3128
|
+
} else if (key !== "VERSION") {
|
|
3129
|
+
output[key] = source[key];
|
|
3130
|
+
}
|
|
3131
|
+
});
|
|
3132
|
+
|
|
3133
|
+
return output;
|
|
3134
|
+
};
|
|
3135
|
+
|
|
3136
|
+
const hasObjectKeys = (value) => {
|
|
3137
|
+
return value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length > 0;
|
|
3138
|
+
};
|
|
3139
|
+
|
|
3140
|
+
const ensureAdmobNextgenConfigDefaults = (config) => {
|
|
3141
|
+
const normalizedConfig = { ...config };
|
|
3142
|
+
const runtimeConfig = hasObjectKeys(config?.config) ? config.config : {};
|
|
3143
|
+
|
|
3144
|
+
normalizedConfig.config = mergeObjects(ADMOB_NEXTGEN_DEFAULTS.config, runtimeConfig);
|
|
3145
|
+
|
|
3146
|
+
return normalizedConfig;
|
|
3147
|
+
};
|
|
3148
|
+
|
|
3149
|
+
const mergeNextgenConfigWithoutReplacingExisting = (defaultConfig, oldConfig, existingNextgenConfig) => {
|
|
3150
|
+
const defaultWithOldConfig = mergeObjects(defaultConfig, oldConfig);
|
|
3151
|
+
return mergeObjects(defaultWithOldConfig, existingNextgenConfig);
|
|
3152
|
+
};
|
|
3153
|
+
|
|
3154
|
+
const buildNextgenAdmobConfigFromOldConfig = (templateConfig, existingNextgenConfig, oldConfig) => {
|
|
3155
|
+
const mergedConfig = mergeNextgenConfigWithoutReplacingExisting(templateConfig, oldConfig, existingNextgenConfig);
|
|
3156
|
+
|
|
3157
|
+
const templateVersion = templateConfig.VERSION || ADMOB_NEXTGEN_DEFAULTS.VERSION;
|
|
3158
|
+
mergedConfig.VERSION = compareVersionStrings(existingNextgenConfig?.VERSION || '0', templateVersion) > 0
|
|
3159
|
+
? existingNextgenConfig.VERSION : templateVersion;
|
|
3160
|
+
|
|
3161
|
+
return ensureAdmobNextgenConfigDefaults(mergedConfig);
|
|
3162
|
+
};
|
|
3163
|
+
|
|
3164
|
+
const syncAdmobNextgenPackage = () => {
|
|
3165
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
3166
|
+
console.warn("⚠️ package.json not found. Skipping AdMob nextgen package sync.");
|
|
3167
|
+
return;
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
if (isPackageAvailable("emi-indo-cordova-plugin-admob")) {
|
|
3171
|
+
runNpmCommand("npm uninstall emi-indo-cordova-plugin-admob");
|
|
3172
|
+
} else {
|
|
3173
|
+
console.log("ℹ️ emi-indo-cordova-plugin-admob is not installed.");
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3176
|
+
if (!isPackageAvailable("capacitor-admob-nextgen")) {
|
|
3177
|
+
runNpmCommand("npm install capacitor-admob-nextgen");
|
|
3178
|
+
} else {
|
|
3179
|
+
console.log("ℹ️ capacitor-admob-nextgen is already installed.");
|
|
3180
|
+
}
|
|
3181
|
+
};
|
|
3182
|
+
|
|
3183
|
+
const downloadOptionalCodeplayFile = async (fileName, destPath) => {
|
|
3184
|
+
const url = `https://htmlcodeplay.com/code-play-plugin/${fileName}`;
|
|
3185
|
+
|
|
3186
|
+
if (!(await urlExists(url))) {
|
|
3187
|
+
return false;
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
await downloadFile(url, destPath);
|
|
3191
|
+
console.log(`⬇ Downloaded → ${fileName}`);
|
|
3192
|
+
return true;
|
|
3193
|
+
};
|
|
3194
|
+
|
|
3195
|
+
const syncAdmobConfigImports = () => {
|
|
3196
|
+
const adsDir = path.join(process.cwd(), "src", "js", "Ads");
|
|
3197
|
+
if (!fs.existsSync(adsDir)) return;
|
|
3198
|
+
|
|
3199
|
+
// Downloads invalidate the inventory; content-only migrations reuse it.
|
|
3200
|
+
for (const fullPath of getSourceFiles()) {
|
|
3201
|
+
if (!fullPath.startsWith(adsDir + path.sep)) continue;
|
|
3202
|
+
if (!/\.(js|mjs|ts|f7)$/.test(fullPath) || fullPath.endsWith('.min.js')) continue;
|
|
3203
|
+
let content = fs.readFileSync(fullPath, 'utf8');
|
|
3204
|
+
if (!content.includes(ADMOB_OLD_CONFIG)) continue;
|
|
3205
|
+
content = content.split(ADMOB_OLD_CONFIG).join(ADMOB_NEXTGEN_CONFIG);
|
|
3206
|
+
fs.writeFileSync(fullPath, content, 'utf8');
|
|
3207
|
+
console.log(`✏️ Updated AdMob config import in ${path.relative(process.cwd(), fullPath)}`);
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
};
|
|
3211
|
+
|
|
3212
|
+
const ensureAdmobNextgenFiles = async () => {
|
|
3213
|
+
const adsDir = path.join(process.cwd(), "src", "js", "Ads");
|
|
3214
|
+
if (!fs.existsSync(adsDir)) {
|
|
3215
|
+
console.log("ℹ️ Ads folder not found. Skipping AdMob nextgen file sync.");
|
|
3216
|
+
return;
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
await syncAdmobNextgen({ projectRoot: process.cwd(), versions: await fetchVersions() });
|
|
3220
|
+
sourceFileInventory = null;
|
|
3221
|
+
const nextgenConfigPath = path.join(adsDir, ADMOB_NEXTGEN_CONFIG);
|
|
3222
|
+
const oldConfigPath = path.join(adsDir, ADMOB_OLD_CONFIG);
|
|
3223
|
+
const templateDir = path.join(process.cwd(), 'agent-temp', 'admob-config-sync');
|
|
3224
|
+
fs.mkdirSync(templateDir, { recursive: true });
|
|
3225
|
+
const tempTemplatePath = path.join(templateDir, ADMOB_NEXTGEN_CONFIG);
|
|
3226
|
+
|
|
3227
|
+
let templateConfig = ADMOB_NEXTGEN_DEFAULTS;
|
|
3228
|
+
|
|
3229
|
+
if (await downloadOptionalCodeplayFile(ADMOB_NEXTGEN_CONFIG, tempTemplatePath)) {
|
|
3230
|
+
const downloadedTemplateConfig = JSON.parse(fs.readFileSync(tempTemplatePath, "utf8"));
|
|
3231
|
+
templateConfig = mergeObjects(ADMOB_NEXTGEN_DEFAULTS, downloadedTemplateConfig);
|
|
3232
|
+
templateConfig.VERSION = downloadedTemplateConfig.VERSION || ADMOB_NEXTGEN_DEFAULTS.VERSION;
|
|
3233
|
+
fs.unlinkSync(tempTemplatePath);
|
|
3234
|
+
} else if (fs.existsSync(nextgenConfigPath)) {
|
|
3235
|
+
templateConfig = mergeObjects(ADMOB_NEXTGEN_DEFAULTS, JSON.parse(fs.readFileSync(nextgenConfigPath, "utf8")));
|
|
3236
|
+
}
|
|
3237
|
+
|
|
3238
|
+
const oldConfig = fs.existsSync(oldConfigPath)
|
|
3239
|
+
? JSON.parse(fs.readFileSync(oldConfigPath, "utf8"))
|
|
3240
|
+
: {};
|
|
3241
|
+
const existingNextgenConfig = fs.existsSync(nextgenConfigPath)
|
|
3242
|
+
? JSON.parse(fs.readFileSync(nextgenConfigPath, "utf8"))
|
|
3243
|
+
: {};
|
|
3244
|
+
|
|
3245
|
+
const mergedConfig = buildNextgenAdmobConfigFromOldConfig(templateConfig, existingNextgenConfig, oldConfig);
|
|
3246
|
+
|
|
3247
|
+
const capacitorConfig = readJsonSafe(configPath) || {};
|
|
3248
|
+
const legacyAdmobConfig = capacitorConfig.plugins?.AdMob || {};
|
|
3249
|
+
const mergedAppIds = getAdmobJsonAppIds(mergedConfig);
|
|
3250
|
+
const androidAppId = mergedAppIds.androidAppId || legacyAdmobConfig.APP_ID_ANDROID || "";
|
|
3251
|
+
const iosAppId = mergedAppIds.iosAppId || legacyAdmobConfig.APP_ID_IOS || "";
|
|
3252
|
+
|
|
3253
|
+
if (androidAppId && !mergedAppIds.androidAppId) {
|
|
3254
|
+
mergedConfig.androidAppId = androidAppId;
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
if (iosAppId && !mergedAppIds.iosAppId) {
|
|
3258
|
+
mergedConfig.iosAppId = iosAppId;
|
|
3259
|
+
}
|
|
3260
|
+
|
|
3261
|
+
if (!fs.existsSync(nextgenConfigPath)) sourceFileInventory = null;
|
|
3262
|
+
fs.writeFileSync(nextgenConfigPath, JSON.stringify(mergedConfig, null, 2) + "\n", "utf8");
|
|
3263
|
+
syncAdmobConfigImports();
|
|
3264
|
+
console.log("✅ AdMob nextgen files and configuration are ready.");
|
|
3265
|
+
};
|
|
3266
|
+
|
|
3267
|
+
const syncAdmobNextgenMigration = async () => {
|
|
3268
|
+
syncAdmobNextgenPackage();
|
|
3269
|
+
await ensureAdmobNextgenFiles();
|
|
3270
|
+
syncAdmobAliasInViteConfig();
|
|
3271
|
+
};
|
|
3272
|
+
|
|
3273
|
+
const syncSystemBarsSafeAreaMigration = () => {
|
|
3274
|
+
if (isPackageAvailable("@capacitor-community/safe-area")) {
|
|
3275
|
+
runNpmCommand("npm uninstall @capacitor-community/safe-area");
|
|
3276
|
+
} else {
|
|
3277
|
+
console.log("ℹ️ @capacitor-community/safe-area is not installed.");
|
|
3278
|
+
}
|
|
3279
|
+
|
|
3280
|
+
if (!fs.existsSync(configPath)) {
|
|
3281
|
+
console.warn("⚠️ capacitor.config.json not found. Skipping SystemBars config sync.");
|
|
3282
|
+
return;
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
3286
|
+
if (!config.plugins) config.plugins = {};
|
|
3287
|
+
if (!config.plugins.SystemBars) config.plugins.SystemBars = {};
|
|
3288
|
+
|
|
3289
|
+
if (config.plugins.SystemBars.insetsHandling !== "css") {
|
|
3290
|
+
config.plugins.SystemBars.insetsHandling = "css";
|
|
3291
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
3292
|
+
console.log('✅ capacitor.config.json SystemBars.insetsHandling set to "css".');
|
|
3293
|
+
} else {
|
|
3294
|
+
console.log('ℹ️ capacitor.config.json SystemBars.insetsHandling already "css".');
|
|
3295
|
+
}
|
|
3296
|
+
};
|
|
3297
|
+
|
|
3298
|
+
const ensureFirebaseAnalyticsWrapper = (analyticsEnabled) => {
|
|
3299
|
+
const analyticsDir = getLatestIapDir();
|
|
3300
|
+
const legacyAnalyticsDir = path.join(process.cwd(), "src", "js", "analytics");
|
|
3301
|
+
const legacyAnalyticsFile = path.join(legacyAnalyticsDir, "firebase-analytics.js");
|
|
3302
|
+
|
|
3303
|
+
if (!analyticsDir) {
|
|
3304
|
+
console.warn("⚠️ No IAP-x.x folder found. Skipping Firebase analytics wrapper sync.");
|
|
3305
|
+
return null;
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
const analyticsFile = path.join(analyticsDir, "firebase-analytics.js");
|
|
3309
|
+
|
|
3310
|
+
if (!fs.existsSync(analyticsDir)) {
|
|
3311
|
+
fs.mkdirSync(analyticsDir, { recursive: true });
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
const activeWrapper = `import { Capacitor, registerPlugin } from '@capacitor/core';
|
|
3315
|
+
|
|
3316
|
+
const FirebaseAnalytics = registerPlugin('FirebaseAnalytics');
|
|
3317
|
+
|
|
3318
|
+
const isEnabledValue = (value) => value === true || value === 'true';
|
|
3319
|
+
|
|
3320
|
+
const canUseNativeAnalytics = () => {
|
|
3321
|
+
if (!Capacitor.isNativePlatform()) return false;
|
|
3322
|
+
|
|
3323
|
+
const platform = Capacitor.getPlatform();
|
|
3324
|
+
|
|
3325
|
+
if (platform === 'android') {
|
|
3326
|
+
return isEnabledValue(import.meta.env.VITE_ANDROID_ANALYTICS_ENABLED);
|
|
3327
|
+
}
|
|
3328
|
+
|
|
3329
|
+
if (platform === 'ios') {
|
|
3330
|
+
return isEnabledValue(import.meta.env.VITE_IOS_ANALYTICS_ENABLED);
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
return false;
|
|
3334
|
+
};
|
|
3335
|
+
|
|
3336
|
+
const cleanParams = (params = {}) => {
|
|
3337
|
+
return Object.entries(params).reduce((result, [key, value]) => {
|
|
3338
|
+
if (value === undefined || value === null || value === '') return result;
|
|
3339
|
+
if (typeof value === 'number' && Number.isNaN(value)) return result;
|
|
3340
|
+
|
|
3341
|
+
result[key] = value;
|
|
3342
|
+
return result;
|
|
3343
|
+
}, {});
|
|
3344
|
+
};
|
|
3345
|
+
|
|
3346
|
+
const trackEvent = async (name, params = {}) => {
|
|
3347
|
+
if (!canUseNativeAnalytics()) return;
|
|
3348
|
+
|
|
3349
|
+
try {
|
|
3350
|
+
await FirebaseAnalytics.logEvent({
|
|
3351
|
+
name,
|
|
3352
|
+
params: cleanParams(params),
|
|
3353
|
+
});
|
|
3354
|
+
} catch (err) {
|
|
3355
|
+
console.warn(\`Firebase analytics event failed: \${name}\`, err);
|
|
3356
|
+
}
|
|
3357
|
+
};
|
|
3358
|
+
|
|
3359
|
+
const normalizeCurrency = (currency) => {
|
|
3360
|
+
if (typeof currency !== 'string') return '';
|
|
3361
|
+
return currency.trim().toUpperCase();
|
|
3362
|
+
};
|
|
3363
|
+
|
|
3364
|
+
const buildPurchaseParams = (params = {}) => {
|
|
3365
|
+
const value = Number(params.value || 0);
|
|
3366
|
+
const itemId = params.item_id || params.itemId || '';
|
|
3367
|
+
const itemName = params.item_name || params.itemName || itemId || params.store || 'purchase';
|
|
3368
|
+
const quantity = Number(params.quantity || 1);
|
|
3369
|
+
const transactionId = params.transaction_id || params.transactionId || \`\${params.store || 'store'}_\${itemId || itemName || 'purchase'}\`;
|
|
3370
|
+
|
|
3371
|
+
return cleanParams({
|
|
3372
|
+
transaction_id: transactionId,
|
|
3373
|
+
currency: normalizeCurrency(params.currency),
|
|
3374
|
+
value,
|
|
3375
|
+
items: [
|
|
3376
|
+
cleanParams({
|
|
3377
|
+
item_id: itemId,
|
|
3378
|
+
item_name: itemName,
|
|
3379
|
+
item_category: params.store || '',
|
|
3380
|
+
price: value,
|
|
3381
|
+
quantity,
|
|
3382
|
+
subscription_period: params.subscription_period || '',
|
|
3383
|
+
}),
|
|
3384
|
+
],
|
|
3385
|
+
});
|
|
3386
|
+
};
|
|
3387
|
+
|
|
3388
|
+
const buildAdRevenueParams = (params = {}) => {
|
|
3389
|
+
const value = Number(params.value || 0);
|
|
3390
|
+
|
|
3391
|
+
return cleanParams({
|
|
3392
|
+
ad_platform: params.ad_platform || 'AdMob',
|
|
3393
|
+
ad_source: params.ad_source || 'AdMob',
|
|
3394
|
+
ad_unit_name: params.ad_unit_name || params.adUnitId || '',
|
|
3395
|
+
ad_format: params.ad_format || '',
|
|
3396
|
+
currency: normalizeCurrency(params.currency),
|
|
3397
|
+
value,
|
|
3398
|
+
});
|
|
3399
|
+
};
|
|
3400
|
+
|
|
3401
|
+
const trackMonetizationEvent = async (customEventName, params = {}) => {
|
|
3402
|
+
await trackEvent(customEventName, params);
|
|
3403
|
+
await trackEvent('purchase', buildPurchaseParams(params));
|
|
3404
|
+
};
|
|
3405
|
+
|
|
3406
|
+
export const trackAdRevenue = (params) => {
|
|
3407
|
+
trackEvent('ad_impression', buildAdRevenueParams(params));
|
|
3408
|
+
};
|
|
3409
|
+
|
|
3410
|
+
export const trackSubscriptionScreenViewed = (params) => {
|
|
3411
|
+
trackEvent('subscription_screen_view', params);
|
|
3412
|
+
};
|
|
3413
|
+
|
|
3414
|
+
export const trackSubscriptionCheckoutStarted = (params) => {
|
|
3415
|
+
trackEvent('subscription_checkout_start', params);
|
|
3416
|
+
};
|
|
3417
|
+
|
|
3418
|
+
export const trackSubscriptionPurchased = (params) => {
|
|
3419
|
+
trackMonetizationEvent('subscription_purchase', params);
|
|
3420
|
+
};
|
|
3421
|
+
|
|
3422
|
+
export const trackConsumablePurchased = (params) => {
|
|
3423
|
+
trackMonetizationEvent('consumable_purchase', params);
|
|
3424
|
+
};
|
|
3425
|
+
`;
|
|
3426
|
+
|
|
3427
|
+
const noopWrapper = `const trackEvent = () => {};
|
|
3428
|
+
|
|
3429
|
+
export const trackAdRevenue = trackEvent;
|
|
3430
|
+
export const trackSubscriptionScreenViewed = trackEvent;
|
|
3431
|
+
export const trackSubscriptionCheckoutStarted = trackEvent;
|
|
3432
|
+
export const trackSubscriptionPurchased = trackEvent;
|
|
3433
|
+
export const trackConsumablePurchased = trackEvent;
|
|
3434
|
+
`;
|
|
3435
|
+
|
|
3436
|
+
const nextContent = analyticsEnabled ? activeWrapper : noopWrapper;
|
|
3437
|
+
const analyticsFileExists = fs.existsSync(analyticsFile);
|
|
3438
|
+
const currentContent = analyticsFileExists ? fs.readFileSync(analyticsFile, "utf8") : "";
|
|
3439
|
+
if (!analyticsFileExists) sourceFileInventory = null;
|
|
3440
|
+
|
|
3441
|
+
if (currentContent !== nextContent) {
|
|
3442
|
+
fs.writeFileSync(analyticsFile, nextContent, "utf8");
|
|
3443
|
+
console.log(`✅ Firebase analytics wrapper ${analyticsEnabled ? "enabled" : "disabled"}: ${path.relative(process.cwd(), analyticsFile)}`);
|
|
3444
|
+
} else {
|
|
3445
|
+
console.log(`ℹ️ Firebase analytics wrapper already ${analyticsEnabled ? "enabled" : "disabled"}.`);
|
|
3446
|
+
}
|
|
3447
|
+
|
|
3448
|
+
if (fs.existsSync(legacyAnalyticsFile)) {
|
|
3449
|
+
fs.unlinkSync(legacyAnalyticsFile);
|
|
3450
|
+
sourceFileInventory = null;
|
|
3451
|
+
console.log("✅ Removed old Firebase analytics wrapper: src/js/analytics/firebase-analytics.js");
|
|
3452
|
+
}
|
|
3453
|
+
|
|
3454
|
+
if (fs.existsSync(legacyAnalyticsDir) && fs.readdirSync(legacyAnalyticsDir).length === 0) {
|
|
3455
|
+
fs.rmdirSync(legacyAnalyticsDir);
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3458
|
+
return analyticsFile;
|
|
3459
|
+
};
|
|
3460
|
+
|
|
3461
|
+
const syncFirebaseAnalyticsPackages = () => {
|
|
3462
|
+
const analyticsConfig = getAnalyticsConfig();
|
|
3463
|
+
|
|
3464
|
+
if (analyticsConfig.enabled) {
|
|
3465
|
+
if (!isPackageAvailable("@capacitor-firebase/analytics") || !isPackageAvailable("firebase")) {
|
|
3466
|
+
runNpmCommand("npm install @capacitor-firebase/analytics firebase");
|
|
3467
|
+
} else {
|
|
3468
|
+
console.log("ℹ️ Firebase Analytics packages already installed.");
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
ensureFirebaseAnalyticsWrapper(true);
|
|
3472
|
+
return;
|
|
3473
|
+
}
|
|
3474
|
+
|
|
3475
|
+
const analyticsFile = ensureFirebaseAnalyticsWrapper(false);
|
|
3476
|
+
|
|
3477
|
+
if (isPackageAvailable("@capacitor-firebase/analytics")) {
|
|
3478
|
+
runNpmCommand("npm uninstall @capacitor-firebase/analytics");
|
|
3479
|
+
} else {
|
|
3480
|
+
console.log("ℹ️ @capacitor-firebase/analytics is not installed.");
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
if (isPackageAvailable("firebase")) {
|
|
3484
|
+
if (sourceImportsPackage("firebase", analyticsFile)) {
|
|
3485
|
+
console.log("ℹ️ Keeping firebase package because other source files import it.");
|
|
3486
|
+
} else {
|
|
3487
|
+
runNpmCommand("npm uninstall firebase");
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
};
|
|
3491
|
+
|
|
3492
|
+
const syncFirebaseAnalyticsPodfile = () => {
|
|
3493
|
+
const podfilePath = path.join(process.cwd(), "ios", "App", "Podfile");
|
|
3494
|
+
if (!fs.existsSync(podfilePath)) return;
|
|
3495
|
+
|
|
3496
|
+
const { iosEnabled } = getAnalyticsConfig();
|
|
3497
|
+
const basePodLine = " pod 'CapacitorFirebaseAnalytics', :path => '../../node_modules/@capacitor-firebase/analytics'";
|
|
3498
|
+
const analyticsPodLine = " pod 'CapacitorFirebaseAnalytics/Analytics', :path => '../../node_modules/@capacitor-firebase/analytics'";
|
|
3499
|
+
|
|
3500
|
+
let content = fs.readFileSync(podfilePath, "utf8");
|
|
3501
|
+
let nextContent = content;
|
|
3502
|
+
|
|
3503
|
+
if (iosEnabled) {
|
|
3504
|
+
if (!nextContent.includes(basePodLine)) {
|
|
3505
|
+
nextContent = nextContent.replace(
|
|
3506
|
+
/(pod 'CapacitorCommunitySafeArea'[^\n]*\r?\n)/,
|
|
3507
|
+
`$1${basePodLine}\n`
|
|
3508
|
+
);
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3511
|
+
if (!nextContent.includes(analyticsPodLine)) {
|
|
3512
|
+
nextContent = nextContent.replace(
|
|
3513
|
+
/(# Add your Pods here\r?\n)/,
|
|
3514
|
+
`$1${analyticsPodLine}\n`
|
|
3515
|
+
);
|
|
3516
|
+
}
|
|
3517
|
+
} else {
|
|
3518
|
+
nextContent = nextContent
|
|
3519
|
+
.split(/\r?\n/)
|
|
3520
|
+
.filter(line => !line.includes("CapacitorFirebaseAnalytics"))
|
|
3521
|
+
.join("\n");
|
|
3522
|
+
}
|
|
3523
|
+
|
|
3524
|
+
if (nextContent !== content) {
|
|
3525
|
+
fs.writeFileSync(podfilePath, nextContent, "utf8");
|
|
3526
|
+
console.log(`✅ iOS Firebase Analytics Podfile entries ${iosEnabled ? "enabled" : "removed"}.`);
|
|
3527
|
+
} else {
|
|
3528
|
+
console.log(`ℹ️ iOS Firebase Analytics Podfile entries already ${iosEnabled ? "enabled" : "disabled"}.`);
|
|
3529
|
+
}
|
|
3530
|
+
};
|
|
3531
|
+
|
|
3532
|
+
function ensureImportOrRequire(content, moduleName, esmLine, cjsLine) {
|
|
3533
|
+
const escaped = moduleName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3534
|
+
if (new RegExp(`from\\s*['"]${escaped}['"]|require\\s*\\(\\s*['"]${escaped}['"]\\s*\\)`).test(content)) {
|
|
3535
|
+
return content;
|
|
3536
|
+
}
|
|
3537
|
+
|
|
3538
|
+
const isCommonJs = /module\.exports|require\s*\(/.test(content);
|
|
3539
|
+
const line = isCommonJs ? cjsLine : esmLine;
|
|
3540
|
+
|
|
3541
|
+
return `${line}\n${content}`;
|
|
3542
|
+
};
|
|
3543
|
+
|
|
3544
|
+
const ensureViteDefineEntry = (viteContent, key, valueExpression) => {
|
|
3545
|
+
const entry = ` '${key}': JSON.stringify(${valueExpression}),`;
|
|
3546
|
+
|
|
3547
|
+
if (viteContent.includes(`'${key}'`) || viteContent.includes(`"${key}"`)) {
|
|
3548
|
+
return viteContent;
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
if (/define\s*:\s*{/.test(viteContent)) {
|
|
3552
|
+
return viteContent.replace(/define\s*:\s*{\s*/, match => `${match}\n${entry}\n`);
|
|
3553
|
+
}
|
|
3554
|
+
|
|
3555
|
+
const defineBlock = ` define: {
|
|
3556
|
+
'${key}': JSON.stringify(${valueExpression}),
|
|
3557
|
+
},`;
|
|
3558
|
+
|
|
3559
|
+
if (/\n\s*publicDir\s*:\s*[^,\n]+,?/.test(viteContent)) {
|
|
3560
|
+
return viteContent.replace(
|
|
3561
|
+
/(\n\s*publicDir\s*:\s*[^,\n]+,?)/,
|
|
3562
|
+
`$1\n${defineBlock}`
|
|
3563
|
+
);
|
|
3564
|
+
}
|
|
3565
|
+
|
|
3566
|
+
return viteContent.replace(
|
|
3567
|
+
/(return\s+{)/,
|
|
3568
|
+
`$1\n${defineBlock}`
|
|
3569
|
+
);
|
|
3570
|
+
};
|
|
3571
|
+
|
|
3572
|
+
const ensurePrivacyAndAnalyticsViteConfig = () => {
|
|
3573
|
+
const viteConfigPath = getViteConfigPath();
|
|
3574
|
+
|
|
3575
|
+
if (!viteConfigPath) {
|
|
3576
|
+
console.warn("⚠️ No vite config found. Skipping privacy/analytics Vite sync.");
|
|
3577
|
+
return;
|
|
3578
|
+
}
|
|
3579
|
+
|
|
3580
|
+
let viteContent = fs.readFileSync(viteConfigPath, "utf8");
|
|
3581
|
+
const originalContent = viteContent;
|
|
3582
|
+
|
|
3583
|
+
viteContent = ensureImportOrRequire(
|
|
3584
|
+
viteContent,
|
|
3585
|
+
"fs",
|
|
3586
|
+
"import fs from 'fs';",
|
|
3587
|
+
"const fs = require('fs');"
|
|
3588
|
+
);
|
|
3589
|
+
|
|
3590
|
+
viteContent = ensureImportOrRequire(
|
|
3591
|
+
viteContent,
|
|
3592
|
+
"path",
|
|
3593
|
+
"import path from 'path';",
|
|
3594
|
+
"const path = require('path');"
|
|
3595
|
+
);
|
|
3596
|
+
|
|
3597
|
+
if (!/\bcapacitorConfig\b/.test(viteContent)) {
|
|
3598
|
+
viteContent = viteContent.replace(
|
|
3599
|
+
/(\r?\nexport\s+default|\r?\nmodule\.exports)/,
|
|
3600
|
+
`\nconst capacitorConfig = JSON.parse(fs.readFileSync(path.resolve(__dirname, './capacitor.config.json'), 'utf8'));\n$1`
|
|
3601
|
+
);
|
|
3602
|
+
}
|
|
3603
|
+
|
|
3604
|
+
if (!/\bAPP_UNIQUE_ID\b/.test(viteContent)) {
|
|
3605
|
+
viteContent = viteContent.replace(
|
|
3606
|
+
/(const capacitorConfig = JSON\.parse\(fs\.readFileSync\(path\.resolve\(__dirname,\s*['"]\.\/capacitor\.config\.json['"]\),\s*['"]utf8['"]\)\);\r?\n)/,
|
|
3607
|
+
`$1const APP_UNIQUE_ID = capacitorConfig.android?.APP_UNIQUE_ID ?? '';\n`
|
|
3608
|
+
);
|
|
3609
|
+
}
|
|
3610
|
+
|
|
3611
|
+
if (!/\bANDROID_ANALYTICS_ENABLED\b/.test(viteContent)) {
|
|
3612
|
+
viteContent = viteContent.replace(
|
|
3613
|
+
/(const APP_UNIQUE_ID = capacitorConfig\.android\?\.APP_UNIQUE_ID \?\? ['"]['"];\r?\n)/,
|
|
3614
|
+
`$1const ANDROID_ANALYTICS_ENABLED = capacitorConfig.android?.ANALYTICS_ENABLED ?? false;\n`
|
|
3615
|
+
);
|
|
3616
|
+
}
|
|
3617
|
+
|
|
3618
|
+
if (!/\bIOS_ANALYTICS_ENABLED\b/.test(viteContent)) {
|
|
3619
|
+
viteContent = viteContent.replace(
|
|
3620
|
+
/(const ANDROID_ANALYTICS_ENABLED = capacitorConfig\.android\?\.ANALYTICS_ENABLED \?\? false;\r?\n)/,
|
|
3621
|
+
`$1const IOS_ANALYTICS_ENABLED = capacitorConfig.ios?.ANALYTICS_ENABLED ?? false;\n`
|
|
3622
|
+
);
|
|
3623
|
+
}
|
|
3624
|
+
|
|
3625
|
+
if (!/\bIAP_DESIGN\b/.test(viteContent)) {
|
|
3626
|
+
viteContent = viteContent.replace(
|
|
3627
|
+
/(const IOS_ANALYTICS_ENABLED = capacitorConfig\.ios\?\.ANALYTICS_ENABLED \?\? false;\r?\n)/,
|
|
3628
|
+
`$1const IAP_DESIGN = capacitorConfig["IAP-Design"] ?? 'default';\n`
|
|
3629
|
+
);
|
|
3630
|
+
}
|
|
3631
|
+
|
|
3632
|
+
viteContent = viteContent
|
|
3633
|
+
.replace(
|
|
3634
|
+
/const ANDROID_ANALYTICS_ENABLED = capacitorConfig\.android\?\.ANALY[ST]ICS_ENABLED \?\? capacitorConfig\.android\?\.ANALYTICS_ENABLED \?\? false;/g,
|
|
3635
|
+
"const ANDROID_ANALYTICS_ENABLED = capacitorConfig.android?.ANALYTICS_ENABLED ?? false;"
|
|
3636
|
+
)
|
|
3637
|
+
.replace(
|
|
3638
|
+
/const IOS_ANALYTICS_ENABLED = capacitorConfig\.ios\?\.ANALY[ST]ICS_ENABLED \?\? capacitorConfig\.ios\?\.ANALYTICS_ENABLED \?\? false;/g,
|
|
3639
|
+
"const IOS_ANALYTICS_ENABLED = capacitorConfig.ios?.ANALYTICS_ENABLED ?? false;"
|
|
3640
|
+
);
|
|
3641
|
+
|
|
3642
|
+
viteContent = ensureViteDefineEntry(viteContent, "import.meta.env.VITE_APP_UNIQUE_ID", "APP_UNIQUE_ID");
|
|
3643
|
+
viteContent = ensureViteDefineEntry(viteContent, "import.meta.env.VITE_ANDROID_ANALYTICS_ENABLED", "ANDROID_ANALYTICS_ENABLED");
|
|
3644
|
+
viteContent = ensureViteDefineEntry(viteContent, "import.meta.env.VITE_IOS_ANALYTICS_ENABLED", "IOS_ANALYTICS_ENABLED");
|
|
3645
|
+
viteContent = ensureViteDefineEntry(viteContent, "import.meta.env.VITE_IAP_DESIGN", "IAP_DESIGN");
|
|
3646
|
+
|
|
3647
|
+
if (viteContent !== originalContent) {
|
|
3648
|
+
fs.writeFileSync(viteConfigPath, viteContent, "utf8");
|
|
3649
|
+
console.log("✅ vite.config.(m)js privacy/analytics settings synced.");
|
|
3650
|
+
} else {
|
|
3651
|
+
console.log("ℹ️ vite.config.(m)js privacy/analytics settings already synced.");
|
|
3652
|
+
}
|
|
3653
|
+
};
|
|
3654
|
+
|
|
3655
|
+
const syncFirebaseAnalyticsSetup = () => {
|
|
3656
|
+
syncFirebaseAnalyticsPackages();
|
|
3657
|
+
syncFirebaseAnalyticsPodfile();
|
|
3658
|
+
ensurePrivacyAndAnalyticsViteConfig();
|
|
3659
|
+
};
|
|
3660
|
+
|
|
3661
|
+
const getAdmobIapConfigPath = () => {
|
|
3662
|
+
const possiblePaths = [
|
|
3663
|
+
path.join(process.cwd(), 'src', 'js', 'Ads', 'admob-ad-configuration-nextgen.json'),
|
|
3664
|
+
path.join(process.cwd(), 'src', 'js', 'Ads', 'admob-ad-configuration.json')
|
|
3665
|
+
];
|
|
3666
|
+
|
|
3667
|
+
return possiblePaths.find(fileExists);
|
|
3668
|
+
};
|
|
3669
|
+
|
|
3670
|
+
const getAdmobIapConfig = () => {
|
|
3671
|
+
const admobIapConfigPath = getAdmobIapConfigPath();
|
|
3672
|
+
|
|
3673
|
+
if (!admobIapConfigPath) {
|
|
3674
|
+
console.warn("⚠️ No AdMob configuration JSON found. Skipping RevenueCat IAP package sync.");
|
|
3675
|
+
return null;
|
|
3676
|
+
}
|
|
3677
|
+
|
|
3678
|
+
try {
|
|
3679
|
+
return JSON.parse(fs.readFileSync(admobIapConfigPath, "utf8"));
|
|
3680
|
+
} catch (err) {
|
|
3681
|
+
console.error(`❌ Failed to read ${path.relative(process.cwd(), admobIapConfigPath)}`, err);
|
|
3682
|
+
process.exit(1);
|
|
3683
|
+
}
|
|
3684
|
+
};
|
|
3685
|
+
|
|
3686
|
+
const isAnyIapStoreEnabled = (iapConfig) => {
|
|
3687
|
+
return Boolean(
|
|
3688
|
+
iapConfig?.playstore === true ||
|
|
3689
|
+
iapConfig?.samsung === true ||
|
|
3690
|
+
iapConfig?.amazon === true ||
|
|
3691
|
+
iapConfig?.ios === true
|
|
3692
|
+
);
|
|
3693
|
+
};
|
|
3694
|
+
|
|
3695
|
+
const ensureIapDesignInCapacitorConfig = (iapConfig) => {
|
|
3696
|
+
if (!isAnyIapStoreEnabled(iapConfig)) return;
|
|
3697
|
+
|
|
3698
|
+
if (!fs.existsSync(configPath)) {
|
|
3699
|
+
console.warn("⚠️ capacitor.config.json not found. Skipping IAP-Design sync.");
|
|
3700
|
+
return;
|
|
3701
|
+
}
|
|
3702
|
+
|
|
3703
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
3704
|
+
|
|
3705
|
+
if (Object.prototype.hasOwnProperty.call(config, "IAP-Design")) {
|
|
3706
|
+
console.log("ℹ️ capacitor.config.json IAP-Design already configured.");
|
|
3707
|
+
return;
|
|
3708
|
+
}
|
|
3709
|
+
|
|
3710
|
+
config["IAP-Design"] = "default";
|
|
3711
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
3712
|
+
console.log('✅ Added "IAP-Design": "default" to capacitor.config.json.');
|
|
3713
|
+
};
|
|
3714
|
+
|
|
3715
|
+
const getCurrentStoreId = () => {
|
|
3716
|
+
const envStoreId = Number(process.env.VITE_STORE_ID);
|
|
3717
|
+
if (Number.isInteger(envStoreId) && envStoreId > 0) return envStoreId;
|
|
3718
|
+
|
|
3719
|
+
const viteConfigPath = getViteConfigPath();
|
|
3720
|
+
if (!viteConfigPath) return 1;
|
|
3721
|
+
|
|
3722
|
+
const viteConfigContent = fs.readFileSync(viteConfigPath, "utf8");
|
|
3723
|
+
const commonAliasMatch = viteConfigContent.match(/['"]@common['"]:\s*path\.resolve\(__dirname,\s*['"]([^'"]+)['"]\)/);
|
|
3724
|
+
|
|
3725
|
+
if (!commonAliasMatch) return 1;
|
|
3726
|
+
|
|
3727
|
+
const commonFilePath = path.resolve(path.dirname(viteConfigPath), commonAliasMatch[1]);
|
|
3728
|
+
if (!fs.existsSync(commonFilePath)) return 1;
|
|
3729
|
+
|
|
3730
|
+
const commonFileContent = fs.readFileSync(commonFilePath, "utf8");
|
|
3731
|
+
const environmentStoreIdMatch = commonFileContent.match(
|
|
3732
|
+
/^[ \t]*export\s+let\s+_storeid\s*=\s*import\.meta\.env\.VITE_STORE_ID\s*\|\|\s*(\d+)\s*;/m
|
|
3733
|
+
);
|
|
3734
|
+
if (environmentStoreIdMatch) return Number(environmentStoreIdMatch[1]);
|
|
3735
|
+
|
|
3736
|
+
const directStoreIdMatch = commonFileContent.match(
|
|
3737
|
+
/^[ \t]*export\s+let\s+_storeid\s*=\s*(\d+)\s*;/m
|
|
3738
|
+
);
|
|
3739
|
+
|
|
3740
|
+
return directStoreIdMatch ? Number(directStoreIdMatch[1]) : 1;
|
|
3741
|
+
};
|
|
3742
|
+
|
|
3743
|
+
const getRevenueCatStoreState = (iapConfig) => {
|
|
3744
|
+
const storeId = getCurrentStoreId();
|
|
3745
|
+
const configuration = iapConfig.configuration || {};
|
|
3746
|
+
const storeMap = {
|
|
3747
|
+
1: {
|
|
3748
|
+
name: "PlayStore",
|
|
3749
|
+
enabled: iapConfig.playstore === true,
|
|
3750
|
+
apiKeyName: "IAP.configuration.playstore",
|
|
3751
|
+
apiKey: configuration.playstore,
|
|
3752
|
+
usesGoogleBilling: true
|
|
3753
|
+
},
|
|
3754
|
+
6: {
|
|
3755
|
+
name: "iOSStore",
|
|
3756
|
+
enabled: iapConfig.ios === true,
|
|
3757
|
+
apiKeyName: "IAP.configuration.ios",
|
|
3758
|
+
apiKey: configuration.ios,
|
|
3759
|
+
usesGoogleBilling: false
|
|
3760
|
+
},
|
|
3761
|
+
7: {
|
|
3762
|
+
name: "AmazonStore",
|
|
3763
|
+
enabled: iapConfig.amazon === true,
|
|
3764
|
+
apiKeyName: "IAP.configuration.amazon",
|
|
3765
|
+
apiKey: configuration.amazon,
|
|
3766
|
+
usesGoogleBilling: false
|
|
3767
|
+
}
|
|
3768
|
+
};
|
|
3769
|
+
|
|
3770
|
+
return {
|
|
3771
|
+
storeId,
|
|
3772
|
+
store: storeMap[storeId] || null
|
|
3773
|
+
};
|
|
3774
|
+
};
|
|
3775
|
+
|
|
3776
|
+
const syncAmazonFireWebViewDependency = (enabled) => {
|
|
3777
|
+
if (!fs.existsSync(androidAppBuildGradlePath)) {
|
|
3778
|
+
console.error(`❌ Android app build.gradle not found: ${androidAppBuildGradlePath}`);
|
|
3779
|
+
process.exit(1);
|
|
3780
|
+
}
|
|
3781
|
+
|
|
3782
|
+
const currentContent = fs.readFileSync(androidAppBuildGradlePath, "utf8");
|
|
3783
|
+
let nextContent = currentContent.replace(AMAZON_WINDOW_EXTENSIONS_DEPENDENCY_PATTERN, "");
|
|
3784
|
+
|
|
3785
|
+
if (enabled) {
|
|
3786
|
+
const newline = nextContent.includes("\r\n") ? "\r\n" : "\n";
|
|
3787
|
+
const dependenciesHeaderPattern = /(^dependencies[ \t]*\{[ \t]*\r?\n)/m;
|
|
3788
|
+
|
|
3789
|
+
if (!dependenciesHeaderPattern.test(nextContent)) {
|
|
3790
|
+
console.error(`❌ dependencies block not found in ${androidAppBuildGradlePath}`);
|
|
3791
|
+
process.exit(1);
|
|
3792
|
+
}
|
|
3793
|
+
|
|
3794
|
+
nextContent = nextContent.replace(
|
|
3795
|
+
dependenciesHeaderPattern,
|
|
3796
|
+
`$1 ${AMAZON_WINDOW_EXTENSIONS_DEPENDENCY}${newline}`
|
|
3797
|
+
);
|
|
3798
|
+
}
|
|
3799
|
+
|
|
3800
|
+
if (nextContent !== currentContent) {
|
|
3801
|
+
fs.writeFileSync(androidAppBuildGradlePath, nextContent, "utf8");
|
|
3802
|
+
}
|
|
3803
|
+
|
|
3804
|
+
console.log(enabled
|
|
3805
|
+
? "✅ Amazon Fire WebView compatibility dependency enabled for Store ID 7."
|
|
3806
|
+
: "ℹ️ Amazon Fire WebView compatibility dependency disabled for this store.");
|
|
3807
|
+
};
|
|
3808
|
+
|
|
3809
|
+
function syncAndroidAdIdPermission() {
|
|
3810
|
+
if (!fs.existsSync(androidManifestPath)) return;
|
|
3811
|
+
const originalContent = fs.readFileSync(androidManifestPath, 'utf8');
|
|
3812
|
+
const newline = originalContent.includes('\r\n') ? '\r\n' : '\n';
|
|
3813
|
+
const marker = '<!-- Codeplay: child-directed advertising ID exclusion -->';
|
|
3814
|
+
const permission = 'com.google.android.gms.permission.AD_ID';
|
|
3815
|
+
const permissionTag = `<uses-permission android:name="${permission}" tools:node="remove" />`;
|
|
3816
|
+
const generatedRule = new RegExp(`^[ \\t]*${marker}\\r?\\n[ \\t]*<uses-permission\\b[^>]*tools:node="remove"[^>]*\\/>\\r?\\n`, 'gm');
|
|
3817
|
+
const hadGeneratedRule = originalContent.includes(marker);
|
|
3818
|
+
let content = originalContent.replace(generatedRule, '');
|
|
3819
|
+
if (isKidsAdmobApp()) {
|
|
3820
|
+
content = content.replace(/(?:^[ \t]*)?<uses-permission\b[^>]*>(?:[ \t\r\n]*<\/uses-permission>)?[ \t]*(?:\r?\n)?/gm, tag =>
|
|
3821
|
+
/android:name\s*=\s*["']com\.google\.android\.gms\.permission\.AD_ID["']/.test(tag) ? '' : tag);
|
|
3822
|
+
if (!/xmlns:tools\s*=/.test(content)) {
|
|
3823
|
+
content = content.replace(/<manifest\b/, '<manifest xmlns:tools="http://schemas.android.com/tools"');
|
|
3824
|
+
}
|
|
3825
|
+
content = content.replace(/(<manifest\b[^>]*>)(?:\r?\n)?/, (match, root) => `${root}${newline} ${marker}${newline} ${permissionTag}${newline}`);
|
|
3826
|
+
} else if (hadGeneratedRule) {
|
|
3827
|
+
content = content.replace(/(<manifest\b[^>]*>)(?:\r?\n)?/, (match, root) => `${root}${newline} <uses-permission android:name="${permission}" />${newline}`);
|
|
3828
|
+
}
|
|
3829
|
+
if (content !== originalContent) {
|
|
3830
|
+
fs.writeFileSync(androidManifestPath, content, 'utf8');
|
|
3831
|
+
console.log(isKidsAdmobApp() ? 'Android advertising ID permission excluded for this kids app.' : 'Android advertising ID permission restored for this non-kids app.');
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
|
|
3835
|
+
const ensureAndroidPermission = (permission, enabled) => {
|
|
3836
|
+
if (!fs.existsSync(androidManifestPath)) return false;
|
|
3837
|
+
|
|
3838
|
+
let manifestContent = fs.readFileSync(androidManifestPath, "utf8");
|
|
3839
|
+
const permissionLine = ` <uses-permission android:name="${permission}" />`;
|
|
3840
|
+
const permissionRegex = new RegExp(`^\\s*<uses-permission\\s+android:name="${permission.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"\\s*/?>\\s*\\r?\\n?`, "m");
|
|
3841
|
+
|
|
3842
|
+
if (enabled) {
|
|
3843
|
+
if (permissionRegex.test(manifestContent)) return false;
|
|
3844
|
+
|
|
3845
|
+
manifestContent = manifestContent.replace(
|
|
3846
|
+
/<\/manifest>\s*$/,
|
|
3847
|
+
`${permissionLine}\n</manifest>`
|
|
3848
|
+
);
|
|
3849
|
+
fs.writeFileSync(androidManifestPath, manifestContent, "utf8");
|
|
3850
|
+
console.log(`✅ Added Android permission: ${permission}`);
|
|
3851
|
+
return true;
|
|
3852
|
+
}
|
|
3853
|
+
|
|
3854
|
+
if (!permissionRegex.test(manifestContent)) return false;
|
|
3855
|
+
|
|
3856
|
+
manifestContent = manifestContent.replace(permissionRegex, "");
|
|
3857
|
+
fs.writeFileSync(androidManifestPath, manifestContent, "utf8");
|
|
3858
|
+
console.log(`✅ Removed Android permission: ${permission}`);
|
|
3859
|
+
return true;
|
|
3860
|
+
};
|
|
3861
|
+
|
|
3862
|
+
const syncRevenueCatIapSetup = () => {
|
|
3863
|
+
const admobConfig = getAdmobIapConfig();
|
|
3864
|
+
const iapConfig = admobConfig?.IAP;
|
|
3865
|
+
|
|
3866
|
+
if (!iapConfig) {
|
|
3867
|
+
syncAmazonFireWebViewDependency(false);
|
|
3868
|
+
console.log("ℹ️ IAP configuration not found. Skipping RevenueCat package sync.");
|
|
3869
|
+
return;
|
|
3870
|
+
}
|
|
3871
|
+
|
|
3872
|
+
ensureIapDesignInCapacitorConfig(iapConfig);
|
|
3873
|
+
|
|
3874
|
+
const revenueCatEnabled =
|
|
3875
|
+
iapConfig.playstore === true ||
|
|
3876
|
+
iapConfig.ios === true ||
|
|
3877
|
+
iapConfig.amazon === true;
|
|
3878
|
+
const { storeId, store } = getRevenueCatStoreState(iapConfig);
|
|
3879
|
+
const revenueCatEnabledForCurrentStore = store?.enabled === true;
|
|
3880
|
+
syncAmazonFireWebViewDependency(storeId === 7 && iapConfig.amazon === true);
|
|
3881
|
+
|
|
3882
|
+
if (revenueCatEnabledForCurrentStore && !store.apiKey) {
|
|
3883
|
+
console.error(`❌ RevenueCat IAP is enabled for ${store.name} but ${store.apiKeyName} is missing.`);
|
|
3884
|
+
process.exit(1);
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3887
|
+
let packageChanged = false;
|
|
3888
|
+
|
|
3889
|
+
if (revenueCatEnabledForCurrentStore) {
|
|
3890
|
+
if (!isPackageAvailable("@revenuecat/purchases-capacitor")) {
|
|
3891
|
+
runNpmCommand("npm install @revenuecat/purchases-capacitor");
|
|
3892
|
+
packageChanged = true;
|
|
3893
|
+
} else {
|
|
3894
|
+
console.log(`ℹ️ RevenueCat package already installed for ${store.name}.`);
|
|
3895
|
+
}
|
|
3896
|
+
} else if (isPackageAvailable("@revenuecat/purchases-capacitor")) {
|
|
3897
|
+
runNpmCommand("npm uninstall @revenuecat/purchases-capacitor");
|
|
3898
|
+
packageChanged = true;
|
|
3899
|
+
} else {
|
|
3900
|
+
console.log(`ℹ️ RevenueCat package is not required for store id ${storeId}.`);
|
|
3901
|
+
}
|
|
3902
|
+
|
|
3903
|
+
ensureAndroidPermission("com.android.vending.BILLING", store?.usesGoogleBilling === true && revenueCatEnabledForCurrentStore);
|
|
3904
|
+
|
|
3905
|
+
if (iapConfig.samsung === true && revenueCatEnabled) {
|
|
3906
|
+
console.warn("⚠️ Samsung IAP uses cordova-plugin-samsungiap; PlayStore/Amazon/iOS use RevenueCat.");
|
|
3907
|
+
}
|
|
3908
|
+
|
|
3909
|
+
if (packageChanged) {
|
|
3910
|
+
runNpmCommand("npx cap sync");
|
|
3911
|
+
}
|
|
3912
|
+
};
|
|
3913
|
+
|
|
3914
|
+
|
|
3915
|
+
|
|
3916
|
+
|
|
3917
|
+
|
|
3918
|
+
|
|
3919
|
+
|
|
3920
|
+
|
|
3921
|
+
|
|
3922
|
+
const compareVersion = (v1, v2) => {
|
|
3923
|
+
const a = v1.split(".").map(Number);
|
|
3924
|
+
const b = v2.split(".").map(Number);
|
|
3925
|
+
|
|
3926
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
3927
|
+
const num1 = a[i] || 0;
|
|
3928
|
+
const num2 = b[i] || 0;
|
|
3929
|
+
if (num1 > num2) return 1;
|
|
3930
|
+
if (num1 < num2) return -1;
|
|
3931
|
+
}
|
|
3932
|
+
return 0;
|
|
3933
|
+
};
|
|
3934
|
+
|
|
3935
|
+
|
|
3936
|
+
|
|
3937
|
+
|
|
3938
|
+
|
|
3939
|
+
|
|
3940
|
+
|
|
3941
|
+
|
|
3942
|
+
|
|
3943
|
+
|
|
3944
|
+
const admobConfigPaths = [
|
|
3945
|
+
path.join('src', 'js', 'Ads', 'admob-ad-configuration-nextgen.json'),
|
|
3946
|
+
path.join('src', 'js', 'Ads', 'admob-ad-configuration.json')
|
|
3947
|
+
];
|
|
3948
|
+
|
|
3949
|
+
const checkAdmobConfigurationProperty=()=>{
|
|
3950
|
+
|
|
3951
|
+
|
|
3952
|
+
if (!_admobConfig.ADMOB_ENABLED)
|
|
3953
|
+
{
|
|
3954
|
+
console.log("ℹ️ Admob is not enabled so ad configuration checking is skipping...");
|
|
3955
|
+
return;
|
|
3956
|
+
}
|
|
3957
|
+
|
|
3958
|
+
const admobConfigPath = admobConfigPaths.find(fileExists);
|
|
3959
|
+
|
|
3960
|
+
if (!admobConfigPath) {
|
|
3961
|
+
console.error("❌ Failed to find AdMob configuration JSON. Checked admob-ad-configuration-nextgen.json and admob-ad-configuration.json");
|
|
3962
|
+
process.exit(1);
|
|
3963
|
+
}
|
|
3964
|
+
|
|
3965
|
+
|
|
3966
|
+
const REQUIRED_CONFIG_KEYS = [
|
|
3967
|
+
"isKidsApp",
|
|
3968
|
+
"isTesting",
|
|
3969
|
+
"consentDebug",
|
|
3970
|
+
"isConsoleLogEnabled",
|
|
3971
|
+
"bannerEnabled",
|
|
3972
|
+
"bannerDisabledPages",
|
|
3973
|
+
"interstitialEnabled",
|
|
3974
|
+
"appOpenEnabled",
|
|
3975
|
+
"rewardVideoEnabled",
|
|
3976
|
+
"rewardInterstitialEnabled",
|
|
3977
|
+
"collapsibleEnabled",
|
|
3978
|
+
"isLandScape",
|
|
3979
|
+
"isOverlappingEnable",
|
|
3980
|
+
"overlappingHeight",
|
|
3981
|
+
"bannerTypeAndroid",
|
|
3982
|
+
"bannerTypeAndroidLandscape",
|
|
3983
|
+
"bannerTypeiOS",
|
|
3984
|
+
"bannerTypeiOSLandscape",
|
|
3985
|
+
"bannerTopSpaceColor",
|
|
3986
|
+
"interstitialLoadScreenTextColor",
|
|
3987
|
+
"interstitialLoadScreenBackgroundColor",
|
|
3988
|
+
"beforeBannerSpace",
|
|
3989
|
+
"whenShow",
|
|
3990
|
+
"minimumClick",
|
|
3991
|
+
"interstitialTimeOut",
|
|
3992
|
+
"interstitialFirstTimeOut",
|
|
3993
|
+
"appOpenAdsTimeOut",
|
|
3994
|
+
"maxRetryCount",
|
|
3995
|
+
"retrySecondsAr",
|
|
3996
|
+
"appOpenPerSession",
|
|
3997
|
+
"interstitialPerSession",
|
|
3998
|
+
"appOpenFirstTimeOut"
|
|
3999
|
+
];
|
|
4000
|
+
|
|
4001
|
+
|
|
4002
|
+
|
|
4003
|
+
|
|
4004
|
+
|
|
4005
|
+
let admobConfigInJson;
|
|
4006
|
+
|
|
4007
|
+
try {
|
|
4008
|
+
admobConfigInJson = JSON.parse(readFileSync(admobConfigPath, "utf8"));
|
|
4009
|
+
} catch (err) {
|
|
4010
|
+
console.error(`❌ Failed to read ${admobConfigPath}`, err);
|
|
4011
|
+
process.exit(1);
|
|
4012
|
+
}
|
|
4013
|
+
|
|
4014
|
+
// ✅ Validate config object exists
|
|
4015
|
+
if (!admobConfigInJson.config) {
|
|
4016
|
+
console.error(`❌ "config" object is missing in ${admobConfigPath}`);
|
|
4017
|
+
process.exit(1);
|
|
4018
|
+
}
|
|
4019
|
+
|
|
4020
|
+
|
|
4021
|
+
const admobConfigMinVersion="1.0"
|
|
4022
|
+
|
|
4023
|
+
if (compareVersion(admobConfigInJson.VERSION, admobConfigMinVersion) < 0) {
|
|
4024
|
+
console.error(`❌ Please use at-least version ${admobConfigMinVersion} in "${admobConfigPath}"`);
|
|
4025
|
+
process.exit(1);
|
|
4026
|
+
}
|
|
4027
|
+
|
|
4028
|
+
|
|
4029
|
+
const config = admobConfigInJson.config;
|
|
4030
|
+
|
|
4031
|
+
// ✅ Find missing properties
|
|
4032
|
+
const missingKeys = REQUIRED_CONFIG_KEYS.filter(
|
|
4033
|
+
key => !(key in config)
|
|
4034
|
+
);
|
|
4035
|
+
|
|
4036
|
+
|
|
4037
|
+
|
|
4038
|
+
if (missingKeys.length > 0) {
|
|
4039
|
+
console.error(`❌ Missing required configuration keys. Please check it in '${admobConfigPath}'`);
|
|
4040
|
+
|
|
4041
|
+
missingKeys.forEach(k => console.error(" - " + k));
|
|
4042
|
+
process.exit(1);
|
|
4043
|
+
}
|
|
4044
|
+
|
|
4045
|
+
|
|
4046
|
+
console.log(`✅ All keys exist in "${admobConfigPath}". Configuration looks good.`);
|
|
4047
|
+
}
|
|
4048
|
+
|
|
4049
|
+
|
|
4050
|
+
|
|
4051
|
+
function ensureGitignoreEntry(entry, shouldIgnore = true) {
|
|
4052
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
4053
|
+
|
|
4054
|
+
// If .gitignore doesn't exist, create it
|
|
4055
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
4056
|
+
if (!shouldIgnore) return;
|
|
4057
|
+
fs.writeFileSync(gitignorePath, `${entry}\n`, 'utf8');
|
|
4058
|
+
console.log(`✅ .gitignore created and added: ${entry}`);
|
|
4059
|
+
return;
|
|
4060
|
+
}
|
|
4061
|
+
|
|
4062
|
+
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
4063
|
+
|
|
4064
|
+
if (!shouldIgnore) {
|
|
4065
|
+
const normalizedEntry = entry.replace(/^\/|\/$/g, '');
|
|
4066
|
+
const nextContent = content.split(/(?<=\n)/).filter(line =>
|
|
4067
|
+
line.trim().replace(/^\/|\/$/g, '') !== normalizedEntry
|
|
4068
|
+
).join('');
|
|
4069
|
+
if (nextContent !== content) {
|
|
4070
|
+
fs.writeFileSync(gitignorePath, nextContent, 'utf8');
|
|
4071
|
+
console.log(`✅ Removed from .gitignore: ${entry}`);
|
|
4072
|
+
}
|
|
4073
|
+
return;
|
|
4074
|
+
}
|
|
4075
|
+
|
|
4076
|
+
// Normalize lines (trim + remove trailing slashes for comparison)
|
|
4077
|
+
const lines = content
|
|
4078
|
+
.split(/\r?\n/)
|
|
4079
|
+
.map(l => l.trim());
|
|
4080
|
+
|
|
4081
|
+
const normalizedEntry = entry.replace(/\/$/, '');
|
|
4082
|
+
|
|
4083
|
+
const exists = lines.some(
|
|
4084
|
+
line => line.replace(/\/$/, '') === normalizedEntry
|
|
4085
|
+
);
|
|
4086
|
+
|
|
4087
|
+
if (exists) {
|
|
4088
|
+
console.log(`ℹ️ .gitignore already contains: ${entry}`);
|
|
4089
|
+
return;
|
|
4090
|
+
}
|
|
4091
|
+
|
|
4092
|
+
// Ensure file ends with newline
|
|
4093
|
+
const separator = content.endsWith('\n') ? '' : '\n';
|
|
4094
|
+
|
|
4095
|
+
fs.appendFileSync(gitignorePath, `${separator}${entry}\n`, 'utf8');
|
|
4096
|
+
console.log(`✅ Added to .gitignore: ${entry}`);
|
|
4097
|
+
}
|
|
4098
|
+
|
|
4099
|
+
|
|
4100
|
+
ensureGitignoreEntry('buildCodeplay/', false);
|
|
4101
|
+
ensureGitignoreEntry('agent-temp/');
|
|
4102
|
+
|
|
4103
|
+
|
|
4104
|
+
// ======================================================
|
|
4105
|
+
// Validate theme folder location (src/js/theme is NOT allowed)
|
|
4106
|
+
// ======================================================
|
|
4107
|
+
|
|
4108
|
+
function validateThemeFolderLocation() {
|
|
4109
|
+
const oldThemePath = path.join(process.cwd(), 'src', 'js', 'theme');
|
|
4110
|
+
const newThemePath = path.join(process.cwd(), 'src', 'theme');
|
|
4111
|
+
|
|
4112
|
+
// ❌ Block old structure
|
|
4113
|
+
if (fs.existsSync(oldThemePath)) {
|
|
4114
|
+
console.error(
|
|
4115
|
+
'\n❌ INVALID PROJECT STRUCTURE DETECTED\n' +
|
|
4116
|
+
'--------------------------------------------------\n' +
|
|
4117
|
+
'The "theme" folder must NOT be inside:\n' +
|
|
4118
|
+
' src/js/theme\n\n' +
|
|
4119
|
+
'✅ Correct location is:\n' +
|
|
4120
|
+
' src/theme\n\n' +
|
|
4121
|
+
'🛑 Please move the folder and re-run the build.\n'
|
|
4122
|
+
);
|
|
4123
|
+
process.exit(1);
|
|
4124
|
+
}
|
|
4125
|
+
|
|
4126
|
+
// ⚠️ Optional warning if new theme folder is missing
|
|
4127
|
+
if (!fs.existsSync(newThemePath)) {
|
|
4128
|
+
console.warn(
|
|
4129
|
+
'\n⚠️ WARNING: "src/theme" folder not found.\n' +
|
|
4130
|
+
'If your app uses themes, please ensure it exists.\n'
|
|
4131
|
+
);
|
|
4132
|
+
} else {
|
|
4133
|
+
console.log('✅ Theme folder structure validated (src/theme).');
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
4136
|
+
const validateAndRestoreSignDetails=()=>{
|
|
4137
|
+
|
|
4138
|
+
// Read config file
|
|
4139
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
4140
|
+
|
|
4141
|
+
// Ensure android and buildOptions exist
|
|
4142
|
+
if (!config.android) config.android = {};
|
|
4143
|
+
if (!config.android.buildOptions) config.android.buildOptions = {};
|
|
4144
|
+
|
|
4145
|
+
// Update only if changed
|
|
4146
|
+
let updated = false;
|
|
4147
|
+
|
|
4148
|
+
if (config.android.buildOptions.releaseType !== 'AAB') {
|
|
4149
|
+
config.android.buildOptions.releaseType = 'AAB';
|
|
4150
|
+
updated = true;
|
|
4151
|
+
}
|
|
4152
|
+
|
|
4153
|
+
if (config.android.buildOptions.signingType !== 'jarsigner') {
|
|
4154
|
+
config.android.buildOptions.signingType = 'jarsigner';
|
|
4155
|
+
updated = true;
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
// Write back only if modified
|
|
4159
|
+
if (updated) {
|
|
4160
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
4161
|
+
console.log('capacitor.config.json updated successfully.');
|
|
4162
|
+
} else {
|
|
4163
|
+
console.log('No changes needed.');
|
|
4164
|
+
}
|
|
4165
|
+
|
|
4166
|
+
}
|
|
4167
|
+
|
|
4168
|
+
//################################## SystemBars.java update for "@capacitor/android": "^8.3.0" plugin START ###############################
|
|
4169
|
+
|
|
4170
|
+
|
|
4171
|
+
const filePath = path.join(
|
|
4172
|
+
__dirname,
|
|
4173
|
+
"../node_modules/@capacitor/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java"
|
|
4174
|
+
);
|
|
4175
|
+
|
|
4176
|
+
const SYSTEM_BARS_PATCH_MARKER = "CODEPLAY_KEYBOARD_INSET_FIX";
|
|
4177
|
+
|
|
4178
|
+
function getSystemBarsPatchedBlock(originalBlock, content) {
|
|
4179
|
+
const isCapacitor842OrNewer = content.includes("private String insetsHandling = INSETS_HANDLING_CSS;");
|
|
4180
|
+
const safeAreaCondition = isCapacitor842OrNewer
|
|
4181
|
+
? "INSETS_HANDLING_CSS.equals(insetsHandling)"
|
|
4182
|
+
: "Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled";
|
|
4183
|
+
|
|
4184
|
+
return `if (shouldPassthroughInsets) {
|
|
4185
|
+
/* ${SYSTEM_BARS_PATCH_MARKER}
|
|
4186
|
+
${originalBlock.split("\n").map(line => " " + line).join("\n")}
|
|
4187
|
+
*/
|
|
4188
|
+
|
|
4189
|
+
// Codeplay fix: avoid adding IME height as WebView parent padding.
|
|
4190
|
+
v.setPadding(0, 0, 0, 0);
|
|
4191
|
+
|
|
4192
|
+
if (${safeAreaCondition}) {
|
|
4193
|
+
Insets safeAreaInsets = calcSafeAreaInsets(insets);
|
|
4194
|
+
injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
|
|
4195
|
+
}
|
|
4196
|
+
|
|
4197
|
+
return insets; // WebView handles keyboard and safe-area sizing.
|
|
4198
|
+
}`;
|
|
4199
|
+
}
|
|
4200
|
+
|
|
4201
|
+
function findShouldPassthroughInsetsBlock(content) {
|
|
4202
|
+
const start = content.indexOf(" if (shouldPassthroughInsets) {");
|
|
4203
|
+
if (start === -1) return null;
|
|
4204
|
+
|
|
4205
|
+
const endMarker = "\n\n if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM)";
|
|
4206
|
+
const end = content.indexOf(endMarker, start);
|
|
4207
|
+
if (end === -1) return null;
|
|
4208
|
+
|
|
4209
|
+
return {
|
|
4210
|
+
start,
|
|
4211
|
+
end,
|
|
4212
|
+
text: content.slice(start, end)
|
|
4213
|
+
};
|
|
4214
|
+
}
|
|
4215
|
+
|
|
4216
|
+
// 🚨 ERROR MESSAGE
|
|
4217
|
+
const ERROR_MSG = `
|
|
4218
|
+
❌ Capacitor SystemBars.java structure changed!
|
|
4219
|
+
|
|
4220
|
+
Plugin: @capacitor/android
|
|
4221
|
+
Path : node_modules\@capacitor\android\capacitor\src\main\java\com\getcapacitor\plugin\SystemBars.java
|
|
4222
|
+
|
|
4223
|
+
Check version of "@capacitor/android": in package.json
|
|
4224
|
+
|
|
4225
|
+
👉 Expected code block not found.
|
|
4226
|
+
|
|
4227
|
+
This usually means Capacitor updated internally.
|
|
4228
|
+
|
|
4229
|
+
Please:
|
|
4230
|
+
1. Open SystemBars.java
|
|
4231
|
+
2. Update patch script
|
|
4232
|
+
3. Re-run build
|
|
4233
|
+
|
|
4234
|
+
⛔ Build stopped.
|
|
4235
|
+
`;
|
|
4236
|
+
|
|
4237
|
+
function patchFile() {
|
|
4238
|
+
if (!fs.existsSync(filePath)) {
|
|
4239
|
+
console.error("❌ SystemBars.java not found!");
|
|
4240
|
+
process.exit(1);
|
|
4241
|
+
}
|
|
4242
|
+
|
|
4243
|
+
const capacitorConfig = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
4244
|
+
if (capacitorConfig.plugins?.SystemBars?.insetsHandling === "disable") {
|
|
4245
|
+
console.log('ℹ️ SystemBars.insetsHandling is "disable"; skipping SystemBars.java keyboard inset patch.');
|
|
4246
|
+
return;
|
|
4247
|
+
}
|
|
4248
|
+
|
|
4249
|
+
let content = fs.readFileSync(filePath, "utf8");
|
|
4250
|
+
|
|
4251
|
+
// ✅ Already patched?
|
|
4252
|
+
if (content.includes(SYSTEM_BARS_PATCH_MARKER) || content.includes("🔴 ORIGINAL CODE (COMMENTED FOR SAFETY)")) {
|
|
4253
|
+
console.log("✅ Already SystemBars.java patched. Skipping...");
|
|
4254
|
+
return;
|
|
4255
|
+
}
|
|
4256
|
+
|
|
4257
|
+
const block = findShouldPassthroughInsetsBlock(content);
|
|
4258
|
+
if (!block) {
|
|
4259
|
+
console.error(ERROR_MSG);
|
|
4260
|
+
process.exit(1);
|
|
4261
|
+
}
|
|
4262
|
+
|
|
4263
|
+
// 🔁 Replace
|
|
4264
|
+
const updated =
|
|
4265
|
+
content.slice(0, block.start) +
|
|
4266
|
+
getSystemBarsPatchedBlock(block.text, content) +
|
|
4267
|
+
content.slice(block.end);
|
|
4268
|
+
|
|
4269
|
+
fs.writeFileSync(filePath, updated, "utf8");
|
|
4270
|
+
|
|
4271
|
+
console.log("✅ SystemBars.java patched with Codeplay keyboard inset fix!");
|
|
4272
|
+
}
|
|
4273
|
+
|
|
4274
|
+
//patchFile();
|
|
4275
|
+
|
|
4276
|
+
//################################## SystemBars.java update for "@capacitor/android": "^8.3.0" plugin END ###############################
|
|
4277
|
+
|
|
4278
|
+
// Android build optimization START
|
|
4279
|
+
function findAndroidBuildBlock(content, name) {
|
|
4280
|
+
// Mask comments and strings without changing offsets or line endings.
|
|
4281
|
+
const masked = content.replace(/\/\*[\s\S]*?\*\/|\/\/[^\r\n]*|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g,
|
|
4282
|
+
value => value.replace(/[^\r\n]/g, ' '));
|
|
4283
|
+
const header = new RegExp(`\\b${name}\\s*\\{`, 'g');
|
|
4284
|
+
let depth = 0;
|
|
4285
|
+
for (let index = 0; index < masked.length; index += 1) {
|
|
4286
|
+
if (depth === 0) {
|
|
4287
|
+
header.lastIndex = index;
|
|
4288
|
+
const match = header.exec(masked);
|
|
4289
|
+
if (match && match.index === index) {
|
|
4290
|
+
const start = header.lastIndex;
|
|
4291
|
+
let blockDepth = 1;
|
|
4292
|
+
for (let end = start; end < masked.length; end += 1) {
|
|
4293
|
+
if (masked[end] === '{') blockDepth += 1;
|
|
4294
|
+
if (masked[end] === '}' && --blockDepth === 0) return { start, end };
|
|
4295
|
+
}
|
|
4296
|
+
throw new Error(`Unclosed Android Gradle ${name} block.`);
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
if (masked[index] === '{') depth += 1;
|
|
4300
|
+
if (masked[index] === '}') depth -= 1;
|
|
4301
|
+
}
|
|
4302
|
+
return null;
|
|
4303
|
+
}
|
|
4304
|
+
|
|
4305
|
+
function syncAndroidBuildOptimization() {
|
|
4306
|
+
if (buildPlatform !== 'android' || !fs.existsSync(androidAppBuildGradlePath)) return;
|
|
4307
|
+
|
|
4308
|
+
const original = fs.readFileSync(androidAppBuildGradlePath, 'utf8');
|
|
4309
|
+
const newline = original.includes('\r\n') ? '\r\n' : '\n';
|
|
4310
|
+
const androidBlock = findAndroidBuildBlock(original, 'android');
|
|
4311
|
+
if (!androidBlock) throw new Error('Android Gradle android block was not found.');
|
|
4312
|
+
let androidContent = original.slice(androidBlock.start, androidBlock.end);
|
|
4313
|
+
const buildTypesBlock = findAndroidBuildBlock(androidContent, 'buildTypes');
|
|
4314
|
+
if (!buildTypesBlock) throw new Error('Android Gradle buildTypes block was not found.');
|
|
4315
|
+
let buildTypesContent = androidContent.slice(buildTypesBlock.start, buildTypesBlock.end);
|
|
4316
|
+
|
|
4317
|
+
// Match the existing finalrelease compatibility exception for this native plugin.
|
|
4318
|
+
const ffmpegPluginXml = path.join(process.cwd(), 'node_modules', 'codeplay-fmpg', 'plugin.xml');
|
|
4319
|
+
const optimizeRelease = !(fs.existsSync(ffmpegPluginXml)
|
|
4320
|
+
&& /<plugin\b/.test(fs.readFileSync(ffmpegPluginXml, 'utf8')));
|
|
4321
|
+
|
|
4322
|
+
for (const name of ['debug', 'release']) {
|
|
4323
|
+
const enabled = name === 'debug' || optimizeRelease;
|
|
4324
|
+
const block = findAndroidBuildBlock(buildTypesContent, name);
|
|
4325
|
+
const blockComments = buildTypesContent.match(/\/\*[\s\S]*?\*\/|\/\/[^\r\n]*|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g) || [];
|
|
4326
|
+
if (!block && blockComments.some(comment => (comment.startsWith('//') || comment.startsWith('/*'))
|
|
4327
|
+
&& new RegExp(`\\b${name}\\s*\\{`).test(comment))) continue;
|
|
4328
|
+
let body = block ? buildTypesContent.slice(block.start, block.end) : `${newline} `;
|
|
4329
|
+
const comments = (body.match(/\/\*[\s\S]*?\*\/|\/\/[^\r\n]*|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g) || [])
|
|
4330
|
+
.filter(value => value.startsWith('//') || value.startsWith('/*'));
|
|
4331
|
+
for (const setting of ['minifyEnabled', 'shrinkResources']) {
|
|
4332
|
+
const masked = body.replace(/\/\*[\s\S]*?\*\/|\/\/[^\r\n]*|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g,
|
|
4333
|
+
value => value.replace(/[^\r\n]/g, ' '));
|
|
4334
|
+
const settingPattern = new RegExp(`\\b${setting}\\b`);
|
|
4335
|
+
// Existing values, expressions and commented opt-outs take precedence.
|
|
4336
|
+
if (settingPattern.test(masked) || comments.some(comment => settingPattern.test(comment))) continue;
|
|
4337
|
+
const value = setting === 'shrinkResources'
|
|
4338
|
+
? enabled && /\bminifyEnabled\s*(?:=\s*)?true\b/.test(masked)
|
|
4339
|
+
: enabled;
|
|
4340
|
+
body = `${newline} ${setting} ${value}${body}`;
|
|
4341
|
+
}
|
|
4342
|
+
const activeBody = body.replace(/\/\*[\s\S]*?\*\/|\/\/[^\r\n]*|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g,
|
|
4343
|
+
value => value.startsWith('//') || value.startsWith('/*') ? value.replace(/[^\r\n]/g, ' ') : value);
|
|
4344
|
+
const defaultRules = [...activeBody.matchAll(/getDefaultProguardFile\(\s*(['"])proguard-android\.txt\1\s*\)/g)];
|
|
4345
|
+
for (const match of defaultRules.reverse()) {
|
|
4346
|
+
body = body.slice(0, match.index) + "getDefaultProguardFile('proguard-android-optimize.txt')"
|
|
4347
|
+
+ body.slice(match.index + match[0].length);
|
|
4348
|
+
}
|
|
4349
|
+
if (!/\bproguardFiles\b/.test(activeBody)
|
|
4350
|
+
&& !comments.some(comment => /\bproguardFiles\b/.test(comment))) {
|
|
4351
|
+
body = `${newline} proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'${body}`;
|
|
4352
|
+
}
|
|
4353
|
+
buildTypesContent = block
|
|
4354
|
+
? buildTypesContent.slice(0, block.start) + body + buildTypesContent.slice(block.end)
|
|
4355
|
+
: `${buildTypesContent.trimEnd()}${newline} ${name} {${body}}${newline} `;
|
|
4356
|
+
}
|
|
4357
|
+
|
|
4358
|
+
androidContent = androidContent.slice(0, buildTypesBlock.start) + buildTypesContent
|
|
4359
|
+
+ androidContent.slice(buildTypesBlock.end);
|
|
4360
|
+
const next = original.slice(0, androidBlock.start) + androidContent + original.slice(androidBlock.end);
|
|
4361
|
+
if (next !== original) fs.writeFileSync(androidAppBuildGradlePath, next, 'utf8');
|
|
4362
|
+
|
|
4363
|
+
const propertiesPath = path.join(androidPlatformPath, 'gradle.properties');
|
|
4364
|
+
const properties = fs.existsSync(propertiesPath) ? fs.readFileSync(propertiesPath, 'utf8') : '';
|
|
4365
|
+
const property = 'android.r8.optimizedResourceShrinking=true';
|
|
4366
|
+
const propertyPattern = /^[ \t]*[#!]?[ \t]*android\.r8\.optimizedResourceShrinking(?:[ \t]*[=:]|[ \t]+|[ \t]*$)/m;
|
|
4367
|
+
const nextProperties = propertyPattern.test(properties)
|
|
4368
|
+
? properties
|
|
4369
|
+
: `${properties.trimEnd()}${newline}${property}${newline}`;
|
|
4370
|
+
if (nextProperties !== properties) fs.writeFileSync(propertiesPath, nextProperties, 'utf8');
|
|
4371
|
+
console.log('✅ Android build optimization defaults checked; existing settings and commented opt-outs preserved.');
|
|
4372
|
+
}
|
|
4373
|
+
// Android build optimization END
|
|
4374
|
+
|
|
4375
|
+
function syncAndroidCordovaResourceKeep() {
|
|
4376
|
+
if (buildPlatform !== 'android' || !fs.existsSync(androidPlatformPath)) return;
|
|
4377
|
+
|
|
4378
|
+
// Cordova loads config.xml by name; preserve it even when no plugins are installed yet.
|
|
4379
|
+
const rawPath = path.join(androidPlatformPath, 'app', 'src', 'main', 'res', 'raw');
|
|
4380
|
+
const keepPath = path.join(rawPath, 'codeplay_cordova_keep.xml');
|
|
4381
|
+
const content = '<?xml version="1.0" encoding="utf-8"?>\n'
|
|
4382
|
+
+ '<resources xmlns:tools="http://schemas.android.com/tools"\n'
|
|
4383
|
+
+ ' tools:keep="@xml/config" />\n';
|
|
4384
|
+
if (fs.existsSync(keepPath) && fs.readFileSync(keepPath, 'utf8') === content) return;
|
|
4385
|
+
|
|
4386
|
+
fs.mkdirSync(rawPath, { recursive: true });
|
|
4387
|
+
fs.writeFileSync(keepPath, content, 'utf8');
|
|
4388
|
+
}
|
|
4389
|
+
|
|
4390
|
+
async function main() {
|
|
4391
|
+
validateThemeFolderLocation();
|
|
4392
|
+
validateAndRestoreSignDetails();
|
|
4393
|
+
ensureMandatoryCapacitorPlugins();
|
|
4394
|
+
|
|
4395
|
+
await loadPluginVersions(); // 🔥 NEW
|
|
4396
|
+
await syncAgentInstructions();
|
|
4397
|
+
|
|
4398
|
+
await syncAdmobNextgenMigration();
|
|
4399
|
+
syncCodeplaySystemBarsPackage();
|
|
4400
|
+
syncSystemBarsSafeAreaMigration();
|
|
4401
|
+
await checkPlugins();
|
|
4402
|
+
ensureIapStartupOnHomePage();
|
|
4403
|
+
syncRevenueCatIapSetup();
|
|
4404
|
+
syncFirebaseAnalyticsSetup();
|
|
4405
|
+
checkAndupdateDropInViteConfig();
|
|
4406
|
+
checkAdmobConfigurationProperty();
|
|
4407
|
+
syncAndroidAdIdPermission();
|
|
4408
|
+
syncAndroidBuildOptimization();
|
|
4409
|
+
syncAndroidCordovaResourceKeep();
|
|
4410
|
+
}
|
|
4411
|
+
|
|
4412
|
+
await main();
|
|
4413
|
+
})().catch(err => {
|
|
4414
|
+
console.error(err?.message || err);
|
|
4415
|
+
process.exit(1);
|
|
4416
|
+
});
|
|
4417
|
+
|
|
4418
|
+
|
|
4419
|
+
|
|
4420
|
+
|
|
4421
|
+
/*
|
|
4422
|
+
Release Notes
|
|
4423
|
+
|
|
4424
|
+
6.5
|
|
4425
|
+
Enable debug/release optimization and optimized resource shrinking before Android builds.
|
|
4426
|
+
Preserve existing values and commented opt-outs without adding duplicate settings.
|
|
4427
|
+
Kids ad configuration disables iOS ATT prompts and excludes Android AD_ID during builds.
|
|
4428
|
+
Non-kids apps retain tracking prompts and normal permission behavior.
|
|
4429
|
+
Ad configuration migration preserves newer local configuration versions.
|
|
4430
|
+
|
|
4431
|
+
5.1
|
|
4432
|
+
Kotlin version update is commented. Previously admob is not worked if not update the kotlin version to higher version
|
|
4433
|
+
|
|
4434
|
+
*/
|