codeplay-common 4.0.5 → 4.0.8

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.
@@ -272,9 +272,61 @@ function compareWithBeta(installedVersion, minVersion, isBeta) {
272
272
 
273
273
 
274
274
 
275
+ const ensureAnalyticsFlagsInConfig = (config) => {
276
+ const analyticsFlagKey = 'ANALYTICS_ENABLED';
277
+ const legacyAnalyticsFlagKey = ['ANALY', 'STICS_ENABLED'].join('');
278
+ let updated = false;
279
+
280
+ if (!config.android) {
281
+ config.android = {};
282
+ updated = true;
283
+ }
284
+
285
+ if (!config.ios) {
286
+ config.ios = {};
287
+ updated = true;
288
+ }
289
+
290
+ const syncPlatformFlag = (platformName, platformConfig) => {
291
+ const existingValue = platformConfig[analyticsFlagKey];
292
+ const legacyValue = platformConfig[legacyAnalyticsFlagKey];
293
+ let nextValue = false;
294
+
295
+ if (typeof existingValue === 'boolean') {
296
+ nextValue = existingValue;
297
+ } else if (typeof legacyValue === 'boolean') {
298
+ nextValue = legacyValue;
299
+ } else if (existingValue !== undefined) {
300
+ console.warn(`⚠️ ${platformName}.ANALYTICS_ENABLED should be true or false. Automatically setting it to false.`);
301
+ }
302
+
303
+ if (platformConfig[analyticsFlagKey] !== nextValue) {
304
+ platformConfig[analyticsFlagKey] = nextValue;
305
+ updated = true;
306
+ console.warn(`⚠️ ${platformName}.ANALYTICS_ENABLED added/updated as ${nextValue}.`);
307
+ }
308
+
309
+ if (legacyAnalyticsFlagKey in platformConfig) {
310
+ delete platformConfig[legacyAnalyticsFlagKey];
311
+ updated = true;
312
+ console.warn(`⚠️ ${platformName}.ANALYTICS_ENABLED spelling corrected.`);
313
+ }
314
+
315
+ if (nextValue === false) {
316
+ console.warn(`⚠️ Google Analytics is disabled for ${platformName}. Mostly recommended to enable it when IAP is available.`);
317
+ }
318
+ };
319
+
320
+ syncPlatformFlag('android', config.android);
321
+ syncPlatformFlag('ios', config.ios);
322
+
323
+ return updated;
324
+ };
325
+
275
326
  const checkAppUniqueId=()=>{
276
327
 
277
328
  const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
329
+ const analyticsFlagsUpdated = ensureAnalyticsFlagsInConfig(config);
278
330
 
279
331
  const appUniqueId = config.android?.APP_UNIQUE_ID;
280
332
  const RESIZEABLE_ACTIVITY = config.android?.RESIZEABLE_ACTIVITY;
@@ -313,14 +365,22 @@ const checkAppUniqueId=()=>{
313
365
  logErrorMessage+='❌ APP_UNIQUE_ID must be an integer. Example: 1, 2, 3, etc.';
314
366
  }
315
367
 
316
-
317
-
318
368
  if(logErrorMessage!="")
319
369
  {
370
+ if (analyticsFlagsUpdated) {
371
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
372
+ console.log('✅ capacitor.config.json analytics flags synced.');
373
+ }
374
+
320
375
  console.error(logErrorMessage);
321
376
  process.exit(1)
322
377
  }
323
378
 
379
+ if (analyticsFlagsUpdated) {
380
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
381
+ console.log('✅ capacitor.config.json analytics flags synced.');
382
+ }
383
+
324
384
 
325
385
  console.log(`✅ APP_UNIQUE_ID is valid: ${appUniqueId}`);
326
386
 
@@ -2520,6 +2580,623 @@ const syncAdmobAliasInViteConfig = () => {
2520
2580
  };
2521
2581
 
2522
2582
 
2583
+ const getViteConfigPath = () => {
2584
+ const possibleFiles = [
2585
+ "vite.config.mjs",
2586
+ "vite.config.js"
2587
+ ];
2588
+
2589
+ return possibleFiles
2590
+ .map(file => path.join(process.cwd(), file))
2591
+ .find(filePath => fs.existsSync(filePath));
2592
+ };
2593
+
2594
+ const getLatestIapDir = () => {
2595
+ const adsDir = path.join(process.cwd(), "src", "js", "Ads");
2596
+
2597
+ if (!fs.existsSync(adsDir)) return null;
2598
+
2599
+ const iapDirs = fs.readdirSync(adsDir, { withFileTypes: true })
2600
+ .filter(entry => entry.isDirectory() && /^IAP-\d+(\.\d+)*$/.test(entry.name))
2601
+ .map(entry => entry.name)
2602
+ .sort((a, b) => {
2603
+ const versionA = a.match(/(\d+(?:\.\d+)*)/)[1].split(".").map(Number);
2604
+ const versionB = b.match(/(\d+(?:\.\d+)*)/)[1].split(".").map(Number);
2605
+
2606
+ for (let i = 0; i < Math.max(versionA.length, versionB.length); i++) {
2607
+ const diff = (versionB[i] || 0) - (versionA[i] || 0);
2608
+ if (diff !== 0) return diff;
2609
+ }
2610
+
2611
+ return 0;
2612
+ });
2613
+
2614
+ return iapDirs.length > 0 ? path.join(adsDir, iapDirs[0]) : null;
2615
+ };
2616
+
2617
+ const getAnalyticsConfig = () => {
2618
+ const config = fs.existsSync(configPath)
2619
+ ? JSON.parse(fs.readFileSync(configPath, "utf8"))
2620
+ : {};
2621
+
2622
+ const analyticsFlagKey = "ANALYTICS_ENABLED";
2623
+ const androidEnabled = config.android?.[analyticsFlagKey] ?? false;
2624
+ const iosEnabled = config.ios?.[analyticsFlagKey] ?? false;
2625
+
2626
+ return {
2627
+ androidEnabled: androidEnabled === true,
2628
+ iosEnabled: iosEnabled === true,
2629
+ enabled: androidEnabled === true || iosEnabled === true
2630
+ };
2631
+ };
2632
+
2633
+ const isPackageAvailable = (packageName) => {
2634
+ if (!fs.existsSync(packageJsonPath)) return false;
2635
+
2636
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
2637
+
2638
+ return Boolean(
2639
+ packageJson.dependencies?.[packageName] ||
2640
+ packageJson.devDependencies?.[packageName] ||
2641
+ fs.existsSync(path.join(process.cwd(), "node_modules", packageName, "package.json"))
2642
+ );
2643
+ };
2644
+
2645
+ const sourceImportsPackage = (packageName, ignoredFilePath) => {
2646
+ const sourceDir = path.join(process.cwd(), "src");
2647
+ if (!fs.existsSync(sourceDir)) return false;
2648
+
2649
+ const escapedPackage = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2650
+ const importRegex = new RegExp(`(?:from\\s*['"]${escapedPackage}(?:/[^'"]*)?['"]|import\\s*['"]${escapedPackage}(?:/[^'"]*)?['"]|import\\s*\\(\\s*['"]${escapedPackage}(?:/[^'"]*)?['"]\\s*\\)|require\\s*\\(\\s*['"]${escapedPackage}(?:/[^'"]*)?['"]\\s*\\))`);
2651
+
2652
+ const scan = (dir) => {
2653
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
2654
+ const fullPath = path.join(dir, entry.name);
2655
+
2656
+ if (ignoredFilePath && path.resolve(fullPath) === path.resolve(ignoredFilePath)) {
2657
+ continue;
2658
+ }
2659
+
2660
+ if (entry.isDirectory()) {
2661
+ if (scan(fullPath)) return true;
2662
+ continue;
2663
+ }
2664
+
2665
+ if (!/\.(js|mjs|cjs|ts|tsx|jsx|f7)$/.test(entry.name)) continue;
2666
+
2667
+ const content = fs.readFileSync(fullPath, "utf8");
2668
+ if (importRegex.test(content)) return true;
2669
+ }
2670
+
2671
+ return false;
2672
+ };
2673
+
2674
+ return scan(sourceDir);
2675
+ };
2676
+
2677
+ const runNpmCommand = (command) => {
2678
+ console.log(`🚀 ${command}`);
2679
+ execSync(command, { stdio: "inherit" });
2680
+ };
2681
+
2682
+ const syncSystemBarsSafeAreaMigration = () => {
2683
+ if (isPackageAvailable("@capacitor-community/safe-area")) {
2684
+ runNpmCommand("npm uninstall @capacitor-community/safe-area");
2685
+ } else {
2686
+ console.log("ℹ️ @capacitor-community/safe-area is not installed.");
2687
+ }
2688
+
2689
+ if (!fs.existsSync(configPath)) {
2690
+ console.warn("⚠️ capacitor.config.json not found. Skipping SystemBars config sync.");
2691
+ return;
2692
+ }
2693
+
2694
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
2695
+ if (!config.plugins) config.plugins = {};
2696
+ if (!config.plugins.SystemBars) config.plugins.SystemBars = {};
2697
+
2698
+ if (config.plugins.SystemBars.insetsHandling !== "css") {
2699
+ config.plugins.SystemBars.insetsHandling = "css";
2700
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
2701
+ console.log('✅ capacitor.config.json SystemBars.insetsHandling set to "css".');
2702
+ } else {
2703
+ console.log('ℹ️ capacitor.config.json SystemBars.insetsHandling already "css".');
2704
+ }
2705
+ };
2706
+
2707
+ const ensureFirebaseAnalyticsWrapper = (analyticsEnabled) => {
2708
+ const analyticsDir = getLatestIapDir();
2709
+ const legacyAnalyticsDir = path.join(process.cwd(), "src", "js", "analytics");
2710
+ const legacyAnalyticsFile = path.join(legacyAnalyticsDir, "firebase-analytics.js");
2711
+
2712
+ if (!analyticsDir) {
2713
+ console.warn("⚠️ No IAP-x.x folder found. Skipping Firebase analytics wrapper sync.");
2714
+ return null;
2715
+ }
2716
+
2717
+ const analyticsFile = path.join(analyticsDir, "firebase-analytics.js");
2718
+
2719
+ if (!fs.existsSync(analyticsDir)) {
2720
+ fs.mkdirSync(analyticsDir, { recursive: true });
2721
+ }
2722
+
2723
+ const activeWrapper = `import { Capacitor, registerPlugin } from '@capacitor/core';
2724
+
2725
+ const FirebaseAnalytics = registerPlugin('FirebaseAnalytics');
2726
+
2727
+ const isEnabledValue = (value) => value === true || value === 'true';
2728
+
2729
+ const canUseNativeAnalytics = () => {
2730
+ if (!Capacitor.isNativePlatform()) return false;
2731
+
2732
+ const platform = Capacitor.getPlatform();
2733
+
2734
+ if (platform === 'android') {
2735
+ return isEnabledValue(import.meta.env.VITE_ANDROID_ANALYTICS_ENABLED);
2736
+ }
2737
+
2738
+ if (platform === 'ios') {
2739
+ return isEnabledValue(import.meta.env.VITE_IOS_ANALYTICS_ENABLED);
2740
+ }
2741
+
2742
+ return false;
2743
+ };
2744
+
2745
+ const cleanParams = (params = {}) => {
2746
+ return Object.entries(params).reduce((result, [key, value]) => {
2747
+ if (value === undefined || value === null || value === '') return result;
2748
+ if (typeof value === 'number' && Number.isNaN(value)) return result;
2749
+
2750
+ result[key] = value;
2751
+ return result;
2752
+ }, {});
2753
+ };
2754
+
2755
+ const trackEvent = async (name, params = {}) => {
2756
+ if (!canUseNativeAnalytics()) return;
2757
+
2758
+ try {
2759
+ await FirebaseAnalytics.logEvent({
2760
+ name,
2761
+ params: cleanParams(params),
2762
+ });
2763
+ } catch (err) {
2764
+ console.warn(\`Firebase analytics event failed: \${name}\`, err);
2765
+ }
2766
+ };
2767
+
2768
+ export const trackSubscriptionScreenViewed = (params) => {
2769
+ trackEvent('subscription_screen_view', params);
2770
+ };
2771
+
2772
+ export const trackSubscriptionCheckoutStarted = (params) => {
2773
+ trackEvent('subscription_checkout_start', params);
2774
+ };
2775
+
2776
+ export const trackSubscriptionPurchased = (params) => {
2777
+ trackEvent('subscription_purchase', params);
2778
+ };
2779
+
2780
+ export const trackConsumablePurchased = (params) => {
2781
+ trackEvent('consumable_purchase', params);
2782
+ };
2783
+ `;
2784
+
2785
+ const noopWrapper = `const trackEvent = () => {};
2786
+
2787
+ export const trackSubscriptionScreenViewed = trackEvent;
2788
+ export const trackSubscriptionCheckoutStarted = trackEvent;
2789
+ export const trackSubscriptionPurchased = trackEvent;
2790
+ export const trackConsumablePurchased = trackEvent;
2791
+ `;
2792
+
2793
+ const nextContent = analyticsEnabled ? activeWrapper : noopWrapper;
2794
+ const currentContent = fs.existsSync(analyticsFile) ? fs.readFileSync(analyticsFile, "utf8") : "";
2795
+
2796
+ if (currentContent !== nextContent) {
2797
+ fs.writeFileSync(analyticsFile, nextContent, "utf8");
2798
+ console.log(`✅ Firebase analytics wrapper ${analyticsEnabled ? "enabled" : "disabled"}: ${path.relative(process.cwd(), analyticsFile)}`);
2799
+ } else {
2800
+ console.log(`ℹ️ Firebase analytics wrapper already ${analyticsEnabled ? "enabled" : "disabled"}.`);
2801
+ }
2802
+
2803
+ if (fs.existsSync(legacyAnalyticsFile)) {
2804
+ fs.unlinkSync(legacyAnalyticsFile);
2805
+ console.log("✅ Removed old Firebase analytics wrapper: src/js/analytics/firebase-analytics.js");
2806
+ }
2807
+
2808
+ if (fs.existsSync(legacyAnalyticsDir) && fs.readdirSync(legacyAnalyticsDir).length === 0) {
2809
+ fs.rmdirSync(legacyAnalyticsDir);
2810
+ }
2811
+
2812
+ return analyticsFile;
2813
+ };
2814
+
2815
+ const syncFirebaseAnalyticsPackages = () => {
2816
+ const analyticsConfig = getAnalyticsConfig();
2817
+
2818
+ if (analyticsConfig.enabled) {
2819
+ if (!isPackageAvailable("@capacitor-firebase/analytics") || !isPackageAvailable("firebase")) {
2820
+ runNpmCommand("npm install @capacitor-firebase/analytics firebase");
2821
+ } else {
2822
+ console.log("ℹ️ Firebase Analytics packages already installed.");
2823
+ }
2824
+
2825
+ ensureFirebaseAnalyticsWrapper(true);
2826
+ return;
2827
+ }
2828
+
2829
+ const analyticsFile = ensureFirebaseAnalyticsWrapper(false);
2830
+
2831
+ if (isPackageAvailable("@capacitor-firebase/analytics")) {
2832
+ runNpmCommand("npm uninstall @capacitor-firebase/analytics");
2833
+ } else {
2834
+ console.log("ℹ️ @capacitor-firebase/analytics is not installed.");
2835
+ }
2836
+
2837
+ if (isPackageAvailable("firebase")) {
2838
+ if (sourceImportsPackage("firebase", analyticsFile)) {
2839
+ console.log("ℹ️ Keeping firebase package because other source files import it.");
2840
+ } else {
2841
+ runNpmCommand("npm uninstall firebase");
2842
+ }
2843
+ }
2844
+ };
2845
+
2846
+ const syncFirebaseAnalyticsPodfile = () => {
2847
+ const podfilePath = path.join(process.cwd(), "ios", "App", "Podfile");
2848
+ if (!fs.existsSync(podfilePath)) return;
2849
+
2850
+ const { iosEnabled } = getAnalyticsConfig();
2851
+ const basePodLine = " pod 'CapacitorFirebaseAnalytics', :path => '../../node_modules/@capacitor-firebase/analytics'";
2852
+ const analyticsPodLine = " pod 'CapacitorFirebaseAnalytics/Analytics', :path => '../../node_modules/@capacitor-firebase/analytics'";
2853
+
2854
+ let content = fs.readFileSync(podfilePath, "utf8");
2855
+ let nextContent = content;
2856
+
2857
+ if (iosEnabled) {
2858
+ if (!nextContent.includes(basePodLine)) {
2859
+ nextContent = nextContent.replace(
2860
+ /(pod 'CapacitorCommunitySafeArea'[^\n]*\r?\n)/,
2861
+ `$1${basePodLine}\n`
2862
+ );
2863
+ }
2864
+
2865
+ if (!nextContent.includes(analyticsPodLine)) {
2866
+ nextContent = nextContent.replace(
2867
+ /(# Add your Pods here\r?\n)/,
2868
+ `$1${analyticsPodLine}\n`
2869
+ );
2870
+ }
2871
+ } else {
2872
+ nextContent = nextContent
2873
+ .split(/\r?\n/)
2874
+ .filter(line => !line.includes("CapacitorFirebaseAnalytics"))
2875
+ .join("\n");
2876
+ }
2877
+
2878
+ if (nextContent !== content) {
2879
+ fs.writeFileSync(podfilePath, nextContent, "utf8");
2880
+ console.log(`✅ iOS Firebase Analytics Podfile entries ${iosEnabled ? "enabled" : "removed"}.`);
2881
+ } else {
2882
+ console.log(`ℹ️ iOS Firebase Analytics Podfile entries already ${iosEnabled ? "enabled" : "disabled"}.`);
2883
+ }
2884
+ };
2885
+
2886
+ const ensureImportOrRequire = (content, moduleName, esmLine, cjsLine) => {
2887
+ const escaped = moduleName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2888
+ if (new RegExp(`from\\s*['"]${escaped}['"]|require\\s*\\(\\s*['"]${escaped}['"]\\s*\\)`).test(content)) {
2889
+ return content;
2890
+ }
2891
+
2892
+ const isCommonJs = /module\.exports|require\s*\(/.test(content);
2893
+ const line = isCommonJs ? cjsLine : esmLine;
2894
+
2895
+ return `${line}\n${content}`;
2896
+ };
2897
+
2898
+ const ensureViteDefineEntry = (viteContent, key, valueExpression) => {
2899
+ const entry = ` '${key}': JSON.stringify(${valueExpression}),`;
2900
+
2901
+ if (viteContent.includes(`'${key}'`) || viteContent.includes(`"${key}"`)) {
2902
+ return viteContent;
2903
+ }
2904
+
2905
+ if (/define\s*:\s*{/.test(viteContent)) {
2906
+ return viteContent.replace(/define\s*:\s*{\s*/, match => `${match}\n${entry}\n`);
2907
+ }
2908
+
2909
+ const defineBlock = ` define: {
2910
+ '${key}': JSON.stringify(${valueExpression}),
2911
+ },`;
2912
+
2913
+ if (/\n\s*publicDir\s*:\s*[^,\n]+,?/.test(viteContent)) {
2914
+ return viteContent.replace(
2915
+ /(\n\s*publicDir\s*:\s*[^,\n]+,?)/,
2916
+ `$1\n${defineBlock}`
2917
+ );
2918
+ }
2919
+
2920
+ return viteContent.replace(
2921
+ /(return\s+{)/,
2922
+ `$1\n${defineBlock}`
2923
+ );
2924
+ };
2925
+
2926
+ const ensurePrivacyAndAnalyticsViteConfig = () => {
2927
+ const viteConfigPath = getViteConfigPath();
2928
+
2929
+ if (!viteConfigPath) {
2930
+ console.warn("⚠️ No vite config found. Skipping privacy/analytics Vite sync.");
2931
+ return;
2932
+ }
2933
+
2934
+ let viteContent = fs.readFileSync(viteConfigPath, "utf8");
2935
+ const originalContent = viteContent;
2936
+
2937
+ viteContent = ensureImportOrRequire(
2938
+ viteContent,
2939
+ "fs",
2940
+ "import fs from 'fs';",
2941
+ "const fs = require('fs');"
2942
+ );
2943
+
2944
+ viteContent = ensureImportOrRequire(
2945
+ viteContent,
2946
+ "path",
2947
+ "import path from 'path';",
2948
+ "const path = require('path');"
2949
+ );
2950
+
2951
+ if (!/\bcapacitorConfig\b/.test(viteContent)) {
2952
+ viteContent = viteContent.replace(
2953
+ /(\r?\nexport\s+default|\r?\nmodule\.exports)/,
2954
+ `\nconst capacitorConfig = JSON.parse(fs.readFileSync(path.resolve(__dirname, './capacitor.config.json'), 'utf8'));\n$1`
2955
+ );
2956
+ }
2957
+
2958
+ if (!/\bAPP_UNIQUE_ID\b/.test(viteContent)) {
2959
+ viteContent = viteContent.replace(
2960
+ /(const capacitorConfig = JSON\.parse\(fs\.readFileSync\(path\.resolve\(__dirname,\s*['"]\.\/capacitor\.config\.json['"]\),\s*['"]utf8['"]\)\);\r?\n)/,
2961
+ `$1const APP_UNIQUE_ID = capacitorConfig.android?.APP_UNIQUE_ID ?? '';\n`
2962
+ );
2963
+ }
2964
+
2965
+ if (!/\bANDROID_ANALYTICS_ENABLED\b/.test(viteContent)) {
2966
+ viteContent = viteContent.replace(
2967
+ /(const APP_UNIQUE_ID = capacitorConfig\.android\?\.APP_UNIQUE_ID \?\? ['"]['"];\r?\n)/,
2968
+ `$1const ANDROID_ANALYTICS_ENABLED = capacitorConfig.android?.ANALYTICS_ENABLED ?? false;\n`
2969
+ );
2970
+ }
2971
+
2972
+ if (!/\bIOS_ANALYTICS_ENABLED\b/.test(viteContent)) {
2973
+ viteContent = viteContent.replace(
2974
+ /(const ANDROID_ANALYTICS_ENABLED = capacitorConfig\.android\?\.ANALYTICS_ENABLED \?\? false;\r?\n)/,
2975
+ `$1const IOS_ANALYTICS_ENABLED = capacitorConfig.ios?.ANALYTICS_ENABLED ?? false;\n`
2976
+ );
2977
+ }
2978
+
2979
+ if (!/\bIAP_DESIGN\b/.test(viteContent)) {
2980
+ viteContent = viteContent.replace(
2981
+ /(const IOS_ANALYTICS_ENABLED = capacitorConfig\.ios\?\.ANALYTICS_ENABLED \?\? false;\r?\n)/,
2982
+ `$1const IAP_DESIGN = capacitorConfig["IAP-Design"] ?? 'default';\n`
2983
+ );
2984
+ }
2985
+
2986
+ viteContent = viteContent
2987
+ .replace(
2988
+ /const ANDROID_ANALYTICS_ENABLED = capacitorConfig\.android\?\.ANALY[ST]ICS_ENABLED \?\? capacitorConfig\.android\?\.ANALYTICS_ENABLED \?\? false;/g,
2989
+ "const ANDROID_ANALYTICS_ENABLED = capacitorConfig.android?.ANALYTICS_ENABLED ?? false;"
2990
+ )
2991
+ .replace(
2992
+ /const IOS_ANALYTICS_ENABLED = capacitorConfig\.ios\?\.ANALY[ST]ICS_ENABLED \?\? capacitorConfig\.ios\?\.ANALYTICS_ENABLED \?\? false;/g,
2993
+ "const IOS_ANALYTICS_ENABLED = capacitorConfig.ios?.ANALYTICS_ENABLED ?? false;"
2994
+ );
2995
+
2996
+ viteContent = ensureViteDefineEntry(viteContent, "import.meta.env.VITE_APP_UNIQUE_ID", "APP_UNIQUE_ID");
2997
+ viteContent = ensureViteDefineEntry(viteContent, "import.meta.env.VITE_ANDROID_ANALYTICS_ENABLED", "ANDROID_ANALYTICS_ENABLED");
2998
+ viteContent = ensureViteDefineEntry(viteContent, "import.meta.env.VITE_IOS_ANALYTICS_ENABLED", "IOS_ANALYTICS_ENABLED");
2999
+ viteContent = ensureViteDefineEntry(viteContent, "import.meta.env.VITE_IAP_DESIGN", "IAP_DESIGN");
3000
+
3001
+ if (viteContent !== originalContent) {
3002
+ fs.writeFileSync(viteConfigPath, viteContent, "utf8");
3003
+ console.log("✅ vite.config.(m)js privacy/analytics settings synced.");
3004
+ } else {
3005
+ console.log("ℹ️ vite.config.(m)js privacy/analytics settings already synced.");
3006
+ }
3007
+ };
3008
+
3009
+ const syncFirebaseAnalyticsSetup = () => {
3010
+ syncFirebaseAnalyticsPackages();
3011
+ syncFirebaseAnalyticsPodfile();
3012
+ ensurePrivacyAndAnalyticsViteConfig();
3013
+ };
3014
+
3015
+ const getAdmobIapConfigPath = () => {
3016
+ const possiblePaths = [
3017
+ path.join(process.cwd(), 'src', 'js', 'Ads', 'admob-ad-configuration-nextgen.json'),
3018
+ path.join(process.cwd(), 'src', 'js', 'Ads', 'admob-ad-configuration.json')
3019
+ ];
3020
+
3021
+ return possiblePaths.find(fileExists);
3022
+ };
3023
+
3024
+ const getAdmobIapConfig = () => {
3025
+ const admobIapConfigPath = getAdmobIapConfigPath();
3026
+
3027
+ if (!admobIapConfigPath) {
3028
+ console.warn("⚠️ No AdMob configuration JSON found. Skipping RevenueCat IAP package sync.");
3029
+ return null;
3030
+ }
3031
+
3032
+ try {
3033
+ return JSON.parse(fs.readFileSync(admobIapConfigPath, "utf8"));
3034
+ } catch (err) {
3035
+ console.error(`❌ Failed to read ${path.relative(process.cwd(), admobIapConfigPath)}`, err);
3036
+ process.exit(1);
3037
+ }
3038
+ };
3039
+
3040
+ const isAnyIapStoreEnabled = (iapConfig) => {
3041
+ return Boolean(
3042
+ iapConfig?.playstore === true ||
3043
+ iapConfig?.samsung === true ||
3044
+ iapConfig?.amazon === true ||
3045
+ iapConfig?.ios === true
3046
+ );
3047
+ };
3048
+
3049
+ const ensureIapDesignInCapacitorConfig = (iapConfig) => {
3050
+ if (!isAnyIapStoreEnabled(iapConfig)) return;
3051
+
3052
+ if (!fs.existsSync(configPath)) {
3053
+ console.warn("⚠️ capacitor.config.json not found. Skipping IAP-Design sync.");
3054
+ return;
3055
+ }
3056
+
3057
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
3058
+
3059
+ if (Object.prototype.hasOwnProperty.call(config, "IAP-Design")) {
3060
+ console.log("ℹ️ capacitor.config.json IAP-Design already configured.");
3061
+ return;
3062
+ }
3063
+
3064
+ config["IAP-Design"] = "default";
3065
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
3066
+ console.log('✅ Added "IAP-Design": "default" to capacitor.config.json.');
3067
+ };
3068
+
3069
+ const getCurrentStoreId = () => {
3070
+ const envStoreId = Number(process.env.VITE_STORE_ID);
3071
+ if (Number.isInteger(envStoreId) && envStoreId > 0) return envStoreId;
3072
+
3073
+ const viteConfigPath = getViteConfigPath();
3074
+ if (!viteConfigPath) return 1;
3075
+
3076
+ const viteConfigContent = fs.readFileSync(viteConfigPath, "utf8");
3077
+ const commonAliasMatch = viteConfigContent.match(/['"]@common['"]:\s*path\.resolve\(__dirname,\s*['"]([^'"]+)['"]\)/);
3078
+
3079
+ if (!commonAliasMatch) return 1;
3080
+
3081
+ const commonFilePath = path.resolve(path.dirname(viteConfigPath), commonAliasMatch[1]);
3082
+ if (!fs.existsSync(commonFilePath)) return 1;
3083
+
3084
+ const commonFileContent = fs.readFileSync(commonFilePath, "utf8");
3085
+ const storeIdMatch = commonFileContent.match(/export\s+let\s+_storeid\s*=\s*import\.meta\.env\.VITE_STORE_ID\s*\|\|\s*(\d+)\s*;/);
3086
+
3087
+ return storeIdMatch ? Number(storeIdMatch[1]) : 1;
3088
+ };
3089
+
3090
+ const getRevenueCatStoreState = (iapConfig) => {
3091
+ const storeId = getCurrentStoreId();
3092
+ const configuration = iapConfig.configuration || {};
3093
+ const storeMap = {
3094
+ 1: {
3095
+ name: "PlayStore",
3096
+ enabled: iapConfig.playstore === true,
3097
+ apiKeyName: "IAP.configuration.playstore",
3098
+ apiKey: configuration.playstore,
3099
+ usesGoogleBilling: true
3100
+ },
3101
+ 6: {
3102
+ name: "iOSStore",
3103
+ enabled: iapConfig.ios === true,
3104
+ apiKeyName: "IAP.configuration.ios",
3105
+ apiKey: configuration.ios,
3106
+ usesGoogleBilling: false
3107
+ },
3108
+ 7: {
3109
+ name: "AmazonStore",
3110
+ enabled: iapConfig.amazon === true,
3111
+ apiKeyName: "IAP.configuration.amazon",
3112
+ apiKey: configuration.amazon,
3113
+ usesGoogleBilling: false
3114
+ }
3115
+ };
3116
+
3117
+ return {
3118
+ storeId,
3119
+ store: storeMap[storeId] || null
3120
+ };
3121
+ };
3122
+
3123
+ const ensureAndroidPermission = (permission, enabled) => {
3124
+ if (!fs.existsSync(androidManifestPath)) return false;
3125
+
3126
+ let manifestContent = fs.readFileSync(androidManifestPath, "utf8");
3127
+ const permissionLine = ` <uses-permission android:name="${permission}" />`;
3128
+ const permissionRegex = new RegExp(`^\\s*<uses-permission\\s+android:name="${permission.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"\\s*/?>\\s*\\r?\\n?`, "m");
3129
+
3130
+ if (enabled) {
3131
+ if (permissionRegex.test(manifestContent)) return false;
3132
+
3133
+ manifestContent = manifestContent.replace(
3134
+ /<\/manifest>\s*$/,
3135
+ `${permissionLine}\n</manifest>`
3136
+ );
3137
+ fs.writeFileSync(androidManifestPath, manifestContent, "utf8");
3138
+ console.log(`✅ Added Android permission: ${permission}`);
3139
+ return true;
3140
+ }
3141
+
3142
+ if (!permissionRegex.test(manifestContent)) return false;
3143
+
3144
+ manifestContent = manifestContent.replace(permissionRegex, "");
3145
+ fs.writeFileSync(androidManifestPath, manifestContent, "utf8");
3146
+ console.log(`✅ Removed Android permission: ${permission}`);
3147
+ return true;
3148
+ };
3149
+
3150
+ const syncRevenueCatIapSetup = () => {
3151
+ const admobConfig = getAdmobIapConfig();
3152
+ const iapConfig = admobConfig?.IAP;
3153
+
3154
+ if (!iapConfig) {
3155
+ console.log("ℹ️ IAP configuration not found. Skipping RevenueCat package sync.");
3156
+ return;
3157
+ }
3158
+
3159
+ ensureIapDesignInCapacitorConfig(iapConfig);
3160
+
3161
+ const revenueCatEnabled =
3162
+ iapConfig.playstore === true ||
3163
+ iapConfig.ios === true ||
3164
+ iapConfig.amazon === true;
3165
+ const { storeId, store } = getRevenueCatStoreState(iapConfig);
3166
+ const revenueCatEnabledForCurrentStore = store?.enabled === true;
3167
+
3168
+ if (revenueCatEnabledForCurrentStore && !store.apiKey) {
3169
+ console.error(`❌ RevenueCat IAP is enabled for ${store.name} but ${store.apiKeyName} is missing.`);
3170
+ process.exit(1);
3171
+ }
3172
+
3173
+ let packageChanged = false;
3174
+
3175
+ if (revenueCatEnabledForCurrentStore) {
3176
+ if (!isPackageAvailable("@revenuecat/purchases-capacitor")) {
3177
+ runNpmCommand("npm install @revenuecat/purchases-capacitor");
3178
+ packageChanged = true;
3179
+ } else {
3180
+ console.log(`ℹ️ RevenueCat package already installed for ${store.name}.`);
3181
+ }
3182
+ } else if (isPackageAvailable("@revenuecat/purchases-capacitor")) {
3183
+ runNpmCommand("npm uninstall @revenuecat/purchases-capacitor");
3184
+ packageChanged = true;
3185
+ } else {
3186
+ console.log(`ℹ️ RevenueCat package is not required for store id ${storeId}.`);
3187
+ }
3188
+
3189
+ ensureAndroidPermission("com.android.vending.BILLING", store?.usesGoogleBilling === true && revenueCatEnabledForCurrentStore);
3190
+
3191
+ if (iapConfig.samsung === true && revenueCatEnabled) {
3192
+ console.warn("⚠️ Samsung IAP uses cordova-plugin-samsungiap; PlayStore/Amazon/iOS use RevenueCat.");
3193
+ }
3194
+
3195
+ if (packageChanged) {
3196
+ runNpmCommand("npx cap sync");
3197
+ }
3198
+ };
3199
+
2523
3200
 
2524
3201
 
2525
3202
 
@@ -2692,11 +3369,14 @@ ensureGitignoreEntry('buildCodeplay/');
2692
3369
 
2693
3370
 
2694
3371
  // Run the validation
2695
- (async () => {
2696
-
2697
- await loadPluginVersions(); // 🔥 NEW
2698
-
2699
- await checkPlugins();
3372
+ (async () => {
3373
+
3374
+ await loadPluginVersions(); // 🔥 NEW
3375
+
3376
+ syncSystemBarsSafeAreaMigration();
3377
+ await checkPlugins();
3378
+ syncRevenueCatIapSetup();
3379
+ syncFirebaseAnalyticsSetup();
2700
3380
  checkAndupdateDropInViteConfig();
2701
3381
  syncAdmobAliasInViteConfig();
2702
3382
  checkAdmobConfigurationProperty()
@@ -2790,45 +3470,45 @@ const filePath = path.join(
2790
3470
  "../node_modules/@capacitor/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java"
2791
3471
  );
2792
3472
 
2793
- // 🔍 OLD BLOCK (anchor)
2794
- const OLD_BLOCK = `if (shouldPassthroughInsets) {
2795
- // We need to correct for a possible shown IME
2796
- v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0);
3473
+ const SYSTEM_BARS_PATCH_MARKER = "CODEPLAY_KEYBOARD_INSET_FIX";
3474
+
3475
+ function getSystemBarsPatchedBlock(originalBlock, content) {
3476
+ const isCapacitor842OrNewer = content.includes("private String insetsHandling = INSETS_HANDLING_CSS;");
3477
+ const safeAreaCondition = isCapacitor842OrNewer
3478
+ ? "INSETS_HANDLING_CSS.equals(insetsHandling)"
3479
+ : "Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled";
3480
+
3481
+ return `if (shouldPassthroughInsets) {
3482
+ /* ${SYSTEM_BARS_PATCH_MARKER}
3483
+ ${originalBlock.split("\n").map(line => " " + line).join("\n")}
3484
+ */
3485
+
3486
+ // Codeplay fix: avoid adding IME height as WebView parent padding.
3487
+ v.setPadding(0, 0, 0, 0);
2797
3488
 
2798
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) {
3489
+ if (${safeAreaCondition}) {
2799
3490
  Insets safeAreaInsets = calcSafeAreaInsets(insets);
2800
3491
  injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
2801
3492
  }
2802
3493
 
2803
- return new WindowInsetsCompat.Builder(insets)
2804
- .setInsets(
2805
- WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(),
2806
- Insets.of(
2807
- systemBarsInsets.left,
2808
- systemBarsInsets.top,
2809
- systemBarsInsets.right,
2810
- getBottomInset(systemBarsInsets, keyboardVisible)
2811
- )
2812
- )
2813
- .build();
3494
+ return insets; // WebView handles keyboard and safe-area sizing.
2814
3495
  }`;
3496
+ }
2815
3497
 
2816
- // NEW BLOCK
2817
- const NEW_BLOCK = `if (shouldPassthroughInsets) {
2818
- /* 🔴 ORIGINAL CODE (COMMENTED FOR SAFETY)
2819
- ${OLD_BLOCK.split("\n").map(line => " " + line).join("\n")}
2820
- */
2821
-
2822
- // ✅ NEW LOGIC (CUSTOM FIX)
2823
- v.setPadding(0, 0, 0, 0);
3498
+ function findShouldPassthroughInsetsBlock(content) {
3499
+ const start = content.indexOf(" if (shouldPassthroughInsets) {");
3500
+ if (start === -1) return null;
2824
3501
 
2825
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) {
2826
- Insets safeAreaInsets = calcSafeAreaInsets(insets);
2827
- injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left);
2828
- }
3502
+ const endMarker = "\n\n if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM)";
3503
+ const end = content.indexOf(endMarker, start);
3504
+ if (end === -1) return null;
2829
3505
 
2830
- return insets; // WebView handles everything
2831
- }`;
3506
+ return {
3507
+ start,
3508
+ end,
3509
+ text: content.slice(start, end)
3510
+ };
3511
+ }
2832
3512
 
2833
3513
  // 🚨 ERROR MESSAGE
2834
3514
  const ERROR_MSG = `
@@ -2857,30 +3537,38 @@ function patchFile() {
2857
3537
  process.exit(1);
2858
3538
  }
2859
3539
 
3540
+ const capacitorConfig = JSON.parse(fs.readFileSync(configPath, "utf8"));
3541
+ if (capacitorConfig.plugins?.SystemBars?.insetsHandling === "disable") {
3542
+ console.log('ℹ️ SystemBars.insetsHandling is "disable"; skipping SystemBars.java keyboard inset patch.');
3543
+ return;
3544
+ }
3545
+
2860
3546
  let content = fs.readFileSync(filePath, "utf8");
2861
3547
 
2862
3548
  // ✅ Already patched?
2863
- if (content.includes("🔴 ORIGINAL CODE (COMMENTED FOR SAFETY)")) {
3549
+ if (content.includes(SYSTEM_BARS_PATCH_MARKER) || content.includes("🔴 ORIGINAL CODE (COMMENTED FOR SAFETY)")) {
2864
3550
  console.log("✅ Already SystemBars.java patched. Skipping...");
2865
3551
  return;
2866
3552
  }
2867
3553
 
2868
- // 🔍 Check old block exists
2869
- if (!content.includes(OLD_BLOCK)) {
3554
+ const block = findShouldPassthroughInsetsBlock(content);
3555
+ if (!block) {
2870
3556
  console.error(ERROR_MSG);
2871
3557
  process.exit(1);
2872
3558
  }
2873
3559
 
2874
3560
  // 🔁 Replace
2875
- const updated = content.replace(OLD_BLOCK, NEW_BLOCK);
3561
+ const updated =
3562
+ content.slice(0, block.start) +
3563
+ getSystemBarsPatchedBlock(block.text, content) +
3564
+ content.slice(block.end);
2876
3565
 
2877
3566
  fs.writeFileSync(filePath, updated, "utf8");
2878
3567
 
2879
- console.log("✅ SystemBars.java patched (comment + new logic)!");
3568
+ console.log("✅ SystemBars.java patched with Codeplay keyboard inset fix!");
2880
3569
  }
2881
3570
 
2882
3571
  //patchFile();
2883
- //console.log("SystemBars.java Not Patched, due to upgrade from @capacitor/android:8.4.2. We need to validate if any problem related to the systembar issue. Note: For this we have disable the 'patchFile()' method");
2884
3572
 
2885
3573
  //################################## SystemBars.java update for "@capacitor/android": "^8.3.0" plugin END ###############################
2886
3574