codeplay-common 4.7.9 → 4.8.0

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.
@@ -44,15 +44,15 @@ const ONESIGNAL_PLUGIN_NAME = "onesignal-cordova-plugin";
44
44
  const ADMOB_NEXTGEN_PLUGIN_NAME = "admob-emi-nextgen";
45
45
  const ADMOB_NEXTGEN_FILE_PATTERN = new RegExp(`^${ADMOB_NEXTGEN_PLUGIN_NAME}-\\d+(?:\\.\\d+)*\\.js$`);
46
46
  const ADMOB_NEXTGEN_CONFIG = "admob-ad-configuration-nextgen.json";
47
- const ADMOB_OLD_CONFIG = "admob-ad-configuration.json";
48
- const AGENT_INSTRUCTIONS_VERSION_KEY = "agents";
49
- const AGENT_INSTRUCTIONS_ZIP_PREFIX = "agents";
50
- const AGENT_INSTRUCTIONS_FILE = "AGENTS.md";
51
- const AGENT_INSTRUCTIONS_DIR = "AGENTS";
47
+ const ADMOB_OLD_CONFIG = "admob-ad-configuration.json";
48
+ const AGENT_INSTRUCTIONS_VERSION_KEY = "agents";
49
+ const AGENT_INSTRUCTIONS_ZIP_PREFIX = "agents";
50
+ const AGENT_INSTRUCTIONS_FILE = "AGENTS.md";
51
+ const AGENT_INSTRUCTIONS_DIR = "AGENTS";
52
52
  const AGENT_INSTRUCTIONS_VERSION_FILE = ".codeplay-version";
53
53
 
54
54
  // Remove the retired plugin before any validation, patching or package updates.
55
- removeOneSignalPlugin();
55
+ removeOneSignalPlugin();
56
56
 
57
57
  const parseVersionParts = (version) => String(version || "0")
58
58
  .trim()
@@ -531,107 +531,107 @@ checkAppUniqueId();
531
531
 
532
532
 
533
533
 
534
- // ======================================================
535
- // ENSURE IAP STARTS FROM THE HOME PAGE
536
- // ======================================================
537
-
538
- const getHomePagePath = () => {
539
- const pagesDir = path.join(ROOT_DIR, "pages");
540
- if (!fs.existsSync(pagesDir)) return null;
541
-
542
- const exactHomePath = path.join(pagesDir, "home.f7");
543
- if (fs.existsSync(exactHomePath)) return exactHomePath;
544
-
545
- const pageFiles = fs.readdirSync(pagesDir)
546
- .filter(fileName => fileName.endsWith(".f7"));
547
-
548
- return pageFiles
549
- .map(fileName => path.join(pagesDir, fileName))
550
- .find(filePath => /data-name\s*=\s*['"]home['"]/.test(fs.readFileSync(filePath, "utf8"))) || null;
551
- };
552
-
553
- const getRelativeModuleImport = (sourceFile, targetFile) => {
554
- let relativePath = path.relative(path.dirname(sourceFile), targetFile).replace(/\\/g, "/");
555
- if (!relativePath.startsWith(".")) relativePath = `./${relativePath}`;
556
- return relativePath;
557
- };
558
-
559
- const addInitIapToExistingImport = (content) => {
560
- const iapImportRegex = /import\s*\{([^}]*)\}\s*from\s*(['"])([^'"]*\/IAP-\d+(?:\.\d+)*\/IAP-check-And-LoadAd\.js)\2\s*;?/;
561
- const match = content.match(iapImportRegex);
562
- if (!match || /\binitIAP\b/.test(match[1])) return content;
563
-
564
- const importNames = match[1].trim();
565
- const nextImportNames = importNames ? `initIAP, ${importNames}` : "initIAP";
566
- return content.replace(iapImportRegex, `import { ${nextImportNames} } from ${match[2]}${match[3]}${match[2]};`);
567
- };
568
-
569
- const addInitIapCall = (content) => {
570
- if (/\binitIAP\s*\(/.test(content)) return content;
571
-
572
- const pageInitRegex = /(\$on\(\s*(['"])pageInit\2\s*,\s*(?:async\s*)?\(\)\s*=>\s*\{\s*\r?\n)/;
573
- if (pageInitRegex.test(content)) {
574
- return content.replace(pageInitRegex, "$1 initIAP();\n");
575
- }
576
-
577
- const componentStartRegex = /(export\s+default\s*\([^)]*\)\s*=>\s*\{\s*\r?\n)/;
578
- if (componentStartRegex.test(content)) {
579
- return content.replace(componentStartRegex, "$1 initIAP();\n");
580
- }
581
-
582
- throw new Error("Unable to find a Framework7 component or pageInit lifecycle for IAP startup.");
583
- };
584
-
585
- const ensureIapStartupOnHomePage = () => {
586
- const iapConfig = getAdmobIapConfig()?.IAP;
587
- if (!isAnyIapStoreEnabled(iapConfig)) {
588
- console.log("ℹ️ IAP is disabled for every store. AdMob will use direct startup.");
589
- return;
590
- }
591
-
592
- const latestIapDir = getLatestIapDir();
593
- if (!latestIapDir) {
594
- throw new Error("IAP is enabled but no IAP-x.x folder was found.");
595
- }
596
-
597
- const homePagePath = getHomePagePath();
598
- if (!homePagePath) {
599
- throw new Error("IAP is enabled but a Framework7 Home page could not be found in src/pages.");
600
- }
601
-
602
- const iapModulePath = path.join(latestIapDir, "IAP-check-And-LoadAd.js");
603
- if (!fs.existsSync(iapModulePath)) {
604
- throw new Error(`IAP startup module is missing: ${iapModulePath}`);
605
- }
606
-
607
- let content = fs.readFileSync(homePagePath, "utf8");
608
- const originalContent = content;
609
- const staticInitImportRegex = /import\s*\{[^}]*\binitIAP\b[^}]*\}\s*from\s*['"][^'"]*\/IAP-\d+(?:\.\d+)*\/IAP-check-And-LoadAd\.js['"]\s*;?/;
610
-
611
- content = addInitIapToExistingImport(content);
612
-
613
- if (!staticInitImportRegex.test(content)) {
614
- const relativeImport = getRelativeModuleImport(homePagePath, iapModulePath);
615
- const scriptStartRegex = /<script>\s*\r?\n/;
616
- if (!scriptStartRegex.test(content)) {
617
- throw new Error(`Unable to find the script block in ${homePagePath}`);
618
- }
619
- content = content.replace(scriptStartRegex, `$&import { initIAP } from '${relativeImport}';\n`);
620
- }
621
-
622
- content = addInitIapCall(content);
623
-
624
- if (content !== originalContent) {
625
- fs.writeFileSync(homePagePath, content, "utf8");
626
- console.log(`✅ Added direct IAP startup to ${path.relative(process.cwd(), homePagePath)}.`);
627
- } else {
628
- console.log(`ℹ️ Direct IAP startup already configured in ${path.relative(process.cwd(), homePagePath)}.`);
629
- }
630
- };
631
-
632
- // ======================================================
633
- // ENSURE IAP STARTS FROM THE HOME PAGE END
634
- // ======================================================
534
+ // ======================================================
535
+ // ENSURE IAP STARTS FROM THE HOME PAGE
536
+ // ======================================================
537
+
538
+ const getHomePagePath = () => {
539
+ const pagesDir = path.join(ROOT_DIR, "pages");
540
+ if (!fs.existsSync(pagesDir)) return null;
541
+
542
+ const exactHomePath = path.join(pagesDir, "home.f7");
543
+ if (fs.existsSync(exactHomePath)) return exactHomePath;
544
+
545
+ const pageFiles = fs.readdirSync(pagesDir)
546
+ .filter(fileName => fileName.endsWith(".f7"));
547
+
548
+ return pageFiles
549
+ .map(fileName => path.join(pagesDir, fileName))
550
+ .find(filePath => /data-name\s*=\s*['"]home['"]/.test(fs.readFileSync(filePath, "utf8"))) || null;
551
+ };
552
+
553
+ const getRelativeModuleImport = (sourceFile, targetFile) => {
554
+ let relativePath = path.relative(path.dirname(sourceFile), targetFile).replace(/\\/g, "/");
555
+ if (!relativePath.startsWith(".")) relativePath = `./${relativePath}`;
556
+ return relativePath;
557
+ };
558
+
559
+ const addInitIapToExistingImport = (content) => {
560
+ const iapImportRegex = /import\s*\{([^}]*)\}\s*from\s*(['"])([^'"]*\/IAP-\d+(?:\.\d+)*\/IAP-check-And-LoadAd\.js)\2\s*;?/;
561
+ const match = content.match(iapImportRegex);
562
+ if (!match || /\binitIAP\b/.test(match[1])) return content;
563
+
564
+ const importNames = match[1].trim();
565
+ const nextImportNames = importNames ? `initIAP, ${importNames}` : "initIAP";
566
+ return content.replace(iapImportRegex, `import { ${nextImportNames} } from ${match[2]}${match[3]}${match[2]};`);
567
+ };
568
+
569
+ const addInitIapCall = (content) => {
570
+ if (/\binitIAP\s*\(/.test(content)) return content;
571
+
572
+ const pageInitRegex = /(\$on\(\s*(['"])pageInit\2\s*,\s*(?:async\s*)?\(\)\s*=>\s*\{\s*\r?\n)/;
573
+ if (pageInitRegex.test(content)) {
574
+ return content.replace(pageInitRegex, "$1 initIAP();\n");
575
+ }
576
+
577
+ const componentStartRegex = /(export\s+default\s*\([^)]*\)\s*=>\s*\{\s*\r?\n)/;
578
+ if (componentStartRegex.test(content)) {
579
+ return content.replace(componentStartRegex, "$1 initIAP();\n");
580
+ }
581
+
582
+ throw new Error("Unable to find a Framework7 component or pageInit lifecycle for IAP startup.");
583
+ };
584
+
585
+ const ensureIapStartupOnHomePage = () => {
586
+ const iapConfig = getAdmobIapConfig()?.IAP;
587
+ if (!isAnyIapStoreEnabled(iapConfig)) {
588
+ console.log("ℹ️ IAP is disabled for every store. AdMob will use direct startup.");
589
+ return;
590
+ }
591
+
592
+ const latestIapDir = getLatestIapDir();
593
+ if (!latestIapDir) {
594
+ throw new Error("IAP is enabled but no IAP-x.x folder was found.");
595
+ }
596
+
597
+ const homePagePath = getHomePagePath();
598
+ if (!homePagePath) {
599
+ throw new Error("IAP is enabled but a Framework7 Home page could not be found in src/pages.");
600
+ }
601
+
602
+ const iapModulePath = path.join(latestIapDir, "IAP-check-And-LoadAd.js");
603
+ if (!fs.existsSync(iapModulePath)) {
604
+ throw new Error(`IAP startup module is missing: ${iapModulePath}`);
605
+ }
606
+
607
+ let content = fs.readFileSync(homePagePath, "utf8");
608
+ const originalContent = content;
609
+ const staticInitImportRegex = /import\s*\{[^}]*\binitIAP\b[^}]*\}\s*from\s*['"][^'"]*\/IAP-\d+(?:\.\d+)*\/IAP-check-And-LoadAd\.js['"]\s*;?/;
610
+
611
+ content = addInitIapToExistingImport(content);
612
+
613
+ if (!staticInitImportRegex.test(content)) {
614
+ const relativeImport = getRelativeModuleImport(homePagePath, iapModulePath);
615
+ const scriptStartRegex = /<script>\s*\r?\n/;
616
+ if (!scriptStartRegex.test(content)) {
617
+ throw new Error(`Unable to find the script block in ${homePagePath}`);
618
+ }
619
+ content = content.replace(scriptStartRegex, `$&import { initIAP } from '${relativeImport}';\n`);
620
+ }
621
+
622
+ content = addInitIapCall(content);
623
+
624
+ if (content !== originalContent) {
625
+ fs.writeFileSync(homePagePath, content, "utf8");
626
+ console.log(`✅ Added direct IAP startup to ${path.relative(process.cwd(), homePagePath)}.`);
627
+ } else {
628
+ console.log(`ℹ️ Direct IAP startup already configured in ${path.relative(process.cwd(), homePagePath)}.`);
629
+ }
630
+ };
631
+
632
+ // ======================================================
633
+ // ENSURE IAP STARTS FROM THE HOME PAGE END
634
+ // ======================================================
635
635
 
636
636
 
637
637
 
@@ -1348,10 +1348,10 @@ let _admobConfig;
1348
1348
 
1349
1349
  const androidPlatformPath = path.join(process.cwd(), 'android');
1350
1350
  const iosPlatformPath = path.join(process.cwd(), 'ios');
1351
- const androidManifestPath = path.join(process.cwd(), 'android', 'app', 'src', 'main', 'AndroidManifest.xml');
1352
- const androidAppBuildGradlePath = path.join(process.cwd(), 'android', 'app', 'build.gradle');
1353
- const AMAZON_WINDOW_EXTENSIONS_DEPENDENCY = 'implementation "androidx.window.extensions.core:core:1.0.0"';
1354
- const AMAZON_WINDOW_EXTENSIONS_DEPENDENCY_PATTERN = /^[ \t]*implementation\s+["']androidx\.window\.extensions\.core:core:[^"']+["'][ \t]*(?:\r?\n)?/gm;
1351
+ const androidManifestPath = path.join(process.cwd(), 'android', 'app', 'src', 'main', 'AndroidManifest.xml');
1352
+ const androidAppBuildGradlePath = path.join(process.cwd(), 'android', 'app', 'build.gradle');
1353
+ const AMAZON_WINDOW_EXTENSIONS_DEPENDENCY = 'implementation "androidx.window.extensions.core:core:1.0.0"';
1354
+ const AMAZON_WINDOW_EXTENSIONS_DEPENDENCY_PATTERN = /^[ \t]*implementation\s+["']androidx\.window\.extensions\.core:core:[^"']+["'][ \t]*(?:\r?\n)?/gm;
1355
1355
  const infoPlistPath = path.join(process.cwd(), 'ios', 'App', 'App', 'Info.plist');
1356
1356
  const iosSceneDelegatePath = path.join(process.cwd(), 'ios', 'App', 'App', 'SceneDelegate.swift');
1357
1357
  const resourcesPath = path.join(process.cwd(), 'resources', 'res');
@@ -1623,7 +1623,8 @@ function updateInfoPlist(admobConfig) {
1623
1623
  const plistData = plist.parse(plistContent);
1624
1624
 
1625
1625
  plistData.GADApplicationIdentifier = admobConfig.APP_ID_IOS;
1626
- plistData.NSUserTrackingUsageDescription = admobConfig.USER_TRACKING_DESCRIPTION;
1626
+ if (isKidsAdmobApp()) delete plistData.NSUserTrackingUsageDescription;
1627
+ else plistData.NSUserTrackingUsageDescription = admobConfig.USER_TRACKING_DESCRIPTION;
1627
1628
  plistData.GADDelayAppMeasurementInit = true;
1628
1629
 
1629
1630
  // https://developers.google.com/admob/ios/quick-start
@@ -1684,25 +1685,7 @@ function updateIOSSceneDelegateForATT() {
1684
1685
  }
1685
1686
 
1686
1687
  let content = fs.readFileSync(iosSceneDelegatePath, 'utf8');
1687
- if (content.includes('ATTrackingManager.requestTrackingAuthorization')) return;
1688
-
1689
1688
  const newline = content.includes('\r\n') ? '\r\n' : '\n';
1690
- const sceneClass = /class SceneDelegate\s*:\s*[^\{]+\{/;
1691
- if (!sceneClass.test(content) || !/import UIKit\r?\n/.test(content) || !content.trimEnd().endsWith('}')) {
1692
- console.warn('SceneDelegate.swift has an unexpected structure. Skipping the iOS ATT scene update.');
1693
- return;
1694
- }
1695
-
1696
- if (!content.includes('import AppTrackingTransparency')) {
1697
- content = content.replace(/import UIKit\r?\n/, (match) => `${match}import AppTrackingTransparency${newline}`);
1698
- }
1699
-
1700
- if (!content.includes('isTrackingAuthorizationRequestScheduled')) {
1701
- content = content.replace(sceneClass, (match) =>
1702
- `${match}${newline} private var isTrackingAuthorizationRequestScheduled = false`
1703
- );
1704
- }
1705
-
1706
1689
  const requestBody = [
1707
1690
  ' if ATTrackingManager.trackingAuthorizationStatus == .notDetermined &&',
1708
1691
  ' !isTrackingAuthorizationRequestScheduled {',
@@ -1719,6 +1702,36 @@ function updateIOSSceneDelegateForATT() {
1719
1702
  ' }',
1720
1703
  ].join(newline);
1721
1704
 
1705
+ if (isKidsAdmobApp()) {
1706
+ const originalContent = content;
1707
+ content = content.split(requestBody).join('');
1708
+ if (content.includes('ATTrackingManager.requestTrackingAuthorization')) {
1709
+ throw new Error('Kids app still contains a custom ATT request in SceneDelegate.swift. Remove that request before building.');
1710
+ }
1711
+ content = content.replace(/^[ \t]*private var isTrackingAuthorizationRequestScheduled = false\r?\n/gm, '');
1712
+ if (!content.includes('ATTrackingManager')) {
1713
+ content = content.replace(/^import AppTrackingTransparency\r?\n/gm, '');
1714
+ }
1715
+ if (content !== originalContent) fs.writeFileSync(iosSceneDelegatePath, content, 'utf8');
1716
+ console.log('iOS ATT scene prompt disabled for this kids app.');
1717
+ return;
1718
+ }
1719
+
1720
+ if (content.includes('ATTrackingManager.requestTrackingAuthorization')) return;
1721
+ const sceneClass = /class SceneDelegate\s*:\s*[^\{]+\{/;
1722
+ if (!sceneClass.test(content) || !/import UIKit\r?\n/.test(content) || !content.trimEnd().endsWith('}')) {
1723
+ console.warn('SceneDelegate.swift has an unexpected structure. Skipping the iOS ATT scene update.');
1724
+ return;
1725
+ }
1726
+ if (!content.includes('import AppTrackingTransparency')) {
1727
+ content = content.replace(/import UIKit\r?\n/, (match) => `${match}import AppTrackingTransparency${newline}`);
1728
+ }
1729
+ if (!content.includes('isTrackingAuthorizationRequestScheduled')) {
1730
+ content = content.replace(sceneClass, (match) =>
1731
+ `${match}${newline} private var isTrackingAuthorizationRequestScheduled = false`
1732
+ );
1733
+ }
1734
+
1722
1735
  const activeSceneMethod = /func sceneDidBecomeActive\s*\(\s*_ scene:\s*UIScene\s*\)\s*\{/;
1723
1736
  if (activeSceneMethod.test(content)) {
1724
1737
  content = content.replace(activeSceneMethod, (match) => `${match}${newline}${requestBody}`);
@@ -1982,7 +1995,7 @@ function updateImports(oldName, newName) {
1982
1995
 
1983
1996
 
1984
1997
  let _serverVersions = null;
1985
- async function fetchVersions() {
1998
+ async function fetchVersions() {
1986
1999
 
1987
2000
  if (_serverVersions) return _serverVersions;
1988
2001
 
@@ -2019,125 +2032,125 @@ async function fetchVersions() {
2019
2032
 
2020
2033
  });
2021
2034
 
2022
- }
2023
-
2024
- function getAgentInstructionsZipLayout(zip) {
2025
- const entries = zip.getEntries().filter(entry => !entry.isDirectory);
2026
-
2027
- for (const entry of entries) {
2028
- const normalizedName = entry.entryName.replace(/\\/g, "/");
2029
- if (
2030
- normalizedName.startsWith("/") ||
2031
- normalizedName.split("/").includes("..") ||
2032
- path.isAbsolute(entry.entryName)
2033
- ) {
2034
- throw new Error(`Unsafe path in agent instructions ZIP: ${entry.entryName}`);
2035
- }
2036
- }
2037
-
2038
- const normalizedEntries = entries.map(entry => ({
2039
- entry,
2040
- name: entry.entryName.replace(/\\/g, "/")
2041
- }));
2042
- const agentFile = normalizedEntries.find(({ name }) =>
2043
- name.toLowerCase() === AGENT_INSTRUCTIONS_FILE.toLowerCase()
2044
- );
2045
- const supportFiles = normalizedEntries
2046
- .filter(({ name }) => name.toLowerCase() !== AGENT_INSTRUCTIONS_FILE.toLowerCase())
2047
- .map(({ entry, name }) => {
2048
- const agentDirectoryPrefix = `${AGENT_INSTRUCTIONS_DIR.toLowerCase()}/`;
2049
- const relativePath = name.toLowerCase().startsWith(agentDirectoryPrefix)
2050
- ? name.slice(name.indexOf("/") + 1)
2051
- : name;
2052
-
2053
- return { entry, relativePath };
2054
- })
2055
- .filter(({ relativePath }) =>
2056
- relativePath &&
2057
- path.basename(relativePath).toLowerCase() !== AGENT_INSTRUCTIONS_VERSION_FILE.toLowerCase()
2058
- );
2059
-
2060
- if (!agentFile || !supportFiles.length) {
2061
- throw new Error(
2062
- `Agent instructions ZIP must contain ${AGENT_INSTRUCTIONS_FILE} (case-insensitive) and at least one supporting file.`
2063
- );
2064
- }
2065
-
2066
- return { agentFile: agentFile.entry, supportFiles };
2067
- }
2068
-
2069
- function readInstalledAgentInstructionsVersion(projectRoot) {
2070
- const versionFile = path.join(projectRoot, AGENT_INSTRUCTIONS_DIR, AGENT_INSTRUCTIONS_VERSION_FILE);
2071
- if (!fs.existsSync(versionFile)) return "0";
2072
-
2073
- const installedVersion = String(fs.readFileSync(versionFile, "utf8")).trim();
2074
- return /^\d+(?:\.\d+)*$/.test(installedVersion) ? installedVersion : "0";
2075
- }
2076
-
2077
- async function syncAgentInstructions() {
2078
- const projectRoot = process.cwd();
2079
- const targetAgentFile = path.join(projectRoot, AGENT_INSTRUCTIONS_FILE);
2080
- const targetAgentDir = path.join(projectRoot, AGENT_INSTRUCTIONS_DIR);
2081
- const localVersion = readInstalledAgentInstructionsVersion(projectRoot);
2082
-
2083
- const versions = await fetchVersions();
2084
- const remoteVersion = String(versions?.[AGENT_INSTRUCTIONS_VERSION_KEY] || "").trim();
2085
-
2086
- if (!/^\d+(?:\.\d+)*$/.test(remoteVersion)) {
2087
- throw new Error(`Required agent instructions could not be resolved from the server.`);
2088
- }
2089
-
2090
- const remoteIsNewer = compareVersionStrings(remoteVersion, localVersion) > 0;
2091
- if (!remoteIsNewer) {
2092
- console.log(`✅ Agent instructions do not need an update (local ${localVersion}, online ${remoteVersion}).`);
2093
- return;
2094
- }
2095
-
2096
- const zipName = `${AGENT_INSTRUCTIONS_ZIP_PREFIX}-${remoteVersion}.zip`;
2097
- const url = `https://htmlcodeplay.com/code-play-plugin/${zipName}`;
2098
- const tempRoot = fs.mkdtempSync(path.join(require("os").tmpdir(), "codeplay-agents-"));
2099
- const zipPath = path.join(tempRoot, zipName);
2100
- const extractPath = path.join(tempRoot, "extracted");
2101
-
2102
- try {
2103
- console.log(`🔍 Checking latest agent instructions: ${zipName}`);
2104
- if (!(await urlExists(url))) {
2105
- throw new Error(`Required agent instructions archive not found: ${url}`);
2106
- }
2107
-
2108
- await downloadFile(url, zipPath);
2109
- console.log(`⬇️ Downloaded agent instructions archive: ${zipName}`);
2110
- const zip = new AdmZip(zipPath);
2111
- const archiveLayout = getAgentInstructionsZipLayout(zip);
2112
-
2113
- const stagedAgentFile = path.join(extractPath, AGENT_INSTRUCTIONS_FILE);
2114
- const stagedAgentDir = path.join(extractPath, AGENT_INSTRUCTIONS_DIR);
2115
- fs.mkdirSync(stagedAgentDir, { recursive: true });
2116
- fs.writeFileSync(stagedAgentFile, archiveLayout.agentFile.getData());
2117
- archiveLayout.supportFiles.forEach(({ entry, relativePath }) => {
2118
- const stagedSupportFile = path.join(stagedAgentDir, relativePath);
2119
- fs.mkdirSync(path.dirname(stagedSupportFile), { recursive: true });
2120
- fs.writeFileSync(stagedSupportFile, entry.getData());
2121
- });
2122
-
2123
- fs.rmSync(targetAgentDir, { recursive: true, force: true });
2124
- fs.mkdirSync(targetAgentDir, { recursive: true });
2125
- fs.copyFileSync(stagedAgentFile, targetAgentFile);
2126
- fs.cpSync(stagedAgentDir, targetAgentDir, { recursive: true, force: true });
2127
- fs.writeFileSync(
2128
- path.join(targetAgentDir, AGENT_INSTRUCTIONS_VERSION_FILE),
2129
- `${remoteVersion}\n`,
2130
- "utf8"
2131
- );
2132
-
2133
- writeUpdateLine(`Agent instructions ${localVersion} -> ${remoteVersion}`);
2134
- console.log(`✅ Agent instructions replaced in the project root → v${remoteVersion}.`);
2135
- } catch (error) {
2136
- throw new Error(`Required agent instructions installation failed: ${error?.message || error}`);
2137
- } finally {
2138
- fs.rmSync(tempRoot, { recursive: true, force: true });
2139
- }
2140
- }
2035
+ }
2036
+
2037
+ function getAgentInstructionsZipLayout(zip) {
2038
+ const entries = zip.getEntries().filter(entry => !entry.isDirectory);
2039
+
2040
+ for (const entry of entries) {
2041
+ const normalizedName = entry.entryName.replace(/\\/g, "/");
2042
+ if (
2043
+ normalizedName.startsWith("/") ||
2044
+ normalizedName.split("/").includes("..") ||
2045
+ path.isAbsolute(entry.entryName)
2046
+ ) {
2047
+ throw new Error(`Unsafe path in agent instructions ZIP: ${entry.entryName}`);
2048
+ }
2049
+ }
2050
+
2051
+ const normalizedEntries = entries.map(entry => ({
2052
+ entry,
2053
+ name: entry.entryName.replace(/\\/g, "/")
2054
+ }));
2055
+ const agentFile = normalizedEntries.find(({ name }) =>
2056
+ name.toLowerCase() === AGENT_INSTRUCTIONS_FILE.toLowerCase()
2057
+ );
2058
+ const supportFiles = normalizedEntries
2059
+ .filter(({ name }) => name.toLowerCase() !== AGENT_INSTRUCTIONS_FILE.toLowerCase())
2060
+ .map(({ entry, name }) => {
2061
+ const agentDirectoryPrefix = `${AGENT_INSTRUCTIONS_DIR.toLowerCase()}/`;
2062
+ const relativePath = name.toLowerCase().startsWith(agentDirectoryPrefix)
2063
+ ? name.slice(name.indexOf("/") + 1)
2064
+ : name;
2065
+
2066
+ return { entry, relativePath };
2067
+ })
2068
+ .filter(({ relativePath }) =>
2069
+ relativePath &&
2070
+ path.basename(relativePath).toLowerCase() !== AGENT_INSTRUCTIONS_VERSION_FILE.toLowerCase()
2071
+ );
2072
+
2073
+ if (!agentFile || !supportFiles.length) {
2074
+ throw new Error(
2075
+ `Agent instructions ZIP must contain ${AGENT_INSTRUCTIONS_FILE} (case-insensitive) and at least one supporting file.`
2076
+ );
2077
+ }
2078
+
2079
+ return { agentFile: agentFile.entry, supportFiles };
2080
+ }
2081
+
2082
+ function readInstalledAgentInstructionsVersion(projectRoot) {
2083
+ const versionFile = path.join(projectRoot, AGENT_INSTRUCTIONS_DIR, AGENT_INSTRUCTIONS_VERSION_FILE);
2084
+ if (!fs.existsSync(versionFile)) return "0";
2085
+
2086
+ const installedVersion = String(fs.readFileSync(versionFile, "utf8")).trim();
2087
+ return /^\d+(?:\.\d+)*$/.test(installedVersion) ? installedVersion : "0";
2088
+ }
2089
+
2090
+ async function syncAgentInstructions() {
2091
+ const projectRoot = process.cwd();
2092
+ const targetAgentFile = path.join(projectRoot, AGENT_INSTRUCTIONS_FILE);
2093
+ const targetAgentDir = path.join(projectRoot, AGENT_INSTRUCTIONS_DIR);
2094
+ const localVersion = readInstalledAgentInstructionsVersion(projectRoot);
2095
+
2096
+ const versions = await fetchVersions();
2097
+ const remoteVersion = String(versions?.[AGENT_INSTRUCTIONS_VERSION_KEY] || "").trim();
2098
+
2099
+ if (!/^\d+(?:\.\d+)*$/.test(remoteVersion)) {
2100
+ throw new Error(`Required agent instructions could not be resolved from the server.`);
2101
+ }
2102
+
2103
+ const remoteIsNewer = compareVersionStrings(remoteVersion, localVersion) > 0;
2104
+ if (!remoteIsNewer) {
2105
+ console.log(`✅ Agent instructions do not need an update (local ${localVersion}, online ${remoteVersion}).`);
2106
+ return;
2107
+ }
2108
+
2109
+ const zipName = `${AGENT_INSTRUCTIONS_ZIP_PREFIX}-${remoteVersion}.zip`;
2110
+ const url = `https://htmlcodeplay.com/code-play-plugin/${zipName}`;
2111
+ const tempRoot = fs.mkdtempSync(path.join(require("os").tmpdir(), "codeplay-agents-"));
2112
+ const zipPath = path.join(tempRoot, zipName);
2113
+ const extractPath = path.join(tempRoot, "extracted");
2114
+
2115
+ try {
2116
+ console.log(`🔍 Checking latest agent instructions: ${zipName}`);
2117
+ if (!(await urlExists(url))) {
2118
+ throw new Error(`Required agent instructions archive not found: ${url}`);
2119
+ }
2120
+
2121
+ await downloadFile(url, zipPath);
2122
+ console.log(`⬇️ Downloaded agent instructions archive: ${zipName}`);
2123
+ const zip = new AdmZip(zipPath);
2124
+ const archiveLayout = getAgentInstructionsZipLayout(zip);
2125
+
2126
+ const stagedAgentFile = path.join(extractPath, AGENT_INSTRUCTIONS_FILE);
2127
+ const stagedAgentDir = path.join(extractPath, AGENT_INSTRUCTIONS_DIR);
2128
+ fs.mkdirSync(stagedAgentDir, { recursive: true });
2129
+ fs.writeFileSync(stagedAgentFile, archiveLayout.agentFile.getData());
2130
+ archiveLayout.supportFiles.forEach(({ entry, relativePath }) => {
2131
+ const stagedSupportFile = path.join(stagedAgentDir, relativePath);
2132
+ fs.mkdirSync(path.dirname(stagedSupportFile), { recursive: true });
2133
+ fs.writeFileSync(stagedSupportFile, entry.getData());
2134
+ });
2135
+
2136
+ fs.rmSync(targetAgentDir, { recursive: true, force: true });
2137
+ fs.mkdirSync(targetAgentDir, { recursive: true });
2138
+ fs.copyFileSync(stagedAgentFile, targetAgentFile);
2139
+ fs.cpSync(stagedAgentDir, targetAgentDir, { recursive: true, force: true });
2140
+ fs.writeFileSync(
2141
+ path.join(targetAgentDir, AGENT_INSTRUCTIONS_VERSION_FILE),
2142
+ `${remoteVersion}\n`,
2143
+ "utf8"
2144
+ );
2145
+
2146
+ writeUpdateLine(`Agent instructions ${localVersion} -> ${remoteVersion}`);
2147
+ console.log(`✅ Agent instructions replaced in the project root → v${remoteVersion}.`);
2148
+ } catch (error) {
2149
+ throw new Error(`Required agent instructions installation failed: ${error?.message || error}`);
2150
+ } finally {
2151
+ fs.rmSync(tempRoot, { recursive: true, force: true });
2152
+ }
2153
+ }
2141
2154
 
2142
2155
 
2143
2156
 
@@ -2918,11 +2931,11 @@ const sourceImportsPackage = (packageName, ignoredFilePath) => {
2918
2931
  });
2919
2932
  };
2920
2933
 
2921
- function runNpmCommand(command) {
2922
- console.log(`🚀 ${command}`);
2923
- execSync(command, { stdio: "inherit" });
2924
- };
2925
-
2934
+ function runNpmCommand(command) {
2935
+ console.log(`🚀 ${command}`);
2936
+ execSync(command, { stdio: "inherit" });
2937
+ };
2938
+
2926
2939
  function removeOneSignalPlugin() {
2927
2940
  if (!isPackageAvailable(ONESIGNAL_PLUGIN_NAME)) {
2928
2941
  console.log('ℹ️ OneSignal is not installed. Skipping removal.');
@@ -2938,8 +2951,8 @@ function removeOneSignalPlugin() {
2938
2951
  console.log(`✅ Uninstalled ${ONESIGNAL_PLUGIN_NAME}.`);
2939
2952
  }
2940
2953
 
2941
- const CODEPLAY_SYSTEM_BARS_PLUGIN = "capacitor-codeplay-system-bars";
2942
-
2954
+ const CODEPLAY_SYSTEM_BARS_PLUGIN = "capacitor-codeplay-system-bars";
2955
+
2943
2956
  function ensureMandatoryCapacitorPlugins() {
2944
2957
  const requiredPackages = [
2945
2958
  "@capacitor/device",
@@ -2957,31 +2970,31 @@ function ensureMandatoryCapacitorPlugins() {
2957
2970
  }
2958
2971
  }
2959
2972
 
2960
- const syncCodeplaySystemBarsPackage = () => {
2961
- if (!fs.existsSync(packageJsonPath)) {
2962
- console.warn(`⚠️ package.json not found. Skipping ${CODEPLAY_SYSTEM_BARS_PLUGIN} package sync.`);
2963
- return;
2964
- }
2965
-
2966
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
2967
- const declaredVersion = packageJson.dependencies?.[CODEPLAY_SYSTEM_BARS_PLUGIN];
2968
- const usesLocalReference = typeof declaredVersion === "string" && declaredVersion.startsWith("file:");
2969
-
2970
- if (usesLocalReference) {
2971
- console.log(`ℹ️ Replacing the local ${CODEPLAY_SYSTEM_BARS_PLUGIN} reference with the npm package.`);
2972
- runNpmCommand(`npm install ${CODEPLAY_SYSTEM_BARS_PLUGIN}`);
2973
- return;
2974
- }
2975
-
2976
- if (!isPackageAvailable(CODEPLAY_SYSTEM_BARS_PLUGIN)) {
2977
- runNpmCommand(`npm install ${CODEPLAY_SYSTEM_BARS_PLUGIN}`);
2978
- return;
2979
- }
2980
-
2981
- console.log(`ℹ️ ${CODEPLAY_SYSTEM_BARS_PLUGIN} is already installed.`);
2982
- };
2983
-
2984
- const ADMOB_NEXTGEN_DEFAULTS = {
2973
+ const syncCodeplaySystemBarsPackage = () => {
2974
+ if (!fs.existsSync(packageJsonPath)) {
2975
+ console.warn(`⚠️ package.json not found. Skipping ${CODEPLAY_SYSTEM_BARS_PLUGIN} package sync.`);
2976
+ return;
2977
+ }
2978
+
2979
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
2980
+ const declaredVersion = packageJson.dependencies?.[CODEPLAY_SYSTEM_BARS_PLUGIN];
2981
+ const usesLocalReference = typeof declaredVersion === "string" && declaredVersion.startsWith("file:");
2982
+
2983
+ if (usesLocalReference) {
2984
+ console.log(`ℹ️ Replacing the local ${CODEPLAY_SYSTEM_BARS_PLUGIN} reference with the npm package.`);
2985
+ runNpmCommand(`npm install ${CODEPLAY_SYSTEM_BARS_PLUGIN}`);
2986
+ return;
2987
+ }
2988
+
2989
+ if (!isPackageAvailable(CODEPLAY_SYSTEM_BARS_PLUGIN)) {
2990
+ runNpmCommand(`npm install ${CODEPLAY_SYSTEM_BARS_PLUGIN}`);
2991
+ return;
2992
+ }
2993
+
2994
+ console.log(`ℹ️ ${CODEPLAY_SYSTEM_BARS_PLUGIN} is already installed.`);
2995
+ };
2996
+
2997
+ const ADMOB_NEXTGEN_DEFAULTS = {
2985
2998
  VERSION: "1.6",
2986
2999
  ios: { nativeid: "" },
2987
3000
  android: { nativeid: "" },
@@ -3054,6 +3067,10 @@ function readJsonSafe(filePath) {
3054
3067
  }
3055
3068
  }
3056
3069
 
3070
+ function isKidsAdmobApp() {
3071
+ return readJsonSafe(getAdmobJsonPath())?.config?.isKidsApp === true;
3072
+ }
3073
+
3057
3074
  function pickFirstValue(...values) {
3058
3075
  return values.find(value => typeof value === "string" && value.trim() !== "") || "";
3059
3076
  }
@@ -3127,7 +3144,9 @@ const mergeNextgenConfigWithoutReplacingExisting = (defaultConfig, oldConfig, ex
3127
3144
  const buildNextgenAdmobConfigFromOldConfig = (templateConfig, existingNextgenConfig, oldConfig) => {
3128
3145
  const mergedConfig = mergeNextgenConfigWithoutReplacingExisting(templateConfig, oldConfig, existingNextgenConfig);
3129
3146
 
3130
- mergedConfig.VERSION = templateConfig.VERSION || ADMOB_NEXTGEN_DEFAULTS.VERSION;
3147
+ const templateVersion = templateConfig.VERSION || ADMOB_NEXTGEN_DEFAULTS.VERSION;
3148
+ mergedConfig.VERSION = compareVersionStrings(existingNextgenConfig?.VERSION || '0', templateVersion) > 0
3149
+ ? existingNextgenConfig.VERSION : templateVersion;
3131
3150
 
3132
3151
  return ensureAdmobNextgenConfigDefaults(mergedConfig);
3133
3152
  };
@@ -3720,19 +3739,19 @@ const getCurrentStoreId = () => {
3720
3739
  if (!fs.existsSync(commonFilePath)) return 1;
3721
3740
 
3722
3741
  const commonFileContent = fs.readFileSync(commonFilePath, "utf8");
3723
- const environmentStoreIdMatch = commonFileContent.match(
3724
- /^[ \t]*export\s+let\s+_storeid\s*=\s*import\.meta\.env\.VITE_STORE_ID\s*\|\|\s*(\d+)\s*;/m
3725
- );
3726
- if (environmentStoreIdMatch) return Number(environmentStoreIdMatch[1]);
3727
-
3728
- const directStoreIdMatch = commonFileContent.match(
3729
- /^[ \t]*export\s+let\s+_storeid\s*=\s*(\d+)\s*;/m
3730
- );
3731
-
3732
- return directStoreIdMatch ? Number(directStoreIdMatch[1]) : 1;
3733
- };
3734
-
3735
- const getRevenueCatStoreState = (iapConfig) => {
3742
+ const environmentStoreIdMatch = commonFileContent.match(
3743
+ /^[ \t]*export\s+let\s+_storeid\s*=\s*import\.meta\.env\.VITE_STORE_ID\s*\|\|\s*(\d+)\s*;/m
3744
+ );
3745
+ if (environmentStoreIdMatch) return Number(environmentStoreIdMatch[1]);
3746
+
3747
+ const directStoreIdMatch = commonFileContent.match(
3748
+ /^[ \t]*export\s+let\s+_storeid\s*=\s*(\d+)\s*;/m
3749
+ );
3750
+
3751
+ return directStoreIdMatch ? Number(directStoreIdMatch[1]) : 1;
3752
+ };
3753
+
3754
+ const getRevenueCatStoreState = (iapConfig) => {
3736
3755
  const storeId = getCurrentStoreId();
3737
3756
  const configuration = iapConfig.configuration || {};
3738
3757
  const storeMap = {
@@ -3762,43 +3781,69 @@ const getRevenueCatStoreState = (iapConfig) => {
3762
3781
  return {
3763
3782
  storeId,
3764
3783
  store: storeMap[storeId] || null
3765
- };
3766
- };
3767
-
3768
- const syncAmazonFireWebViewDependency = (enabled) => {
3769
- if (!fs.existsSync(androidAppBuildGradlePath)) {
3770
- console.error(`❌ Android app build.gradle not found: ${androidAppBuildGradlePath}`);
3771
- process.exit(1);
3772
- }
3773
-
3774
- const currentContent = fs.readFileSync(androidAppBuildGradlePath, "utf8");
3775
- let nextContent = currentContent.replace(AMAZON_WINDOW_EXTENSIONS_DEPENDENCY_PATTERN, "");
3776
-
3777
- if (enabled) {
3778
- const newline = nextContent.includes("\r\n") ? "\r\n" : "\n";
3779
- const dependenciesHeaderPattern = /(^dependencies[ \t]*\{[ \t]*\r?\n)/m;
3780
-
3781
- if (!dependenciesHeaderPattern.test(nextContent)) {
3782
- console.error(`❌ dependencies block not found in ${androidAppBuildGradlePath}`);
3783
- process.exit(1);
3784
- }
3785
-
3786
- nextContent = nextContent.replace(
3787
- dependenciesHeaderPattern,
3788
- `$1 ${AMAZON_WINDOW_EXTENSIONS_DEPENDENCY}${newline}`
3789
- );
3790
- }
3791
-
3792
- if (nextContent !== currentContent) {
3793
- fs.writeFileSync(androidAppBuildGradlePath, nextContent, "utf8");
3794
- }
3795
-
3796
- console.log(enabled
3797
- ? "✅ Amazon Fire WebView compatibility dependency enabled for Store ID 7."
3798
- : "ℹ️ Amazon Fire WebView compatibility dependency disabled for this store.");
3799
- };
3800
-
3801
- const ensureAndroidPermission = (permission, enabled) => {
3784
+ };
3785
+ };
3786
+
3787
+ const syncAmazonFireWebViewDependency = (enabled) => {
3788
+ if (!fs.existsSync(androidAppBuildGradlePath)) {
3789
+ console.error(`❌ Android app build.gradle not found: ${androidAppBuildGradlePath}`);
3790
+ process.exit(1);
3791
+ }
3792
+
3793
+ const currentContent = fs.readFileSync(androidAppBuildGradlePath, "utf8");
3794
+ let nextContent = currentContent.replace(AMAZON_WINDOW_EXTENSIONS_DEPENDENCY_PATTERN, "");
3795
+
3796
+ if (enabled) {
3797
+ const newline = nextContent.includes("\r\n") ? "\r\n" : "\n";
3798
+ const dependenciesHeaderPattern = /(^dependencies[ \t]*\{[ \t]*\r?\n)/m;
3799
+
3800
+ if (!dependenciesHeaderPattern.test(nextContent)) {
3801
+ console.error(`❌ dependencies block not found in ${androidAppBuildGradlePath}`);
3802
+ process.exit(1);
3803
+ }
3804
+
3805
+ nextContent = nextContent.replace(
3806
+ dependenciesHeaderPattern,
3807
+ `$1 ${AMAZON_WINDOW_EXTENSIONS_DEPENDENCY}${newline}`
3808
+ );
3809
+ }
3810
+
3811
+ if (nextContent !== currentContent) {
3812
+ fs.writeFileSync(androidAppBuildGradlePath, nextContent, "utf8");
3813
+ }
3814
+
3815
+ console.log(enabled
3816
+ ? "✅ Amazon Fire WebView compatibility dependency enabled for Store ID 7."
3817
+ : "ℹ️ Amazon Fire WebView compatibility dependency disabled for this store.");
3818
+ };
3819
+
3820
+ function syncAndroidAdIdPermission() {
3821
+ if (!fs.existsSync(androidManifestPath)) return;
3822
+ const originalContent = fs.readFileSync(androidManifestPath, 'utf8');
3823
+ const newline = originalContent.includes('\r\n') ? '\r\n' : '\n';
3824
+ const marker = '<!-- Codeplay: child-directed advertising ID exclusion -->';
3825
+ const permission = 'com.google.android.gms.permission.AD_ID';
3826
+ const permissionTag = `<uses-permission android:name="${permission}" tools:node="remove" />`;
3827
+ const generatedRule = new RegExp(`^[ \\t]*${marker}\\r?\\n[ \\t]*<uses-permission\\b[^>]*tools:node="remove"[^>]*\\/>\\r?\\n`, 'gm');
3828
+ const hadGeneratedRule = originalContent.includes(marker);
3829
+ let content = originalContent.replace(generatedRule, '');
3830
+ if (isKidsAdmobApp()) {
3831
+ content = content.replace(/(?:^[ \t]*)?<uses-permission\b[^>]*>(?:[ \t\r\n]*<\/uses-permission>)?[ \t]*(?:\r?\n)?/gm, tag =>
3832
+ /android:name\s*=\s*["']com\.google\.android\.gms\.permission\.AD_ID["']/.test(tag) ? '' : tag);
3833
+ if (!/xmlns:tools\s*=/.test(content)) {
3834
+ content = content.replace(/<manifest\b/, '<manifest xmlns:tools="http://schemas.android.com/tools"');
3835
+ }
3836
+ content = content.replace(/(<manifest\b[^>]*>)(?:\r?\n)?/, (match, root) => `${root}${newline} ${marker}${newline} ${permissionTag}${newline}`);
3837
+ } else if (hadGeneratedRule) {
3838
+ content = content.replace(/(<manifest\b[^>]*>)(?:\r?\n)?/, (match, root) => `${root}${newline} <uses-permission android:name="${permission}" />${newline}`);
3839
+ }
3840
+ if (content !== originalContent) {
3841
+ fs.writeFileSync(androidManifestPath, content, 'utf8');
3842
+ console.log(isKidsAdmobApp() ? 'Android advertising ID permission excluded for this kids app.' : 'Android advertising ID permission restored for this non-kids app.');
3843
+ }
3844
+ }
3845
+
3846
+ const ensureAndroidPermission = (permission, enabled) => {
3802
3847
  if (!fs.existsSync(androidManifestPath)) return false;
3803
3848
 
3804
3849
  let manifestContent = fs.readFileSync(androidManifestPath, "utf8");
@@ -3825,15 +3870,15 @@ const ensureAndroidPermission = (permission, enabled) => {
3825
3870
  return true;
3826
3871
  };
3827
3872
 
3828
- const syncRevenueCatIapSetup = () => {
3829
- const admobConfig = getAdmobIapConfig();
3830
- const iapConfig = admobConfig?.IAP;
3831
-
3832
- if (!iapConfig) {
3833
- syncAmazonFireWebViewDependency(false);
3834
- console.log("ℹ️ IAP configuration not found. Skipping RevenueCat package sync.");
3835
- return;
3836
- }
3873
+ const syncRevenueCatIapSetup = () => {
3874
+ const admobConfig = getAdmobIapConfig();
3875
+ const iapConfig = admobConfig?.IAP;
3876
+
3877
+ if (!iapConfig) {
3878
+ syncAmazonFireWebViewDependency(false);
3879
+ console.log("ℹ️ IAP configuration not found. Skipping RevenueCat package sync.");
3880
+ return;
3881
+ }
3837
3882
 
3838
3883
  ensureIapDesignInCapacitorConfig(iapConfig);
3839
3884
 
@@ -3841,9 +3886,9 @@ const syncRevenueCatIapSetup = () => {
3841
3886
  iapConfig.playstore === true ||
3842
3887
  iapConfig.ios === true ||
3843
3888
  iapConfig.amazon === true;
3844
- const { storeId, store } = getRevenueCatStoreState(iapConfig);
3845
- const revenueCatEnabledForCurrentStore = store?.enabled === true;
3846
- syncAmazonFireWebViewDependency(storeId === 7 && iapConfig.amazon === true);
3889
+ const { storeId, store } = getRevenueCatStoreState(iapConfig);
3890
+ const revenueCatEnabledForCurrentStore = store?.enabled === true;
3891
+ syncAmazonFireWebViewDependency(storeId === 7 && iapConfig.amazon === true);
3847
3892
 
3848
3893
  if (revenueCatEnabledForCurrentStore && !store.apiKey) {
3849
3894
  console.error(`❌ RevenueCat IAP is enabled for ${store.name} but ${store.apiKeyName} is missing.`);
@@ -4246,18 +4291,19 @@ async function main() {
4246
4291
  validateAndRestoreSignDetails();
4247
4292
  ensureMandatoryCapacitorPlugins();
4248
4293
 
4249
- await loadPluginVersions(); // 🔥 NEW
4250
- await syncAgentInstructions();
4251
-
4252
- await syncAdmobNextgenMigration();
4253
- syncCodeplaySystemBarsPackage();
4254
- syncSystemBarsSafeAreaMigration();
4255
- await checkPlugins();
4256
- ensureIapStartupOnHomePage();
4257
- syncRevenueCatIapSetup();
4294
+ await loadPluginVersions(); // 🔥 NEW
4295
+ await syncAgentInstructions();
4296
+
4297
+ await syncAdmobNextgenMigration();
4298
+ syncCodeplaySystemBarsPackage();
4299
+ syncSystemBarsSafeAreaMigration();
4300
+ await checkPlugins();
4301
+ ensureIapStartupOnHomePage();
4302
+ syncRevenueCatIapSetup();
4258
4303
  syncFirebaseAnalyticsSetup();
4259
4304
  checkAndupdateDropInViteConfig();
4260
4305
  checkAdmobConfigurationProperty();
4306
+ syncAndroidAdIdPermission();
4261
4307
  }
4262
4308
 
4263
4309
  await main();
@@ -4272,6 +4318,11 @@ await main();
4272
4318
  /*
4273
4319
  Release Notes
4274
4320
 
4321
+ 6.5
4322
+ Kids ad configuration disables iOS ATT prompts and excludes Android AD_ID during builds.
4323
+ Non-kids apps retain tracking prompts and normal permission behavior.
4324
+ Ad configuration migration preserves newer local configuration versions.
4325
+
4275
4326
  5.1
4276
4327
  Kotlin version update is commented. Previously admob is not worked if not update the kotlin version to higher version
4277
4328