@mpgd/cli 0.57.0 → 0.59.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.
Files changed (46) hide show
  1. package/dist/capacitor-shell-starter.d.ts +30 -0
  2. package/dist/capacitor-shell-starter.js +243 -11
  3. package/dist/index.js +170 -11
  4. package/dist/legal-site-output.d.ts +18 -0
  5. package/dist/legal-site-output.js +46 -1
  6. package/dist/native-build-info.json +2 -2
  7. package/dist/native-build-target.js +1663 -899
  8. package/dist/native-deploy-run.js +4 -0
  9. package/dist/native-shell-store-policy.d.ts +123 -0
  10. package/dist/native-shell-store-policy.js +816 -0
  11. package/dist/release-state.d.ts +2 -0
  12. package/dist/release-state.js +4 -2
  13. package/dist/testflight-submission.d.ts +9 -0
  14. package/dist/testflight-submission.js +69 -0
  15. package/package.json +17 -17
  16. package/templates/lite-overlay/agent/acceptance.md +1 -1
  17. package/templates/lite-overlay/agent/brief.md +1 -1
  18. package/templates/lite-overlay/src/lite/deleteAccountButton.ts +173 -0
  19. package/templates/lite-overlay/src/lite/restorePurchasesButton.ts +63 -0
  20. package/templates/lite-overlay/src/runtime/createGame.ts +8 -0
  21. package/templates/notifications-overlay/common/docs/NOTIFICATIONS.md +12 -6
  22. package/templates/notifications-overlay/common/notifications/deployment.json +2 -1
  23. package/templates/notifications-overlay/common/notifications/topics.json +3 -0
  24. package/templates/notifications-overlay/common/src/platform/capacitorNotifications.ts +8 -3
  25. package/templates/notifications-overlay/common/src/platform/notifications.ts +17 -3
  26. package/templates/phaser-game/README.md +72 -0
  27. package/templates/phaser-game/agent/acceptance.md +4 -1
  28. package/templates/phaser-game/agent/brief.md +14 -4
  29. package/templates/phaser-game/gitignore +11 -0
  30. package/templates/phaser-game/legal/delete-account.html +133 -0
  31. package/templates/phaser-game/legal/privacy.html +111 -12
  32. package/templates/phaser-game/legal/support.html +1 -0
  33. package/templates/phaser-game/legal/terms.html +1 -0
  34. package/templates/phaser-game/mpgd.game.json +3 -0
  35. package/templates/phaser-game/src/env.d.ts +6 -0
  36. package/templates/phaser-game/src/i18n/messages.ts +86 -1
  37. package/templates/phaser-game/src/main.ts +69 -8
  38. package/templates/phaser-game/src/platform/commerceAccountBinding.ts +39 -10
  39. package/templates/phaser-game/src/platform/deleteAccount.ts +334 -0
  40. package/templates/phaser-game/src/platform/gameServices.ts +55 -10
  41. package/templates/phaser-game/src/platform/guestIdentity.ts +301 -0
  42. package/templates/phaser-game/src/platform/restorePurchases.ts +205 -0
  43. package/templates/phaser-game/src/platform/serverSession.ts +8 -0
  44. package/templates/phaser-game/src/platform/sessionGatedServices.ts +217 -0
  45. package/templates/phaser-game/src/scenes/LobbyScene.ts +254 -1
  46. package/templates/phaser-game/src/scenes/PlayScene.ts +16 -6
@@ -1,8 +1,8 @@
1
1
  // node_modules/.cache/mpgd-native-build/tools/target/build-target.js
2
2
  import { spawnSync as spawnSync5 } from "node:child_process";
3
- import { cpSync as cpSync4, existsSync as existsSync24, lstatSync as lstatSync13, mkdirSync as mkdirSync13, mkdtempSync as mkdtempSync5, readdirSync as readdirSync8, readFileSync as readFileSync23, realpathSync as realpathSync10, rmSync as rmSync12, statSync as statSync7, writeFileSync as writeFileSync16 } from "node:fs";
3
+ import { cpSync as cpSync4, existsSync as existsSync25, lstatSync as lstatSync13, mkdirSync as mkdirSync13, mkdtempSync as mkdtempSync5, readdirSync as readdirSync8, readFileSync as readFileSync24, realpathSync as realpathSync10, rmSync as rmSync12, statSync as statSync7, writeFileSync as writeFileSync17 } from "node:fs";
4
4
  import { tmpdir as tmpdir5 } from "node:os";
5
- import { basename as basename5, dirname as dirname11, join as join15, relative as relative13, resolve as resolve18 } from "node:path";
5
+ import { basename as basename5, dirname as dirname12, join as join16, relative as relative13, resolve as resolve18 } from "node:path";
6
6
  import { loadEnv } from "vite";
7
7
 
8
8
  // node_modules/.cache/mpgd-native-build/packages/cli/src/asset-pack-native-audit.js
@@ -4469,6 +4469,9 @@ var recordableNotificationPermissionStates = Object.freeze([
4469
4469
  "denied",
4470
4470
  "blocked"
4471
4471
  ]);
4472
+ var notificationShellRenderedAdvertisingTargets = Object.freeze([
4473
+ "android"
4474
+ ]);
4472
4475
  var referenceNotificationProviders = deepFreeze({
4473
4476
  fcm: {
4474
4477
  provider: "fcm",
@@ -8728,6 +8731,15 @@ var timestampPattern = new RegExp("^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2})
8728
8731
  // node_modules/.cache/mpgd-native-build/packages/game-services/src/notifications/fcm.js
8729
8732
  var fcmAddressKinds = Object.freeze(["fcm-token"]);
8730
8733
  var fcmReservedDataKeys = Object.freeze(["topic", "route"]);
8734
+ var fcmShellRenderedDataKeys = Object.freeze({
8735
+ render: "mpgd.render",
8736
+ title: "mpgd.title",
8737
+ body: "mpgd.body",
8738
+ channel: "mpgd.channel",
8739
+ actionTitle: "mpgd.actionTitle",
8740
+ optOutRoute: "mpgd.optOutRoute",
8741
+ expiresAt: "mpgd.expiresAt"
8742
+ });
8731
8743
  var tokenRefreshMarginMs = 5 * 60 * 1e3;
8732
8744
  var sha256K = new Uint32Array([
8733
8745
  1116352408,
@@ -8800,6 +8812,10 @@ var sha256K = new Uint32Array([
8800
8812
  var notificationHistoryRetentionDays = 30;
8801
8813
  var maxHistoryWindowSeconds = notificationHistoryRetentionDays * 86400;
8802
8814
 
8815
+ // node_modules/.cache/mpgd-native-build/packages/game-services/src/account-deletion.js
8816
+ var staleErasingPendingMs = 60 * 60 * 1e3;
8817
+ var finalErasureDelayMs = 15 * 60 * 1e3;
8818
+
8803
8819
  // node_modules/.cache/mpgd-native-build/packages/game-services/src/notifications/store.js
8804
8820
  var notificationAmbiguousReplayHours = 24;
8805
8821
  var ambiguousReplayMs = notificationAmbiguousReplayHours * 36e5;
@@ -9620,6 +9636,11 @@ var assertPlatformTargetsConfig = /* @__PURE__ */ (() => {
9620
9636
  path: _path + ".sdkMajor",
9621
9637
  expected: "(number | undefined)",
9622
9638
  value: input.sdkMajor
9639
+ }, _errorFactory)) && (void 0 === input.iosDeviceFamily || "iphone" === input.iosDeviceFamily || "universal" === input.iosDeviceFamily || _assertGuard_16._assertGuard(_exceptionable, {
9640
+ method: "typia.createAssert",
9641
+ path: _path + ".iosDeviceFamily",
9642
+ expected: '("iphone" | "universal" | undefined)',
9643
+ value: input.iosDeviceFamily
9623
9644
  }, _errorFactory));
9624
9645
  const _ao6 = (input, _path, _exceptionable = true) => (void 0 === input.identityUpgrade || "action-required" === input.identityUpgrade || "approval-required" === input.identityUpgrade || "available" === input.identityUpgrade || "configuration-required" === input.identityUpgrade || "disabled" === input.identityUpgrade || "temporarily-unavailable" === input.identityUpgrade || "unsupported" === input.identityUpgrade || _assertGuard_16._assertGuard(_exceptionable, {
9625
9646
  method: "typia.createAssert",
@@ -9770,7 +9791,7 @@ var assertPlatformTargetsConfig = /* @__PURE__ */ (() => {
9770
9791
  return true;
9771
9792
  return "packaged" === value || "remote" === value;
9772
9793
  });
9773
- const _io5 = (input) => (void 0 === input.appName || "string" === typeof input.appName) && (void 0 === input.displayName || "string" === typeof input.displayName) && (void 0 === input.primaryColor || "string" === typeof input.primaryColor) && (void 0 === input.packageId || "string" === typeof input.packageId) && (void 0 === input.bundleId || "string" === typeof input.bundleId) && (void 0 === input.sdkMajor || "number" === typeof input.sdkMajor);
9794
+ const _io5 = (input) => (void 0 === input.appName || "string" === typeof input.appName) && (void 0 === input.displayName || "string" === typeof input.displayName) && (void 0 === input.primaryColor || "string" === typeof input.primaryColor) && (void 0 === input.packageId || "string" === typeof input.packageId) && (void 0 === input.bundleId || "string" === typeof input.bundleId) && (void 0 === input.sdkMajor || "number" === typeof input.sdkMajor) && (void 0 === input.iosDeviceFamily || "iphone" === input.iosDeviceFamily || "universal" === input.iosDeviceFamily);
9774
9795
  const _io6 = (input) => (void 0 === input.identityUpgrade || "action-required" === input.identityUpgrade || "approval-required" === input.identityUpgrade || "available" === input.identityUpgrade || "configuration-required" === input.identityUpgrade || "disabled" === input.identityUpgrade || "temporarily-unavailable" === input.identityUpgrade || "unsupported" === input.identityUpgrade) && (void 0 === input.presentation || "action-required" === input.presentation || "approval-required" === input.presentation || "available" === input.presentation || "configuration-required" === input.presentation || "disabled" === input.presentation || "temporarily-unavailable" === input.presentation || "unsupported" === input.presentation) && (void 0 === input.sharing || "action-required" === input.sharing || "approval-required" === input.sharing || "available" === input.sharing || "configuration-required" === input.sharing || "disabled" === input.sharing || "temporarily-unavailable" === input.sharing || "unsupported" === input.sharing) && (void 0 === input.inboundShare || "action-required" === input.inboundShare || "approval-required" === input.inboundShare || "available" === input.inboundShare || "configuration-required" === input.inboundShare || "disabled" === input.inboundShare || "temporarily-unavailable" === input.inboundShare || "unsupported" === input.inboundShare) && (void 0 === input.notifications || "action-required" === input.notifications || "approval-required" === input.notifications || "available" === input.notifications || "configuration-required" === input.notifications || "disabled" === input.notifications || "temporarily-unavailable" === input.notifications || "unsupported" === input.notifications) && (void 0 === input.presentationMode || "fullscreen" === input.presentationMode || "inline-expanded" === input.presentationMode);
9775
9796
  const _io7 = (input) => (void 0 === input.profile || "string" === typeof input.profile) && (void 0 === input.source || "string" === typeof input.source) && (void 0 === input.backgroundColor || "string" === typeof input.backgroundColor) && (void 0 === input.externalUrl || "string" === typeof input.externalUrl) && (void 0 === input.variants || "object" === typeof input.variants && null !== input.variants && false === Array.isArray(input.variants) && _io8(input.variants));
9776
9797
  const _io8 = (input) => (void 0 === input.maskable || "string" === typeof input.maskable) && (void 0 === input.androidForeground || "string" === typeof input.androidForeground) && (void 0 === input.monochrome || "string" === typeof input.monochrome) && (void 0 === input.background || "string" === typeof input.background);
@@ -15423,9 +15444,9 @@ function isProcessAlive(pid) {
15423
15444
  }
15424
15445
 
15425
15446
  // node_modules/.cache/mpgd-native-build/tools/target/native-build-execution.js
15426
- import { existsSync as existsSync23, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync4, readdirSync as readdirSync7, rmSync as rmSync11, statSync as statSync6 } from "node:fs";
15447
+ import { existsSync as existsSync24, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync4, readdirSync as readdirSync7, rmSync as rmSync11, statSync as statSync6 } from "node:fs";
15427
15448
  import { tmpdir as tmpdir4 } from "node:os";
15428
- import { dirname as dirname10, isAbsolute as isAbsolute12, join as join14 } from "node:path";
15449
+ import { dirname as dirname11, isAbsolute as isAbsolute12, join as join15 } from "node:path";
15429
15450
 
15430
15451
  // node_modules/.cache/mpgd-native-build/packages/cli/src/ios-signing-session.js
15431
15452
  import { createHash as createHash12, randomBytes } from "node:crypto";
@@ -16226,922 +16247,1457 @@ function applyCapacitorShellCsp(webDir) {
16226
16247
  }
16227
16248
  }
16228
16249
 
16229
- // node_modules/.cache/mpgd-native-build/tools/target/native-ios-inspection.js
16230
- import { spawnSync as spawnSync3 } from "node:child_process";
16231
- import { existsSync as existsSync19, mkdtempSync as mkdtempSync3, readFileSync as readFileSync20, rmSync as rmSync10, statSync as statSync4 } from "node:fs";
16232
- import { tmpdir as tmpdir3 } from "node:os";
16233
- import path7 from "node:path";
16234
- function inspectSignedIosArchive(archive, input) {
16235
- return inspectSignedIosApplication(path7.join(archive, "Products/Applications/App.app"), input);
16236
- }
16237
- function inspectSignedIosIpa(ipa, input) {
16238
- if (!existsSync19(ipa) || !statSync4(ipa).isFile()) {
16239
- throw new Error("iOS store IPA is missing.");
16240
- }
16241
- const runner = input.runner ?? { run: runCommand2 };
16242
- const listing = runner.run("unzip", ["-Z1", ipa]);
16243
- if (listing.status !== 0) {
16244
- throw new Error("iOS store IPA could not be listed.");
16245
- }
16246
- const entries = listing.stdout.split(/\r?\n/u).filter(Boolean);
16247
- if (entries.some((entry) => entry.startsWith("/") || entry.includes("\\") || entry.split("/").some((segment) => segment === ".." || segment === "."))) {
16248
- throw new Error("iOS store IPA has an unsafe path.");
16250
+ // node_modules/.cache/mpgd-native-build/tools/target/native-store-policy.js
16251
+ import { existsSync as existsSync19, readFileSync as readFileSync20 } from "node:fs";
16252
+ import { dirname as dirname9, join as join13 } from "node:path";
16253
+
16254
+ // node_modules/.cache/mpgd-native-build/packages/cli/src/native-shell-identity.js
16255
+ function assertAndroidSettingsAppProject(source) {
16256
+ const clean = stripGradleComments(source);
16257
+ const code = maskGradleStrings(clean);
16258
+ const includes = [...clean.matchAll(/\binclude\s*(?:\(([^)]*)\)|([^\r\n;]+))/gu)];
16259
+ const includesApp = includes.some((match) => code.slice(match.index, match.index + 7) === "include" && /(?:^|,)\s*["']:app["']\s*(?:,|$)/u.test(match[1] ?? match[2] ?? ""));
16260
+ if (!includesApp) {
16261
+ throw new Error("Android settings must include :app at android/app without remapping it.");
16249
16262
  }
16250
- const appManifests = entries.filter((entry) => /^Payload\/[^/]+\.app\/Info\.plist$/u.test(entry));
16251
- if (appManifests.length !== 1) {
16252
- throw new Error("iOS store IPA must contain exactly one app manifest.");
16263
+ assertAndroidSettingsNoAppRemap(clean);
16264
+ }
16265
+ function assertAndroidSettingsNoAppRemap(source) {
16266
+ const clean = stripGradleComments(source);
16267
+ const code = maskGradleStrings(clean);
16268
+ const appRemap = [...clean.matchAll(/\b(project|findProject)\s*\(\s*["']:app["']\s*\)/gu)].some((match) => code.slice(match.index, match.index + (match[1]?.length ?? 0)) === match[1]);
16269
+ if (appRemap) {
16270
+ throw new Error("Android settings must include :app at android/app without remapping it.");
16253
16271
  }
16254
- const appRelative = path7.dirname(appManifests[0] ?? "");
16255
- const extracted = mkdtempSync3(path7.join(tmpdir3(), "mpgd-ios-inspection-"));
16256
- try {
16257
- const result = runner.run("unzip", ["-q", ipa, "-d", extracted]);
16258
- if (result.status !== 0) {
16259
- throw new Error("iOS store IPA could not be extracted.");
16272
+ }
16273
+ function stripGradleComments(source) {
16274
+ let output = "";
16275
+ let quote;
16276
+ for (let index = 0; index < source.length; index += 1) {
16277
+ const character = source[index] ?? "";
16278
+ const next = source[index + 1];
16279
+ if (quote !== void 0) {
16280
+ output += character;
16281
+ if (character === "\\" && next !== void 0) {
16282
+ output += next;
16283
+ index += 1;
16284
+ } else if (character === quote) {
16285
+ quote = void 0;
16286
+ }
16287
+ continue;
16260
16288
  }
16261
- return inspectSignedIosApplication(path7.join(extracted, appRelative), input);
16262
- } finally {
16263
- rmSync10(extracted, { recursive: true, force: true });
16289
+ if (character === '"' || character === "'") {
16290
+ quote = character;
16291
+ output += character;
16292
+ continue;
16293
+ }
16294
+ if (character === "/" && next === "/") {
16295
+ index += 2;
16296
+ while (index < source.length && source[index] !== "\n") {
16297
+ index += 1;
16298
+ }
16299
+ output += "\n";
16300
+ continue;
16301
+ }
16302
+ if (character === "/" && next === "*") {
16303
+ index += 2;
16304
+ while (index < source.length && !(source[index] === "*" && source[index + 1] === "/")) {
16305
+ output += source[index] === "\n" ? "\n" : " ";
16306
+ index += 1;
16307
+ }
16308
+ index += 1;
16309
+ continue;
16310
+ }
16311
+ output += character;
16264
16312
  }
16313
+ return output;
16265
16314
  }
16266
- function inspectSignedIosApplication(appPath, input) {
16267
- const plist = path7.join(appPath, "Info.plist");
16268
- if (!existsSync19(plist) || !statSync4(plist).isFile()) {
16269
- throw new Error("Signed iOS app manifest is missing.");
16270
- }
16271
- if (!existsSync19(path7.join(appPath, "Assets.car")) || existsSync19(path7.join(appPath, "Info-Smoke.plist"))) {
16272
- throw new Error("Signed iOS app contains smoke assets or omits release icons.");
16273
- }
16274
- const runner = input.runner ?? { run: runCommand2 };
16275
- const signature = runner.run("codesign", ["--verify", "--deep", "--strict", appPath]);
16276
- if (signature.status !== 0) {
16277
- throw new Error("iOS app code signature could not be verified.");
16278
- }
16279
- const signingInfo = runner.run("codesign", ["-dv", "--verbose=4", appPath]);
16280
- const signingText = `${signingInfo.stdout}
16281
- ${signingInfo.stderr}`;
16282
- const expectedTeamLine = `TeamIdentifier=${input.expectedTeamId}`;
16283
- if (signingInfo.status !== 0 || /Signature=adhoc/iu.test(signingText) || !signingText.split(/\r?\n/u).some((line) => line.trim() === expectedTeamLine)) {
16284
- throw new Error("iOS app is not signed for the expected development team.");
16315
+ function hasGradleIdentityMutation(source) {
16316
+ const clean = stripGradleComments(source);
16317
+ const masked = maskGradleStrings(clean);
16318
+ if (hasGradlePropertySetter(clean) || hasGradleBracketIdentityWrite(clean)) {
16319
+ return true;
16285
16320
  }
16286
- const fields = [
16287
- ["bundleId", "CFBundleIdentifier", input.expectedBundleId],
16288
- ["marketingVersion", "CFBundleShortVersionString", input.expectedMarketingVersion],
16289
- ["buildNumber", "CFBundleVersion", input.expectedBuildNumber]
16290
- ];
16291
- const observed = Object.fromEntries(fields.map(([name, key, expected]) => {
16292
- const result = runner.run("/usr/libexec/PlistBuddy", ["-c", `Print :${key}`, plist]);
16293
- if (result.status !== 0 || result.stdout.trim() !== expected) {
16294
- throw new Error(`iOS app ${name} does not match release identity.`);
16321
+ const setter = /\bset(?:ApplicationId|ApplicationIdSuffix|VersionCode|VersionName|VersionNameSuffix)\s*\(/u;
16322
+ const field = "(?:applicationId|applicationIdSuffix|versionCode|versionName|versionNameSuffix)";
16323
+ const assignment = new RegExp(`\\b${field}(?:\\s*(?:\\+?=|\\(|["'\\d]|\\.set\\s*\\()|[ \\t]+[A-Za-z_$])`, "u");
16324
+ return setter.test(masked) || assignment.test(masked);
16325
+ }
16326
+ function hasGradleBracketIdentityWrite(source) {
16327
+ const clean = stripGradleComments(source);
16328
+ const code = maskGradleStrings(clean);
16329
+ const key = "(?:applicationId|applicationIdSuffix|versionCode|versionName|versionNameSuffix)";
16330
+ const bracketWrite = `\\[\\s*["']${key}["']\\s*\\]\\s*(?:\\+?=|\\.\\s*set\\s*\\(|\\()`;
16331
+ const expression = new RegExp(bracketWrite, "gu");
16332
+ return [...clean.matchAll(expression)].some((match) => code[match.index] === "[");
16333
+ }
16334
+ function hasAndroidDisplayNameResourceOverride(source) {
16335
+ const clean = stripGradleComments(source);
16336
+ const code = maskGradleStrings(clean);
16337
+ return [...clean.matchAll(/\bresValue\b/gu)].some((match) => {
16338
+ if (code.slice(match.index, match.index + 8) !== "resValue") {
16339
+ return false;
16340
+ }
16341
+ const tail = clean.slice(match.index);
16342
+ const literal = /^resValue\s*(?:\(\s*)?["'][^"']+["']\s*,\s*["']([^"']+)["']/u.exec(tail);
16343
+ return literal === null || literal[1] === "app_name";
16344
+ });
16345
+ }
16346
+ function hasAndroidResourceSourceSetOverride(source) {
16347
+ const code = maskGradleStrings(stripGradleComments(source));
16348
+ return /\bsourceSets\b/u.test(code) && /\bres\b/u.test(code);
16349
+ }
16350
+ function hasAndroidManifestSourceSetOverride(source) {
16351
+ const code = maskGradleStrings(stripGradleComments(source));
16352
+ return /\bsourceSets\b/u.test(code) && /\bmanifest\b/u.test(code);
16353
+ }
16354
+ function hasGradleTaskAction(source) {
16355
+ const code = maskGradleStrings(stripGradleComments(source));
16356
+ const withoutStandardClean = code.replace(/\btask\s+clean\s*\(\s*type\s*:\s*Delete\s*\)\s*\{\s*delete\s+rootProject\.buildDir\s*;?\s*\}/gu, "");
16357
+ return /\b(?:doFirst|doLast|whenTaskAdded|whenReady)\s*(?:\(|\{)/u.test(withoutStandardClean) || /\btasks\s*(?:\.|\[|\{)/u.test(withoutStandardClean) || /\btask\s*(?:\([^)]*\)|[A-Za-z_]\w*(?:\s*\([^)]*\))?)\s*\{/u.test(withoutStandardClean);
16358
+ }
16359
+ function hasGradlePropertySetter(source) {
16360
+ const clean = stripGradleComments(source);
16361
+ const masked = maskGradleStrings(clean);
16362
+ return /\bsetProperty\s*\(/u.test(masked);
16363
+ }
16364
+ function countGradleIdentityWrites(source, key) {
16365
+ const masked = maskGradleStrings(stripGradleComments(source));
16366
+ const expression = new RegExp(`\\b${key}\\b`, "gu");
16367
+ return [...masked.matchAll(expression)].filter((match) => {
16368
+ const index = match.index ?? 0;
16369
+ const rest = masked.slice(index + key.length);
16370
+ if (masked[index - 1] === ".") {
16371
+ return /^\s*(?:\+?=|\(|\.set\s*\()/u.test(rest);
16372
+ }
16373
+ return /^\s*(?:\+?=|\(|["'\d]|[A-Za-z_$])/u.test(rest);
16374
+ }).length;
16375
+ }
16376
+ function maskGradleStrings(clean) {
16377
+ let masked = "";
16378
+ let quote;
16379
+ for (let index = 0; index < clean.length; index += 1) {
16380
+ const character = clean[index] ?? "";
16381
+ if (quote !== void 0) {
16382
+ if (character === "\\" && index + 1 < clean.length) {
16383
+ masked += " ";
16384
+ index += 1;
16385
+ } else if (character === quote) {
16386
+ masked += character;
16387
+ quote = void 0;
16388
+ } else {
16389
+ masked += character === "\n" ? "\n" : " ";
16390
+ }
16391
+ } else if (character === '"' || character === "'") {
16392
+ quote = character;
16393
+ masked += character;
16394
+ } else {
16395
+ masked += character;
16295
16396
  }
16296
- return [name, expected];
16297
- }));
16298
- const configFiles = [
16299
- path7.join(appPath, "capacitor.config.json"),
16300
- path7.join(appPath, "public/capacitor.config.json")
16301
- ];
16302
- if (!configFiles.some((file) => existsSync19(file))) {
16303
- throw new Error("Signed iOS app Capacitor configuration is missing.");
16304
16397
  }
16305
- for (const configFile of configFiles) {
16306
- if (existsSync19(configFile)) {
16307
- assertNoDebugServer(configFile);
16398
+ return masked;
16399
+ }
16400
+ function readIosReleaseInfoPlist(source) {
16401
+ const target2 = readIosReleaseConfiguration(source, readIosAppConfigurationList(source));
16402
+ let value = readIosInfoPlistSetting(target2);
16403
+ if (value === void 0) {
16404
+ if (/\bbaseConfigurationReference\s*=/u.test(target2)) {
16405
+ throw new Error("Existing ios project Release Info.plist xcconfig cannot be read safely.");
16406
+ }
16407
+ const projectListId = readIosProjectConfigurationList(source);
16408
+ if (projectListId === void 0) {
16409
+ throw new Error("Existing ios project Release Info.plist has no project configuration.");
16410
+ }
16411
+ const project = readIosReleaseConfiguration(source, projectListId);
16412
+ value = readIosInfoPlistSetting(project);
16413
+ if (value === void 0 && /\bbaseConfigurationReference\s*=/u.test(project)) {
16414
+ throw new Error("Existing ios project Release Info.plist xcconfig cannot be read safely.");
16308
16415
  }
16309
16416
  }
16310
- return { ...observed, teamId: input.expectedTeamId, signed: true };
16417
+ if (value === void 0) {
16418
+ throw new Error("Existing ios project Release Info.plist cannot be read safely.");
16419
+ }
16420
+ return value;
16311
16421
  }
16312
- function assertNoDebugServer(file) {
16313
- const value = JSON.parse(readFileSync20(file, "utf8"));
16314
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
16315
- throw new Error("Signed iOS app has malformed Capacitor configuration.");
16422
+ function assertIosReleaseProductName(source) {
16423
+ const target2 = readIosReleaseConfiguration(source, readIosAppConfigurationList(source));
16424
+ let name = readIosProductName(target2);
16425
+ if (name === void 0 || name === "$(inherited)") {
16426
+ if (/\bbaseConfigurationReference\s*=/u.test(target2)) {
16427
+ throw new Error("Existing ios project Release product name xcconfig is unsupported.");
16428
+ }
16429
+ const projectList = readIosProjectConfigurationList(source);
16430
+ if (projectList !== void 0) {
16431
+ const project = readIosReleaseConfiguration(source, projectList);
16432
+ name = readIosProductName(project);
16433
+ if ((name === void 0 || name === "$(inherited)") && /\bbaseConfigurationReference\s*=/u.test(project)) {
16434
+ throw new Error("Existing ios project Release product name xcconfig is unsupported.");
16435
+ }
16436
+ }
16316
16437
  }
16317
- const config2 = value;
16318
- const server = config2.server;
16319
- const ios = config2.ios;
16320
- if (typeof server === "object" && server !== null && "url" in server || typeof ios === "object" && ios !== null && "webContentsDebuggingEnabled" in ios && ios.webContentsDebuggingEnabled === true) {
16321
- throw new Error("Signed iOS app retains a live-reload or debug bridge configuration.");
16438
+ if (name !== void 0 && name !== "$(inherited)" && name !== "App" && name !== "$(TARGET_NAME)") {
16439
+ throw new Error("Existing ios project Release PRODUCT_NAME must build App.app.");
16322
16440
  }
16323
16441
  }
16324
- function runCommand2(command, args) {
16325
- const result = spawnSync3(command, [...args], {
16326
- encoding: "utf8",
16327
- env: nativeInspectionEnvironment(),
16328
- maxBuffer: 4 * 1024 * 1024
16329
- });
16330
- if (result.error !== void 0) {
16331
- throw new Error(`Required iOS inspection command is unavailable: ${command}.`);
16442
+ function assertIosReleaseInfoPlistExpansion(source) {
16443
+ const target2 = readIosReleaseConfiguration(source, readIosAppConfigurationList(source));
16444
+ assertIosInfoPlistExpansionSetting(target2);
16445
+ const projectList = readIosProjectConfigurationList(source);
16446
+ if (projectList !== void 0) {
16447
+ assertIosInfoPlistExpansionSetting(readIosReleaseConfiguration(source, projectList));
16332
16448
  }
16333
- return {
16334
- status: result.status,
16335
- stdout: result.stdout,
16336
- stderr: result.stderr
16449
+ }
16450
+ function assertIosReleasePackagingSettings(source) {
16451
+ const target2 = readIosReleaseConfiguration(source, readIosAppConfigurationList(source));
16452
+ const projectList = readIosProjectConfigurationList(source);
16453
+ const project = projectList === void 0 ? void 0 : readIosReleaseConfiguration(source, projectList);
16454
+ const setting = (configuration, key) => {
16455
+ if (configuration === void 0) {
16456
+ return void 0;
16457
+ }
16458
+ const conditional = new RegExp(`\\b${key}\\[[^\\]\\r\\n]+\\]`, "u");
16459
+ if (conditional.test(configuration)) {
16460
+ throw new Error(`Existing ios project conditional Release ${key} is unsupported.`);
16461
+ }
16462
+ const expression = `(?:^|[\\s{;])["']?${key}["']?\\s*=\\s*([^;]+);`;
16463
+ const pattern = new RegExp(expression, "gu");
16464
+ const values = [...configuration.matchAll(pattern)].map((match) => match[1]?.trim().replace(/^["']|["']$/gu, ""));
16465
+ if (values.length > 1 || /\bbaseConfigurationReference\s*=/u.test(configuration)) {
16466
+ throw new Error(`Existing ios project Release ${key} cannot be read safely.`);
16467
+ }
16468
+ return values[0];
16469
+ };
16470
+ const effective = (key) => {
16471
+ const targetValue = setting(target2, key);
16472
+ return targetValue === void 0 || targetValue === "$(inherited)" ? setting(project, key) : targetValue;
16337
16473
  };
16474
+ const preprocessing = effective("INFOPLIST_PREPROCESS");
16475
+ if (preprocessing !== void 0 && preprocessing !== "NO") {
16476
+ throw new Error("Existing ios project Release Info.plist preprocessing is unsupported.");
16477
+ }
16478
+ for (const key of ["INFOPLIST_PREPROCESSOR_DEFINITIONS", "INFOPLIST_PREFIX_HEADER"]) {
16479
+ if (effective(key) !== void 0) {
16480
+ throw new Error(`Existing ios project Release ${key} is unsupported.`);
16481
+ }
16482
+ }
16483
+ const skipInstall = effective("SKIP_INSTALL");
16484
+ if (skipInstall !== void 0 && skipInstall !== "NO") {
16485
+ throw new Error("Existing ios project Release SKIP_INSTALL must be NO.");
16486
+ }
16338
16487
  }
16339
-
16340
- // node_modules/.cache/mpgd-native-build/tools/target/native-ios-signing.js
16341
- import { spawnSync as spawnSync4 } from "node:child_process";
16342
- import { existsSync as existsSync20, statSync as statSync5 } from "node:fs";
16343
- import path8 from "node:path";
16344
- function resolveIosSigningPlan(environment, mode) {
16345
- const teamId = environment.MPGD_IOS_TEAM_ID?.trim();
16346
- if (teamId === void 0 || !/^[A-Z0-9]{10}$/u.test(teamId)) {
16347
- throw new Error("Signed iOS builds require a 10-character MPGD_IOS_TEAM_ID.");
16488
+ function assertIosInfoPlistExpansionSetting(configuration) {
16489
+ const key = "INFOPLIST_EXPAND_BUILD_SETTINGS";
16490
+ const conditional = /(?:^|[\s{;])["']?INFOPLIST_EXPAND_BUILD_SETTINGS(?:\[[^\]\r\n]+\])+["']?\s*=/u;
16491
+ if (conditional.test(configuration)) {
16492
+ throw new Error("Existing ios project conditional Release Info.plist expansion is unsupported.");
16348
16493
  }
16349
- const style = environment.MPGD_IOS_SIGNING_STYLE ?? "Automatic";
16350
- if (style !== "Automatic" && style !== "Manual") {
16351
- throw new Error("MPGD_IOS_SIGNING_STYLE must be Automatic or Manual.");
16494
+ const values = [...configuration.matchAll(/(?:^|[\s{;])["']?INFOPLIST_EXPAND_BUILD_SETTINGS["']?\s*=\s*([^;]+);/gu)].map((match) => match[1]?.trim().replace(/^["']|["']$/gu, ""));
16495
+ if (values.length === 0 && /\bbaseConfigurationReference\s*=/u.test(configuration)) {
16496
+ throw new Error("Existing ios project Release Info.plist expansion xcconfig is unsupported.");
16352
16497
  }
16353
- const identity = environment.MPGD_IOS_SIGNING_IDENTITY?.trim();
16354
- const profile2 = environment.MPGD_IOS_PROVISIONING_PROFILE_SPECIFIER?.trim();
16355
- if (style === "Manual" && (identity === void 0 || identity === "" || profile2 === void 0 || profile2 === "")) {
16356
- throw new Error("Manual iOS signing requires host identity and provisioning profile names.");
16498
+ if (values.length > 1 || values.some((value) => value !== "YES")) {
16499
+ throw new Error(`Existing ios project Release ${key} must be enabled.`);
16357
16500
  }
16358
- if (style === "Automatic" && profile2 !== void 0 && profile2 !== "") {
16359
- throw new Error("Automatic iOS signing must not set a manual provisioning profile.");
16501
+ }
16502
+ function readIosProductName(configuration) {
16503
+ if (/(?:^|[\s{;])["']?PRODUCT_NAME(?:\[[^\]\r\n]+\])+["']?\s*=/u.test(configuration)) {
16504
+ throw new Error("Existing ios project conditional Release PRODUCT_NAME is unsupported.");
16360
16505
  }
16361
- const archiveBuildSettings = [
16362
- "CODE_SIGNING_ALLOWED=YES",
16363
- `CODE_SIGN_STYLE=${style}`,
16364
- `DEVELOPMENT_TEAM=${teamId}`,
16365
- ...identity === void 0 || identity === "" ? [] : [`CODE_SIGN_IDENTITY=${identity}`],
16366
- ...profile2 === void 0 || profile2 === "" ? [] : [`PROVISIONING_PROFILE_SPECIFIER=${profile2}`],
16367
- ...environment.MPGD_IOS_SIGNING_KEYCHAIN === void 0 ? [] : [`OTHER_CODE_SIGN_FLAGS=--keychain ${JSON.stringify(environment.MPGD_IOS_SIGNING_KEYCHAIN)}`]
16368
- ];
16369
- if (mode !== "store-export") {
16370
- return { teamId, archiveBuildSettings };
16506
+ const values = [...configuration.matchAll(/(?:^|[\s{;])["']?PRODUCT_NAME["']?\s*=\s*([^;]+);/gu)].map((match) => match[1]?.trim().replace(/^["']|["']$/gu, ""));
16507
+ if (values.length > 1) {
16508
+ throw new Error("Existing ios project Release PRODUCT_NAME is ambiguous.");
16371
16509
  }
16372
- const configuredExportFile = environment.MPGD_IOS_EXPORT_OPTIONS_PLIST;
16373
- if (configuredExportFile === void 0 || configuredExportFile.trim() === "") {
16374
- throw new Error("iOS store export requires MPGD_IOS_EXPORT_OPTIONS_PLIST.");
16510
+ return values[0];
16511
+ }
16512
+ function readIosInfoPlistSetting(configuration) {
16513
+ if (/(?:^|[\s{;])["']?INFOPLIST_FILE(?:\[[^\]\r\n]+\])+["']?\s*=/u.test(configuration)) {
16514
+ throw new Error("Existing ios project conditional Release Info.plist is unsupported.");
16375
16515
  }
16376
- const exportOptionsPlist = path8.resolve(configuredExportFile);
16377
- if (!existsSync20(exportOptionsPlist) || !statSync5(exportOptionsPlist).isFile()) {
16378
- throw new Error("iOS export options plist must be an existing host file.");
16516
+ const values = [...configuration.matchAll(/(?:^|[\s{;])["']?INFOPLIST_FILE["']?\s*=\s*([^;]+);/gu)].map((match) => match[1]?.trim().replace(/^["']|["']$/gu, ""));
16517
+ if (values.length > 1) {
16518
+ throw new Error("Existing ios project Release Info.plist is ambiguous.");
16379
16519
  }
16380
- const method = spawnSync4("/usr/libexec/PlistBuddy", ["-c", "Print :method", exportOptionsPlist], {
16381
- encoding: "utf8",
16382
- maxBuffer: 1024 * 1024
16520
+ const value = values[0];
16521
+ if (value === void 0 || value === "$(inherited)") {
16522
+ return void 0;
16523
+ }
16524
+ if (value.includes("$") || value.includes("\\") || pathIsUnsafe(value)) {
16525
+ throw new Error("Existing ios project Release Info.plist cannot be read safely.");
16526
+ }
16527
+ return value;
16528
+ }
16529
+ function pathIsUnsafe(value) {
16530
+ return value.startsWith("/") || value.split("/").some((part) => part === ".." || part === "");
16531
+ }
16532
+ function readIosAppConfigurationList(source) {
16533
+ const target2 = readPbxObject(source, readIosAppTargetId(source));
16534
+ const listId = /\bbuildConfigurationList\s*=\s*([A-F0-9]+)\b/u.exec(target2)?.[1];
16535
+ if (listId === void 0) {
16536
+ throw new Error("Existing ios project App target configuration is missing.");
16537
+ }
16538
+ return listId;
16539
+ }
16540
+ function readIosAppTargetId(source) {
16541
+ const appTargets = [...source.matchAll(/\b([A-F0-9]+)\s*\/\*\s*App\s*\*\/\s*=\s*\{/gu)].map((match) => match[1] ?? "").filter((id) => {
16542
+ const block = readPbxObject(source, id);
16543
+ return /\bisa\s*=\s*PBXNativeTarget;/u.test(block) && /\bname\s*=\s*"?App"?\s*;/u.test(block);
16383
16544
  });
16384
- if (method.error !== void 0 || method.status !== 0 || !["app-store", "app-store-connect"].includes(method.stdout.trim())) {
16385
- throw new Error("iOS export options must select an App Store distribution method.");
16545
+ if (appTargets.length !== 1) {
16546
+ throw new Error("Existing ios project App target could not be read safely.");
16386
16547
  }
16387
- return { teamId, archiveBuildSettings, exportOptionsPlist };
16548
+ const targetId = appTargets[0] ?? "";
16549
+ const target2 = readPbxObject(source, targetId);
16550
+ const productId = /\bproductReference\s*=\s*([A-F0-9]+)\b/u.exec(target2)?.[1];
16551
+ if (!/\bproductType\s*=\s*"?com\.apple\.product-type\.application"?\s*;/u.test(target2) || productId === void 0) {
16552
+ throw new Error("Existing ios App target must produce an application.");
16553
+ }
16554
+ const product = readPbxObject(source, productId);
16555
+ if (!/\bisa\s*=\s*PBXFileReference\s*;/u.test(product) || !/\b(?:explicitFileType|lastKnownFileType)\s*=\s*wrapper\.application\s*;/u.test(product) || !/\bpath\s*=\s*"?App\.app"?\s*;/u.test(product) || !/\bsourceTree\s*=\s*BUILT_PRODUCTS_DIR\s*;/u.test(product)) {
16556
+ throw new Error("Existing ios App target product must be App.app.");
16557
+ }
16558
+ return targetId;
16559
+ }
16560
+ function readIosProjectConfigurationList(source) {
16561
+ const projects = [...source.matchAll(/\b([A-F0-9]+)\s*\/\*[^*]+\*\/\s*=\s*\{/gu)].map((match) => readPbxObject(source, match[1] ?? "")).filter((block) => /\bisa\s*=\s*PBXProject;/u.test(block));
16562
+ return projects.length === 1 ? /\bbuildConfigurationList\s*=\s*([A-F0-9]+)\b/u.exec(projects[0] ?? "")?.[1] : void 0;
16563
+ }
16564
+ function readIosReleaseConfiguration(source, listId) {
16565
+ const list = readPbxObject(source, listId);
16566
+ const configurations = /\bbuildConfigurations\s*=\s*\(([^)]*)\)/u.exec(list)?.[1];
16567
+ const releaseIds = configurations === void 0 ? [] : [...configurations.matchAll(/\b([A-F0-9]+)\s*\/\*\s*Release\s*\*\//gu)].map((match) => match[1]);
16568
+ if (releaseIds.length !== 1) {
16569
+ throw new Error("Existing ios project App Release configuration is missing or ambiguous.");
16570
+ }
16571
+ return stripGradleComments(readPbxObject(source, releaseIds[0] ?? ""));
16572
+ }
16573
+ function readPbxObject(source, id) {
16574
+ const marker = new RegExp(`\\b${id}\\s*\\/\\*[^*]+\\*\\/\\s*=\\s*\\{`, "u").exec(source);
16575
+ if (marker?.index === void 0) {
16576
+ throw new Error(`Existing ios project object ${id} could not be read safely.`);
16577
+ }
16578
+ const opening = source.indexOf("{", marker.index);
16579
+ return readBracedText(source, opening);
16580
+ }
16581
+ function readBracedText(source, opening) {
16582
+ if (opening < 0) {
16583
+ throw new Error("Existing native project block is missing.");
16584
+ }
16585
+ let depth = 0;
16586
+ let quote = false;
16587
+ let blockComment = false;
16588
+ for (let index = opening; index < source.length; index += 1) {
16589
+ const character = source[index];
16590
+ const next = source[index + 1];
16591
+ if (blockComment) {
16592
+ if (character === "*" && next === "/") {
16593
+ blockComment = false;
16594
+ index += 1;
16595
+ }
16596
+ continue;
16597
+ }
16598
+ if (quote) {
16599
+ if (character === "\\") {
16600
+ index += 1;
16601
+ } else if (character === '"') {
16602
+ quote = false;
16603
+ }
16604
+ continue;
16605
+ }
16606
+ if (character === "/" && next === "*") {
16607
+ blockComment = true;
16608
+ index += 1;
16609
+ } else if (character === '"') {
16610
+ quote = true;
16611
+ } else if (character === "{") {
16612
+ depth += 1;
16613
+ } else if (character === "}") {
16614
+ depth -= 1;
16615
+ if (depth === 0) {
16616
+ return source.slice(opening, index + 1);
16617
+ }
16618
+ }
16619
+ }
16620
+ throw new Error("Existing native project block is incomplete.");
16388
16621
  }
16389
16622
 
16390
- // node_modules/.cache/mpgd-native-build/tools/target/native-release-identity.js
16391
- import { existsSync as existsSync22, readFileSync as readFileSync22 } from "node:fs";
16392
- import { dirname as dirname9, isAbsolute as isAbsolute11, join as join13, relative as relative12, resolve as resolve17 } from "node:path";
16393
-
16394
- // node_modules/.cache/mpgd-native-build/packages/cli/src/capacitor-shell-starter.js
16395
- import { chmodSync as chmodSync2, existsSync as existsSync21, lstatSync as lstatSync12, mkdirSync as mkdirSync11, readdirSync as readdirSync6, readFileSync as readFileSync21, realpathSync as realpathSync9, renameSync as renameSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync15 } from "node:fs";
16396
- import path9 from "node:path";
16397
- import { DOMParser as DOMParser2 } from "@xmldom/xmldom";
16398
- import { satisfies, valid, validRange } from "semver";
16399
-
16400
- // node_modules/.cache/mpgd-native-build/packages/cli/src/notifications-starter.js
16401
- var notificationsMessagingPlugin = "@capacitor-firebase/messaging";
16402
- var notificationsGameDependencies = Object.freeze({
16403
- [notificationsMessagingPlugin]: "8.5.2",
16404
- "@capacitor/local-notifications": "8.3.1",
16405
- firebase: "12.19.0"
16406
- });
16407
- var notificationsShellPlugins = Object.freeze([
16408
- notificationsMessagingPlugin,
16409
- "@capacitor/local-notifications"
16410
- ]);
16411
- var gatewayImport = {
16412
- anchor: "import type { RuntimeConfig } from '../runtimeDetector';\n",
16413
- replacement: [
16414
- "import { createStarterCapacitorNotifications } from '../capacitorNotifications';",
16415
- "import type { RuntimeConfig } from '../runtimeDetector';",
16416
- ""
16417
- ].join("\n")
16418
- };
16419
- var gatewayProviders = (platform) => ({
16420
- anchor: " const providers = createStoreProviders(runtime, () => gateway);\n",
16421
- replacement: [
16422
- " const providers = [",
16423
- " ...createStoreProviders(runtime, () => gateway),",
16424
- " // Notifications v2 (FCM and local reminders); `notifications/topics.json`",
16425
- " // maps the topics, and the shell needs the two plugins",
16426
- " // (`mpgd target init capacitor --providers notifications`).",
16427
- ` ...(await createStarterCapacitorNotifications('${platform}', runtime.configTarget)),`,
16428
- " ];",
16429
- ""
16430
- ].join("\n")
16431
- });
16432
- var commonEdits = {
16433
- "src/platform/buildGateways/capacitorAndroid.ts": [gatewayImport, gatewayProviders("android")],
16434
- "src/platform/buildGateways/capacitorIos.ts": [gatewayImport, gatewayProviders("ios")],
16435
- "src/platform/buildGateways/reddit.ts": [
16436
- {
16437
- anchor: " createDevvitCommerceProducts,\n createDevvitPlatformGateway,\n",
16438
- replacement: [
16439
- " createDevvitCommerceProducts,",
16440
- " createDevvitNotificationsBackend,",
16441
- " createDevvitPlatformGateway,",
16442
- ""
16443
- ].join("\n")
16444
- },
16445
- {
16446
- anchor: "import { createDevvitWebPaymentsClient } from '@mpgd/adapter-devvit/web';\n",
16447
- replacement: [
16448
- "import {",
16449
- " createDevvitWebLoginPrompt,",
16450
- " createDevvitWebPaymentsClient,",
16451
- "} from '@mpgd/adapter-devvit/web';",
16452
- ""
16453
- ].join("\n")
16454
- },
16455
- {
16456
- anchor: "import type { RuntimeConfig } from '../runtimeDetector';\n",
16457
- replacement: [
16458
- "import { setStarterHostedNotificationBackend } from '../notificationBackend';",
16459
- "import { starterNotificationTopics } from '../notifications';",
16460
- "import type { RuntimeConfig } from '../runtimeDetector';",
16461
- ""
16462
- ].join("\n")
16463
- },
16464
- {
16465
- anchor: " ...(commerce === undefined ? {} : { commerce }),\n",
16466
- replacement: [
16467
- " ...(commerce === undefined ? {} : { commerce }),",
16468
- " // Notifications v2: the game-rendered opt-in the server bridge serves",
16469
- " // (a gated beta: `integrations.notifications` stays approval-required",
16470
- " // until Reddit approves the app), and Reddit login for a logged-out",
16471
- " // player, who must log in before opting in.",
16472
- " ...notificationOptions(runtime),",
16473
- " loginPrompt: createDevvitWebLoginPrompt(),",
16474
- ""
16475
- ].join("\n")
16476
- },
16477
- {
16478
- anchor: "function createCommerceOptions(): DevvitGatewayCommerceOptions | undefined {\n",
16479
- replacement: [
16480
- "function notificationOptions(",
16481
- " runtime: RuntimeConfig,",
16482
- "): Pick<Parameters<typeof createDevvitPlatformGateway>[0], 'notifications'> {",
16483
- " const topics = starterNotificationTopics(runtime.configTarget).map((topic) => topic.id);",
16484
- " if (topics.length === 0) {",
16485
- " return {};",
16486
- " }",
16487
- " // The runtime's backend is the Devvit server (consent records, reminders, notices for the",
16488
- " // logged-in Reddit user), over the same server bridge as the gateway; created on first use.",
16489
- " setStarterHostedNotificationBackend(runtime.configTarget, () =>",
16490
- " createDevvitNotificationsBackend({ appVersion: runtime.appVersion, buildId: runtime.buildId }));",
16491
- " return { notifications: { topics } };",
16492
- "}",
16493
- "",
16494
- "function createCommerceOptions(): DevvitGatewayCommerceOptions | undefined {",
16495
- ""
16496
- ].join("\n")
16623
+ // node_modules/.cache/mpgd-native-build/packages/cli/src/native-shell-store-policy.js
16624
+ function inspectIosInfoPlistStorePolicy(plist) {
16625
+ const issues = [];
16626
+ if (lacksExportComplianceKey(plist)) {
16627
+ issues.push({
16628
+ level: "warning",
16629
+ message: "iOS Info.plist has no ITSAppUsesNonExemptEncryption: App Store Connect will ask the export-compliance question for every build and TestFlight holds the build until it is answered. Add <key>ITSAppUsesNonExemptEncryption</key><false/> when the game uses only HTTPS/OS encryption (see docs/TESTFLIGHT_INTERNAL_SUBMISSION.md)."
16630
+ });
16631
+ }
16632
+ const capabilities = readPlistTopLevelValue(plist, "UIRequiredDeviceCapabilities");
16633
+ if (capabilities !== void 0 && requires32BitArm(capabilities.xml)) {
16634
+ issues.push({
16635
+ level: "warning",
16636
+ message: "iOS Info.plist UIRequiredDeviceCapabilities lists the 32-bit armv7; use arm64."
16637
+ });
16638
+ }
16639
+ return issues;
16640
+ }
16641
+ function lacksExportComplianceKey(plist) {
16642
+ return readPlistTopLevelValue(plist, "ITSAppUsesNonExemptEncryption") === void 0;
16643
+ }
16644
+ function requires32BitArm(capabilitiesXml) {
16645
+ return /<string>\s*armv7\s*<\/string>/u.test(capabilitiesXml);
16646
+ }
16647
+ function readPlistTopLevelValue(plist, key) {
16648
+ const masked = plist.replace(/<!--[\s\S]*?-->/gu, (comment) => comment.replace(/[^\n]/gu, " "));
16649
+ const marker = new RegExp(`<key>\\s*${escapeRegExp(key)}\\s*</key>\\s*`, "u").exec(masked);
16650
+ if (marker === null) {
16651
+ return void 0;
16652
+ }
16653
+ const start = marker.index + marker[0].length;
16654
+ const open2 = /^<([A-Za-z]+)\s*(\/?)>/u.exec(masked.slice(start));
16655
+ if (open2 === null) {
16656
+ throw new Error(`Info.plist key ${key} has no readable value.`);
16657
+ }
16658
+ const tag = open2[1] ?? "";
16659
+ if (open2[2] === "/") {
16660
+ const end = start + open2[0].length;
16661
+ return { start, end, xml: plist.slice(start, end) };
16662
+ }
16663
+ let depth = 0;
16664
+ const tags = new RegExp(`<(/?)${tag}\\b[^>]*?(/?)>`, "gu");
16665
+ tags.lastIndex = start;
16666
+ for (let match = tags.exec(masked); match !== null; match = tags.exec(masked)) {
16667
+ if (match[2] === "/") {
16668
+ continue;
16497
16669
  }
16498
- ],
16499
- "apps/target-devvit/src/server/index.ts": [
16500
- {
16501
- anchor: "import { context, createServer, getServerPort, payments, reddit, redis } from '@devvit/web/server';\n",
16502
- replacement: [
16503
- "import {",
16504
- " context,",
16505
- " createServer,",
16506
- " getServerPort,",
16507
- " notifications,",
16508
- " payments,",
16509
- " reddit,",
16510
- " redis,",
16511
- "} from '@devvit/web/server';",
16512
- ""
16513
- ].join("\n")
16514
- },
16515
- {
16516
- anchor: [
16517
- "import {",
16518
- " createDevvitCommerceBridgeProducts,",
16519
- " createDevvitCommerceV2BridgeHandler,",
16520
- "} from '@mpgd/adapter-devvit/server';",
16521
- ""
16522
- ].join("\n"),
16523
- replacement: [
16524
- "import {",
16525
- " createDevvitCommerceBridgeProducts,",
16526
- " createDevvitCommerceV2BridgeHandler,",
16527
- " createDevvitNotificationService,",
16528
- " createDevvitNotificationsBackendBridgeHandler,",
16529
- " createDevvitNotificationsV2BridgeHandler,",
16530
- "} from '@mpgd/adapter-devvit/server';",
16531
- ""
16532
- ].join("\n")
16533
- },
16534
- {
16535
- anchor: "import type { ProductCatalog } from '@mpgd/catalog';\n",
16536
- replacement: [
16537
- "import { resolveNotificationTopicsForTarget, type NotificationTopicCatalog, type ProductCatalog } from '@mpgd/catalog';",
16538
- "import { assertNotificationDeployment } from '@mpgd/platform/notifications';",
16539
- ""
16540
- ].join("\n")
16541
- },
16542
- {
16543
- anchor: "import devvitManifest from '../../devvit.json';\n",
16544
- replacement: [
16545
- "import notificationDeploymentJson from '../../../../notifications/deployment.json';",
16546
- "import notificationTopicsJson from '../../../../notifications/topics.json';",
16547
- "import devvitManifest from '../../devvit.json';",
16548
- ""
16549
- ].join("\n")
16550
- },
16551
- {
16552
- anchor: [
16553
- "const handleBridgeRequest = createDevvitBridgeHandler({",
16554
- " redis,",
16555
- " currentPlayerId,",
16556
- " currentDisplayName,",
16557
- " storageKeyNamespace: gameName,",
16558
- " ...(commerce === undefined ? {} : { commerce }),",
16559
- "});",
16560
- ""
16561
- ].join("\n"),
16562
- replacement: [
16563
- "/**",
16564
- " * Notifications v2 (`notifications/topics.json`, mapped for the `reddit`",
16565
- " * target): the game-rendered opt-in for the logged-in requester, and the",
16566
- " * service that records consent and sends server reminders over the Devvit",
16567
- " * notifications plugin. Devvit notifications are a gated beta: the game",
16568
- " * shows them only once `integrations.notifications` is `available`.",
16569
- " */",
16570
- "const notificationTopicCatalog = notificationTopicsJson as NotificationTopicCatalog;",
16571
- "const redditNotificationTopics = readRedditNotificationTopics();",
16572
- "const notificationService: ReturnType<typeof createDevvitNotificationService>['service'] | undefined =",
16573
- " redditNotificationTopics.length === 0 ? undefined : createNotificationService();",
16574
- "// The opt-in is served only with the service that sends; a refused catalog turns both off.",
16575
- "const notificationsBridge = notificationService === undefined",
16576
- " ? undefined",
16577
- " : createDevvitNotificationsV2BridgeHandler({",
16578
- " notifications,",
16579
- " currentUserId: currentPlayerId,",
16580
- " topics: redditNotificationTopics,",
16581
- " });",
16582
- "// The webview runtime's backend: consent records, reminders, and notices for the request's user.",
16583
- "const notificationsBackend = notificationService === undefined",
16584
- " ? undefined",
16585
- " : createDevvitNotificationsBackendBridgeHandler({ service: notificationService, context: () => context });",
16586
- "const handleBridgeRequest = createDevvitBridgeHandler({",
16587
- " redis,",
16588
- " currentPlayerId,",
16589
- " currentDisplayName,",
16590
- " storageKeyNamespace: gameName,",
16591
- " ...(commerce === undefined ? {} : { commerce }),",
16592
- " ...(notificationsBridge === undefined ? {} : { notifications: notificationsBridge }),",
16593
- " ...(notificationsBackend === undefined ? {} : { notificationsBackend }),",
16594
- "});",
16595
- ""
16596
- ].join("\n")
16597
- },
16598
- {
16599
- anchor: [
16600
- "/**",
16601
- " * The notifications v2 service (`createDevvitNotificationService` from",
16602
- " * `@mpgd/adapter-devvit/server`). None until the game declares a topic",
16603
- " * catalog, so the scheduler tasks below do no work.",
16604
- " */",
16605
- "const notificationService: Parameters<typeof handleNotificationTask>[0]['notifications'] = undefined;",
16606
- ""
16607
- ].join("\n"),
16608
- replacement: [
16609
- "/** The topic ids the catalog maps for Reddit; none when the catalog cannot be read. */",
16610
- "function readRedditNotificationTopics(): readonly string[] {",
16611
- " try {",
16612
- " return resolveNotificationTopicsForTarget(notificationTopicCatalog, 'reddit').map((topic) => topic.id);",
16613
- " } catch (error) {",
16614
- " console.error(`devvit notifications disabled: ${errorMessage(error)}`);",
16615
- " return [];",
16616
- " }",
16617
- "}",
16618
- "",
16619
- "/**",
16620
- " * The notifications v2 service over Redis and the Devvit plugin; the",
16621
- " * scheduler tasks below run its reminders and purges. A catalog or",
16622
- " * deployment the service refuses turns notifications off, never the game",
16623
- " * server.",
16624
- " */",
16625
- "function createNotificationService(): ReturnType<typeof createDevvitNotificationService>['service'] | undefined {",
16626
- " try {",
16627
- " return createDevvitNotificationService({",
16628
- " redis,",
16629
- " notifications,",
16630
- " catalog: notificationTopicCatalog,",
16631
- " deployment: assertNotificationDeployment(notificationDeploymentJson),",
16632
- " }).service;",
16633
- " } catch (error) {",
16634
- " console.error(`devvit notifications disabled: ${errorMessage(error)}`);",
16635
- " return undefined;",
16636
- " }",
16637
- "}",
16638
- ""
16639
- ].join("\n")
16670
+ depth += match[1] === "/" ? -1 : 1;
16671
+ if (depth === 0) {
16672
+ const end = match.index + match[0].length;
16673
+ return { start, end, xml: plist.slice(start, end) };
16640
16674
  }
16641
- ]
16642
- };
16643
- var phaserEdits = {
16644
- "src/runtime/createGame.ts": [
16645
- {
16646
- anchor: "import { LobbyScene } from '../scenes/LobbyScene';\n",
16647
- replacement: [
16648
- "import { LobbyScene } from '../scenes/LobbyScene';",
16649
- "import {",
16650
- " NotificationSettingsScene,",
16651
- " routeNotificationSettingsOpens,",
16652
- "} from '../scenes/NotificationSettingsScene';",
16653
- ""
16654
- ].join("\n")
16655
- },
16656
- {
16657
- anchor: " scene: [BootScene, LobbyScene, PlayScene],\n",
16658
- replacement: " scene: [BootScene, LobbyScene, PlayScene, NotificationSettingsScene],\n"
16659
- },
16660
- {
16661
- anchor: " ownGameAudio(game, input.context);\n",
16662
- replacement: [
16663
- " ownGameAudio(game, input.context);",
16664
- " // The advertising opt-out action and consent notices open the settings screen.",
16665
- " routeNotificationSettingsOpens(game, input.context);",
16666
- ""
16667
- ].join("\n")
16675
+ }
16676
+ throw new Error(`Info.plist key ${key} has an unclosed value.`);
16677
+ }
16678
+ function escapeRegExp(value) {
16679
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
16680
+ }
16681
+ var nativeShellOrientations = ["portrait", "landscape", "any"];
16682
+ function readGameNativeOrientation(gameConfig) {
16683
+ if (!isRecord5(gameConfig) || gameConfig.native === void 0) {
16684
+ return void 0;
16685
+ }
16686
+ if (!isRecord5(gameConfig.native)) {
16687
+ throw new Error("mpgd.game.json native must be an object when present.");
16688
+ }
16689
+ for (const key of Object.keys(gameConfig.native)) {
16690
+ if (key !== "orientation") {
16691
+ throw new Error(`mpgd.game.json native.${key} is not supported.`);
16668
16692
  }
16669
- ],
16670
- "src/scenes/LobbyScene.ts": [
16671
- {
16672
- anchor: "import { starterContextKey, type StarterContext } from '../runtime/gameContext';\n",
16673
- replacement: [
16674
- "import { starterContextKey, type StarterContext } from '../runtime/gameContext';",
16675
- "import { notificationSettingsSceneKey } from './NotificationSettingsScene';",
16676
- ""
16677
- ].join("\n")
16678
- },
16679
- {
16680
- anchor: " this.input.once('pointerdown', () => this.scene.start('PlayScene'));\n",
16681
- replacement: [
16682
- " // The settings button shows only where this target has notifications.",
16683
- " const notificationsButton = context.platform.notificationsV2 === undefined",
16684
- " ? undefined",
16685
- " : this.add",
16686
- " .text(940, 16, locale === 'ko' ? '\uC54C\uB9BC \uC124\uC815' : 'Notifications', {",
16687
- " color: '#5eead4',",
16688
- " fontFamily: 'Arial, sans-serif',",
16689
- " fontSize: '22px',",
16690
- " })",
16691
- " .setOrigin(1, 0)",
16692
- " .setPadding(16, 12, 16, 12)",
16693
- " .setInteractive({ useHandCursor: true });",
16694
- " notificationsButton?.on('pointerup', () => this.scene.start(notificationSettingsSceneKey));",
16695
- " // A tap anywhere else starts the game; the scene drops this listener on shutdown.",
16696
- " this.input.on('pointerdown', (_pointer: Phaser.Input.Pointer, over: Phaser.GameObjects.GameObject[]) => {",
16697
- " if (notificationsButton === undefined || !over.includes(notificationsButton)) {",
16698
- " this.scene.start('PlayScene');",
16699
- " }",
16700
- " });",
16701
- ""
16702
- ].join("\n")
16693
+ }
16694
+ const orientation = gameConfig.native.orientation;
16695
+ if (orientation === void 0) {
16696
+ return void 0;
16697
+ }
16698
+ if (!nativeShellOrientations.includes(orientation)) {
16699
+ throw new Error("mpgd.game.json native.orientation must be 'portrait', 'landscape' or 'any'.");
16700
+ }
16701
+ return orientation;
16702
+ }
16703
+ function readIosDeviceFamily(metadata) {
16704
+ if (!isRecord5(metadata) || metadata.iosDeviceFamily === void 0) {
16705
+ return void 0;
16706
+ }
16707
+ if (metadata.iosDeviceFamily !== "universal" && metadata.iosDeviceFamily !== "iphone") {
16708
+ throw new Error("ios metadata.iosDeviceFamily must be 'universal' or 'iphone'.");
16709
+ }
16710
+ return metadata.iosDeviceFamily;
16711
+ }
16712
+ var iosPortrait = "UIInterfaceOrientationPortrait";
16713
+ var iosPortraitUpsideDown = "UIInterfaceOrientationPortraitUpsideDown";
16714
+ var iosLandscape = [
16715
+ "UIInterfaceOrientationLandscapeLeft",
16716
+ "UIInterfaceOrientationLandscapeRight"
16717
+ ];
16718
+ var iosIpadOrientations = [
16719
+ iosPortrait,
16720
+ iosPortraitUpsideDown,
16721
+ ...iosLandscape
16722
+ ];
16723
+ function readPlistStrings(plist, key) {
16724
+ const value = readPlistTopLevelValue(plist, key);
16725
+ if (value === void 0) {
16726
+ return void 0;
16727
+ }
16728
+ return [...value.xml.matchAll(/<string>\s*([^<]*?)\s*<\/string>/gu)].map((match) => match[1] ?? "");
16729
+ }
16730
+ function inspectIosInfoPlistOrientation(plist, orientation, deviceFamily) {
16731
+ const issues = [];
16732
+ if (orientation !== void 0) {
16733
+ const listed = readPlistStrings(plist, "UISupportedInterfaceOrientations");
16734
+ const iphone = listed ?? iosIpadOrientations;
16735
+ const portrait = iphone.some((value) => value === iosPortrait || value === iosPortraitUpsideDown);
16736
+ const landscape = iphone.some((value) => iosLandscape.includes(value));
16737
+ const matchesByOrientation = {
16738
+ portrait: portrait && !landscape,
16739
+ landscape: landscape && !portrait,
16740
+ any: portrait && landscape
16741
+ };
16742
+ const matches = matchesByOrientation[orientation];
16743
+ if (!matches) {
16744
+ issues.push({
16745
+ level: "error",
16746
+ message: `iOS Info.plist UISupportedInterfaceOrientations (${listed === void 0 ? "key missing, so every orientation" : iphone.join(", ") || "none"}) does not match mpgd.game.json native.orientation "${orientation}".`
16747
+ });
16703
16748
  }
16704
- ]
16705
- };
16706
- var liteEdits = {
16707
- "src/runtime/createGame.ts": [
16708
- {
16709
- anchor: "import { createTapGame } from '../lite/tapGame';\n",
16710
- replacement: [
16711
- "import { mountNotificationSettingsPanel } from '../lite/notificationSettingsPanel';",
16712
- "import { createTapGame } from '../lite/tapGame';",
16713
- ""
16714
- ].join("\n")
16715
- },
16716
- {
16717
- anchor: " const shell = createTapGame({ mount, title: '__GAME_TITLE__' });\n",
16718
- replacement: [
16719
- " const shell = createTapGame({ mount, title: '__GAME_TITLE__' });",
16720
- " const disposeNotificationSettings = mountNotificationSettingsPanel(mount, input.context);",
16721
- ""
16722
- ].join("\n")
16723
- },
16724
- {
16725
- anchor: " audio.dispose();\n",
16726
- replacement: [" audio.dispose();", " disposeNotificationSettings();", ""].join("\n")
16749
+ }
16750
+ if (deviceFamily === "universal") {
16751
+ const ipad = readPlistStrings(plist, "UISupportedInterfaceOrientations~ipad") ?? readPlistStrings(plist, "UISupportedInterfaceOrientations") ?? iosIpadOrientations;
16752
+ if (iosIpadOrientations.some((value) => !ipad.includes(value))) {
16753
+ issues.push({
16754
+ level: "warning",
16755
+ message: "iOS Info.plist iPad orientations (UISupportedInterfaceOrientations~ipad, or UISupportedInterfaceOrientations when that key is absent) do not list all four; Apple asks iPad apps to support every orientation (TN3192) and deprecated UIRequiresFullScreen in iPadOS 26."
16756
+ });
16727
16757
  }
16728
- ]
16758
+ }
16759
+ return issues;
16760
+ }
16761
+ function iosTargetedDeviceFamily(family) {
16762
+ return family === "iphone" ? "1" : "1,2";
16763
+ }
16764
+ function readIosAppReleaseDeviceFamily(project) {
16765
+ for (const id of readAppTargetConfigurationIds(project)) {
16766
+ const settings = locatePbxBuildSettings(project, id);
16767
+ if (settings.name !== "Release") {
16768
+ continue;
16769
+ }
16770
+ const value = /\bTARGETED_DEVICE_FAMILY\s*=\s*([^;]+);/u.exec(project.slice(settings.start, settings.end))?.[1];
16771
+ return value?.trim().replace(/^"(.*)"$/u, "$1").replace(/\s+/gu, "");
16772
+ }
16773
+ return void 0;
16774
+ }
16775
+ function inspectIosProjectDeviceFamily(project, family) {
16776
+ if (family === void 0) {
16777
+ return [];
16778
+ }
16779
+ const actual = readIosAppReleaseDeviceFamily(project);
16780
+ const expected = iosTargetedDeviceFamily(family);
16781
+ if (actual === expected) {
16782
+ return [];
16783
+ }
16784
+ return [{
16785
+ level: "error",
16786
+ message: `iOS App target Release TARGETED_DEVICE_FAMILY is ${actual ?? "unset"}, but mpgd.targets.json ios metadata.iosDeviceFamily "${family}" needs "${expected}".`
16787
+ }];
16788
+ }
16789
+ var androidOrientationValues = {
16790
+ portrait: ["portrait", "sensorPortrait", "userPortrait", "reversePortrait"],
16791
+ landscape: ["landscape", "sensorLandscape", "userLandscape", "reverseLandscape"],
16792
+ any: ["unspecified", "user", "sensor", "fullSensor", "fullUser"]
16729
16793
  };
16730
- var readmeSection = [
16731
- "",
16732
- "## Notifications",
16733
- "",
16734
- "This game was created with `--providers notifications`. Its topics live in",
16735
- "`notifications/topics.json` and the backend settings (`advertisingRules`,",
16736
- "the advertising sender) in `notifications/deployment.json`; run",
16737
- "`pnpm validate:notifications` after every change and before every release.",
16738
- "Replace the sender contact placeholder in `notifications/deployment.json`",
16739
- "before any advertising is sent. The notification settings screen asks for the",
16740
- "OS or host permission and records reminder, advertising (\uAD11\uACE0\uC131 \uC815\uBCF4",
16741
- "\uC218\uC2E0\uB3D9\uC758), and night consent through the notifications runtime. See",
16742
- "`docs/NOTIFICATIONS.md` and, for Android and iOS, the mpgd-kit Firebase setup",
16743
- "guide; `mpgd target init capacitor --providers notifications` installs the two",
16744
- "Capacitor plugins into a game-owned shell.",
16745
- ""
16746
- ].join("\n");
16747
- var notificationsCapacitorGateways = Object.freeze({
16748
- android: "src/platform/buildGateways/capacitorAndroid.ts",
16749
- ios: "src/platform/buildGateways/capacitorIos.ts"
16750
- });
16751
- var notificationsCapacitorGatewayModules = Object.freeze([
16752
- "src/platform/capacitorNotifications.ts",
16753
- "src/platform/notifications.ts",
16754
- "src/platform/notificationBackend.ts",
16755
- "src/platform/notificationSettings.ts",
16756
- "src/platform/notificationOpens.ts"
16757
- ]);
16758
- var notificationsCapacitorSupportFiles = Object.freeze([
16759
- ...notificationsCapacitorGatewayModules,
16760
- "notifications/topics.json",
16761
- "notifications/deployment.json",
16762
- "tools/validate-notifications.ts",
16763
- "docs/NOTIFICATIONS.md"
16764
- ]);
16765
- var notificationsCapacitorTemplateSupportFiles = Object.freeze([
16766
- "src/platform/serverSession.ts",
16767
- "src/platform/developmentServerSession.ts"
16768
- ]);
16769
- var notificationsCapacitorTemplateSupportImporters = Object.freeze({
16770
- "src/platform/developmentServerSession.ts": {
16771
- importer: "src/platform/serverSession.ts",
16772
- requires: { packageName: "@mpgd/game-services", exportName: "createDevelopmentServerSession" },
16773
- warning: "src/platform/serverSession.ts predates development notification sessions (VITE_MPGD_DEV_NOTIFICATION_SESSION); to try notifications locally, copy serverSession.ts and developmentServerSession.ts from the starter template."
16794
+ function inspectAndroidManifestOrientation(manifest, orientation) {
16795
+ if (orientation === void 0) {
16796
+ return [];
16774
16797
  }
16775
- });
16776
-
16777
- // node_modules/.cache/mpgd-native-build/packages/cli/src/native-shell-identity.js
16778
- function assertAndroidSettingsAppProject(source) {
16779
- const clean = stripGradleComments(source);
16780
- const code = maskGradleStrings(clean);
16781
- const includes = [...clean.matchAll(/\binclude\s*(?:\(([^)]*)\)|([^\r\n;]+))/gu)];
16782
- const includesApp = includes.some((match) => code.slice(match.index, match.index + 7) === "include" && /(?:^|,)\s*["']:app["']\s*(?:,|$)/u.test(match[1] ?? match[2] ?? ""));
16783
- if (!includesApp) {
16784
- throw new Error("Android settings must include :app at android/app without remapping it.");
16798
+ const activity = findAndroidLauncherActivity(manifest);
16799
+ if (activity === void 0) {
16800
+ return [{ level: "error", message: "Android manifest has no launcher activity." }];
16785
16801
  }
16786
- assertAndroidSettingsNoAppRemap(clean);
16802
+ const value = readXmlAttribute(activity.tag, "android:screenOrientation") ?? "unspecified";
16803
+ const issues = [];
16804
+ if (!androidOrientationValues[orientation].includes(value)) {
16805
+ issues.push({
16806
+ level: "error",
16807
+ message: `Android launcher activity android:screenOrientation="${value}" does not match mpgd.game.json native.orientation "${orientation}".`
16808
+ });
16809
+ }
16810
+ const application = /<application\b[^>]*?>/u.exec(maskXmlComments(manifest))?.[0] ?? "";
16811
+ if (orientation !== "any" && readXmlAttribute(application, "android:appCategory") !== "game") {
16812
+ issues.push({
16813
+ level: "warning",
16814
+ message: 'Android <application> has no android:appCategory="game": Android 16 ignores the orientation lock on tablets and foldables for apps targeting API 36 unless they are games.'
16815
+ });
16816
+ }
16817
+ return issues;
16787
16818
  }
16788
- function assertAndroidSettingsNoAppRemap(source) {
16789
- const clean = stripGradleComments(source);
16790
- const code = maskGradleStrings(clean);
16791
- const appRemap = [...clean.matchAll(/\b(project|findProject)\s*\(\s*["']:app["']\s*\)/gu)].some((match) => code.slice(match.index, match.index + (match[1]?.length ?? 0)) === match[1]);
16792
- if (appRemap) {
16793
- throw new Error("Android settings must include :app at android/app without remapping it.");
16819
+ function findAndroidLauncherActivity(manifest) {
16820
+ const elements2 = readAndroidActivityElements(manifest, "activity");
16821
+ const direct = elements2.find((element) => element.launcher);
16822
+ if (direct !== void 0) {
16823
+ return { start: direct.start, tag: direct.tag };
16824
+ }
16825
+ const alias = readAndroidActivityElements(manifest, "activity-alias").find((element) => element.launcher);
16826
+ const target2 = alias === void 0 ? void 0 : readXmlAttribute(alias.tag, "android:targetActivity");
16827
+ if (target2 === void 0) {
16828
+ return void 0;
16829
+ }
16830
+ const sameClass = (name) => name === target2 || target2.startsWith(".") && name.endsWith(target2) || name.startsWith(".") && target2.endsWith(name) || !name.includes(".") && name !== "" && target2.endsWith(`.${name}`) || !target2.includes(".") && name.endsWith(`.${target2}`);
16831
+ const match = elements2.filter((element) => sameClass(readXmlAttribute(element.tag, "android:name") ?? ""));
16832
+ return match.length === 1 && match[0] !== void 0 ? { start: match[0].start, tag: match[0].tag } : void 0;
16833
+ }
16834
+ function readAndroidActivityElements(manifest, element) {
16835
+ const masked = maskXmlComments(manifest);
16836
+ const openings = new RegExp(`<${element}(?=[\\s/>])(?:"[^"]*"|'[^']*'|[^>"'])*?(\\/?)>`, "gu");
16837
+ const found = [];
16838
+ for (const opening of masked.matchAll(openings)) {
16839
+ const start = opening.index ?? 0;
16840
+ const tagEnd = start + opening[0].length;
16841
+ const tag = manifest.slice(start, tagEnd);
16842
+ if (opening[1] === "/") {
16843
+ found.push({ start, tag, launcher: false });
16844
+ continue;
16845
+ }
16846
+ const close = masked.indexOf(`</${element}>`, tagEnd);
16847
+ const body = close === -1 ? "" : masked.slice(tagEnd, close);
16848
+ found.push({ start, tag, launcher: /android\.intent\.category\.LAUNCHER/u.test(body) });
16794
16849
  }
16850
+ return found;
16795
16851
  }
16796
- function stripGradleComments(source) {
16797
- let output = "";
16798
- let quote;
16799
- for (let index = 0; index < source.length; index += 1) {
16800
- const character = source[index] ?? "";
16801
- const next = source[index + 1];
16802
- if (quote !== void 0) {
16803
- output += character;
16804
- if (character === "\\" && next !== void 0) {
16805
- output += next;
16852
+ function maskXmlComments(xml) {
16853
+ return xml.replace(/<!--[\s\S]*?-->/gu, (comment) => comment.replace(/[^\n]/gu, " "));
16854
+ }
16855
+ function xmlAttribute(name) {
16856
+ return new RegExp(`(\\s+)${escapeRegExp(name)}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`, "u");
16857
+ }
16858
+ function readXmlAttribute(tag, name) {
16859
+ const match = xmlAttribute(name).exec(tag);
16860
+ return match === null ? void 0 : match[2] ?? match[3];
16861
+ }
16862
+ function readAppTargetConfigurationIds(project) {
16863
+ const target2 = findPbxObject(project, (body) => /\bisa\s*=\s*PBXNativeTarget;/u.test(body) && /\bname\s*=\s*"?App"?\s*;/u.test(body));
16864
+ const listId = /\bbuildConfigurationList\s*=\s*([A-F0-9]+)\b/u.exec(target2?.text ?? "")?.[1];
16865
+ const list = listId === void 0 ? void 0 : findPbxObjectById(project, listId);
16866
+ const ids = list === void 0 ? void 0 : readPbxList(project, list, "buildConfigurations")?.ids;
16867
+ if (ids === void 0) {
16868
+ throw new Error("Xcode project has no App target build configuration list.");
16869
+ }
16870
+ return ids;
16871
+ }
16872
+ function locatePbxBuildSettings(project, id) {
16873
+ const marker = new RegExp(`\\b${id}\\s*(?:\\/\\*[^*]*\\*\\/)?\\s*=\\s*\\{`, "u").exec(project);
16874
+ if (marker === null) {
16875
+ throw new Error(`Xcode build configuration ${id} is missing.`);
16876
+ }
16877
+ const object = readBraced(project, project.indexOf("{", marker.index));
16878
+ const name = /\bname\s*=\s*"?([^";]+)"?\s*;/u.exec(object.text.replace(/\bbuildSettings\s*=\s*\{[\s\S]*\}\s*;/u, ""))?.[1] ?? "";
16879
+ const settingsMarker = /\bbuildSettings\s*=\s*\{/u.exec(object.text);
16880
+ if (settingsMarker === null) {
16881
+ throw new Error(`Xcode build configuration ${id} has no build settings.`);
16882
+ }
16883
+ const settings = readBraced(project, object.start + settingsMarker.index + settingsMarker[0].length - 1);
16884
+ return { name: name.trim(), start: settings.start, end: settings.end };
16885
+ }
16886
+ function readBraced(source, open2) {
16887
+ let depth = 0;
16888
+ let quote = false;
16889
+ for (let index = open2; index < source.length; index += 1) {
16890
+ const character = source[index];
16891
+ if (quote) {
16892
+ if (character === "\\") {
16806
16893
  index += 1;
16807
- } else if (character === quote) {
16808
- quote = void 0;
16894
+ } else if (character === '"') {
16895
+ quote = false;
16809
16896
  }
16810
16897
  continue;
16811
16898
  }
16812
- if (character === '"' || character === "'") {
16813
- quote = character;
16814
- output += character;
16899
+ if (character === "/" && source[index + 1] === "*") {
16900
+ const close = source.indexOf("*/", index + 2);
16901
+ index = close === -1 ? source.length : close + 1;
16815
16902
  continue;
16816
16903
  }
16817
- if (character === "/" && next === "/") {
16818
- index += 2;
16819
- while (index < source.length && source[index] !== "\n") {
16820
- index += 1;
16904
+ if (character === '"') {
16905
+ quote = true;
16906
+ } else if (character === "{") {
16907
+ depth += 1;
16908
+ } else if (character === "}") {
16909
+ depth -= 1;
16910
+ if (depth === 0) {
16911
+ return { text: source.slice(open2 + 1, index), start: open2 + 1, end: index };
16821
16912
  }
16822
- output += "\n";
16913
+ }
16914
+ }
16915
+ throw new Error("Xcode project has an unclosed object.");
16916
+ }
16917
+ function isRecord5(value) {
16918
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16919
+ }
16920
+ var iosAppPrivacyManifestFile = "PrivacyInfo.xcprivacy";
16921
+ var iosAppPrivacyManifestXml = [
16922
+ '<?xml version="1.0" encoding="UTF-8"?>',
16923
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
16924
+ '<plist version="1.0">',
16925
+ "<dict>",
16926
+ " <key>NSPrivacyTracking</key>",
16927
+ " <false/>",
16928
+ " <key>NSPrivacyTrackingDomains</key>",
16929
+ " <array/>",
16930
+ " <key>NSPrivacyCollectedDataTypes</key>",
16931
+ " <array/>",
16932
+ " <key>NSPrivacyAccessedAPITypes</key>",
16933
+ " <array/>",
16934
+ "</dict>",
16935
+ "</plist>",
16936
+ ""
16937
+ ].join("\n");
16938
+ function iosAppBundlesResource(project, fileName) {
16939
+ return findAppResourcesPhase(project)?.text.includes(`/* ${fileName} in Resources */`) === true;
16940
+ }
16941
+ function inspectIosAppPrivacyManifest(project) {
16942
+ if (iosAppBundlesResource(project, iosAppPrivacyManifestFile)) {
16943
+ return [];
16944
+ }
16945
+ return [{
16946
+ level: "warning",
16947
+ message: `iOS App target does not bundle an app-level ${iosAppPrivacyManifestFile}; add one to the App group and its Resources phase (the kit reference shell has one) and declare any required-reason API the game's own native code uses.`
16948
+ }];
16949
+ }
16950
+ function findAppResourcesPhase(project) {
16951
+ const target2 = findPbxObject(project, (body) => /\bisa\s*=\s*PBXNativeTarget;/u.test(body) && /\bname\s*=\s*"?App"?\s*;/u.test(body));
16952
+ if (target2 === void 0) {
16953
+ return void 0;
16954
+ }
16955
+ for (const id of readPbxList(project, target2, "buildPhases")?.ids ?? []) {
16956
+ const phase = findPbxObjectById(project, id);
16957
+ if (phase === void 0 || !/\bisa\s*=\s*PBXResourcesBuildPhase\s*;/u.test(phase.text)) {
16823
16958
  continue;
16824
16959
  }
16825
- if (character === "/" && next === "*") {
16826
- index += 2;
16827
- while (index < source.length && !(source[index] === "*" && source[index + 1] === "/")) {
16828
- output += source[index] === "\n" ? "\n" : " ";
16960
+ const files = readPbxList(project, phase, "files");
16961
+ return files === void 0 ? void 0 : { text: files.text, filesEnd: files.end };
16962
+ }
16963
+ return void 0;
16964
+ }
16965
+ function findPbxObject(project, accept) {
16966
+ for (const match of project.matchAll(/\b([A-F0-9]+)\s*(?:\/\*[^*]*\*\/)?\s*=\s*\{/gu)) {
16967
+ const body = readBraced(project, project.indexOf("{", match.index ?? 0));
16968
+ if (accept(body.text)) {
16969
+ return { text: body.text, start: body.start };
16970
+ }
16971
+ }
16972
+ return void 0;
16973
+ }
16974
+ function findPbxObjectById(project, id) {
16975
+ const marker = new RegExp(`\\b${id}\\s*(?:\\/\\*[^*]*\\*\\/)?\\s*=\\s*\\{`, "u").exec(project);
16976
+ if (marker === null) {
16977
+ return void 0;
16978
+ }
16979
+ const body = readBraced(project, project.indexOf("{", marker.index));
16980
+ return { text: body.text, start: body.start };
16981
+ }
16982
+ function readPbxList(project, object, key) {
16983
+ const marker = new RegExp(`\\b${key}\\s*=\\s*\\(`, "u").exec(object.text);
16984
+ if (marker === null) {
16985
+ return void 0;
16986
+ }
16987
+ const start = object.start + marker.index + marker[0].length;
16988
+ let quote = false;
16989
+ for (let index = start; index < project.length; index += 1) {
16990
+ const character = project[index];
16991
+ if (quote) {
16992
+ if (character === "\\") {
16829
16993
  index += 1;
16994
+ } else if (character === '"') {
16995
+ quote = false;
16830
16996
  }
16831
- index += 1;
16832
16997
  continue;
16833
16998
  }
16834
- output += character;
16999
+ if (character === "/" && project[index + 1] === "*") {
17000
+ const close = project.indexOf("*/", index + 2);
17001
+ index = close === -1 ? project.length : close + 1;
17002
+ continue;
17003
+ }
17004
+ if (character === '"') {
17005
+ quote = true;
17006
+ } else if (character === ")") {
17007
+ const text = project.slice(start, index);
17008
+ const ids = text.replace(/\/\*[^]*?\*\//gu, " ").split(",").map((entry) => /^\s*([A-F0-9]+)\b/u.exec(entry)?.[1]).filter((id) => id !== void 0);
17009
+ return { text, ids, end: index };
17010
+ }
16835
17011
  }
16836
- return output;
17012
+ return void 0;
16837
17013
  }
16838
- function hasGradleIdentityMutation(source) {
16839
- const clean = stripGradleComments(source);
16840
- const masked = maskGradleStrings(clean);
16841
- if (hasGradlePropertySetter(clean) || hasGradleBracketIdentityWrite(clean)) {
16842
- return true;
17014
+ var admobCapacitorPlugin = "@capacitor-community/admob";
17015
+ var androidAdIdPermission = "com.google.android.gms.permission.AD_ID";
17016
+ function shellUsesAdMob(shellPackage) {
17017
+ if (!isRecord5(shellPackage)) {
17018
+ return false;
16843
17019
  }
16844
- const setter = /\bset(?:ApplicationId|ApplicationIdSuffix|VersionCode|VersionName|VersionNameSuffix)\s*\(/u;
16845
- const field = "(?:applicationId|applicationIdSuffix|versionCode|versionName|versionNameSuffix)";
16846
- const assignment = new RegExp(`\\b${field}(?:\\s*(?:\\+?=|\\(|["'\\d]|\\.set\\s*\\()|[ \\t]+[A-Za-z_$])`, "u");
16847
- return setter.test(masked) || assignment.test(masked);
17020
+ return [shellPackage.dependencies, shellPackage.devDependencies].some((dependencies) => isRecord5(dependencies) && dependencies[admobCapacitorPlugin] !== void 0);
16848
17021
  }
16849
- function hasGradleBracketIdentityWrite(source) {
16850
- const clean = stripGradleComments(source);
16851
- const code = maskGradleStrings(clean);
16852
- const key = "(?:applicationId|applicationIdSuffix|versionCode|versionName|versionNameSuffix)";
16853
- const bracketWrite = `\\[\\s*["']${key}["']\\s*\\]\\s*(?:\\+?=|\\.\\s*set\\s*\\(|\\()`;
16854
- const expression = new RegExp(bracketWrite, "gu");
16855
- return [...clean.matchAll(expression)].some((match) => code[match.index] === "[");
17022
+ function adIdPermissions(manifest) {
17023
+ const masked = maskXmlComments(manifest);
17024
+ return [...masked.matchAll(/<uses-permission(?=[\s/>])[^>]*?\/?>(?:\s*<\/uses-permission>)?/gu)].filter((match) => readXmlAttribute(match[0], "android:name") === androidAdIdPermission);
16856
17025
  }
16857
- function hasAndroidDisplayNameResourceOverride(source) {
16858
- const clean = stripGradleComments(source);
16859
- const code = maskGradleStrings(clean);
16860
- return [...clean.matchAll(/\bresValue\b/gu)].some((match) => {
16861
- if (code.slice(match.index, match.index + 8) !== "resValue") {
16862
- return false;
16863
- }
16864
- const tail = clean.slice(match.index);
16865
- const literal = /^resValue\s*(?:\(\s*)?["'][^"']+["']\s*,\s*["']([^"']+)["']/u.exec(tail);
16866
- return literal === null || literal[1] === "app_name";
16867
- });
16868
- }
16869
- function hasAndroidResourceSourceSetOverride(source) {
16870
- const code = maskGradleStrings(stripGradleComments(source));
16871
- return /\bsourceSets\b/u.test(code) && /\bres\b/u.test(code);
16872
- }
16873
- function hasAndroidManifestSourceSetOverride(source) {
16874
- const code = maskGradleStrings(stripGradleComments(source));
16875
- return /\bsourceSets\b/u.test(code) && /\bmanifest\b/u.test(code);
16876
- }
16877
- function hasGradleTaskAction(source) {
16878
- const code = maskGradleStrings(stripGradleComments(source));
16879
- const withoutStandardClean = code.replace(/\btask\s+clean\s*\(\s*type\s*:\s*Delete\s*\)\s*\{\s*delete\s+rootProject\.buildDir\s*;?\s*\}/gu, "");
16880
- return /\b(?:doFirst|doLast|whenTaskAdded|whenReady)\s*(?:\(|\{)/u.test(withoutStandardClean) || /\btasks\s*(?:\.|\[|\{)/u.test(withoutStandardClean) || /\btask\s*(?:\([^)]*\)|[A-Za-z_]\w*(?:\s*\([^)]*\))?)\s*\{/u.test(withoutStandardClean);
16881
- }
16882
- function hasGradlePropertySetter(source) {
16883
- const clean = stripGradleComments(source);
16884
- const masked = maskGradleStrings(clean);
16885
- return /\bsetProperty\s*\(/u.test(masked);
16886
- }
16887
- function countGradleIdentityWrites(source, key) {
16888
- const masked = maskGradleStrings(stripGradleComments(source));
16889
- const expression = new RegExp(`\\b${key}\\b`, "gu");
16890
- return [...masked.matchAll(expression)].filter((match) => {
16891
- const index = match.index ?? 0;
16892
- const rest = masked.slice(index + key.length);
16893
- if (masked[index - 1] === ".") {
16894
- return /^\s*(?:\+?=|\(|\.set\s*\()/u.test(rest);
16895
- }
16896
- return /^\s*(?:\+?=|\(|["'\d]|[A-Za-z_$])/u.test(rest);
16897
- }).length;
17026
+ function readAndroidAdIdDeclaration(manifest) {
17027
+ const permissions = adIdPermissions(manifest);
17028
+ if (permissions.length === 0) {
17029
+ return "absent";
17030
+ }
17031
+ return permissions.some((match) => readXmlAttribute(match[0], "tools:node") === "remove") ? "removed" : "declared";
16898
17032
  }
16899
- function maskGradleStrings(clean) {
16900
- let masked = "";
16901
- let quote;
16902
- for (let index = 0; index < clean.length; index += 1) {
16903
- const character = clean[index] ?? "";
16904
- if (quote !== void 0) {
16905
- if (character === "\\" && index + 1 < clean.length) {
16906
- masked += " ";
16907
- index += 1;
16908
- } else if (character === quote) {
16909
- masked += character;
16910
- quote = void 0;
16911
- } else {
16912
- masked += character === "\n" ? "\n" : " ";
16913
- }
16914
- } else if (character === '"' || character === "'") {
16915
- quote = character;
16916
- masked += character;
16917
- } else {
16918
- masked += character;
16919
- }
17033
+ function readGameAdvertisingIdPolicy(gameConfig) {
17034
+ const ads = isRecord5(gameConfig) ? gameConfig.ads : void 0;
17035
+ const admob = isRecord5(ads) ? ads.admob : void 0;
17036
+ const value = isRecord5(admob) ? admob.advertisingId : void 0;
17037
+ if (value === void 0 || value === "declared") {
17038
+ return "declared";
16920
17039
  }
16921
- return masked;
17040
+ if (value === "removed") {
17041
+ return "removed";
17042
+ }
17043
+ throw new Error("mpgd.game.json ads.admob.advertisingId must be 'declared' or 'removed'.");
16922
17044
  }
16923
- function readIosReleaseInfoPlist(source) {
16924
- const target2 = readIosReleaseConfiguration(source, readIosAppConfigurationList(source));
16925
- let value = readIosInfoPlistSetting(target2);
16926
- if (value === void 0) {
16927
- if (/\bbaseConfigurationReference\s*=/u.test(target2)) {
16928
- throw new Error("Existing ios project Release Info.plist xcconfig cannot be read safely.");
17045
+ function inspectAndroidManifestAds(manifest, usesAds, advertisingIdPolicy = "declared") {
17046
+ const issues = [];
17047
+ const declaration = readAndroidAdIdDeclaration(manifest);
17048
+ if (usesAds) {
17049
+ const hasAppId = [...maskXmlComments(manifest).matchAll(/<meta-data\b[^>]*?\/?>/gu)].some((match) => readXmlAttribute(match[0], "android:name") === "com.google.android.gms.ads.APPLICATION_ID");
17050
+ if (!hasAppId) {
17051
+ issues.push({
17052
+ level: "error",
17053
+ message: "The shell installs AdMob but neither android/app/src/main/AndroidManifest.xml nor the release source set's manifest has com.google.android.gms.ads.APPLICATION_ID meta-data; the app crashes at launch."
17054
+ });
16929
17055
  }
16930
- const projectListId = readIosProjectConfigurationList(source);
16931
- if (projectListId === void 0) {
16932
- throw new Error("Existing ios project Release Info.plist has no project configuration.");
17056
+ if (declaration === "removed" && advertisingIdPolicy !== "removed") {
17057
+ issues.push({
17058
+ level: "error",
17059
+ message: `AndroidManifest.xml removes ${androidAdIdPermission} although the shell installs AdMob: ads get a zeroed advertising ID on Android 13+ and the Play Console answer no longer matches. Replace the tools:node="remove" line with <uses-permission android:name="${androidAdIdPermission}" />, or, for a deliberate no-advertising-ID policy (for example a child-directed game), set ads.admob.advertisingId to "removed" in mpgd.game.json.`
17060
+ });
16933
17061
  }
16934
- const project = readIosReleaseConfiguration(source, projectListId);
16935
- value = readIosInfoPlistSetting(project);
16936
- if (value === void 0 && /\bbaseConfigurationReference\s*=/u.test(project)) {
16937
- throw new Error("Existing ios project Release Info.plist xcconfig cannot be read safely.");
17062
+ if (declaration !== "removed" && advertisingIdPolicy === "removed") {
17063
+ issues.push({
17064
+ level: "error",
17065
+ message: `mpgd.game.json ads.admob.advertisingId is "removed", but AndroidManifest.xml does not remove ${androidAdIdPermission}; add <uses-permission android:name="${androidAdIdPermission}" tools:node="remove" />.`
17066
+ });
16938
17067
  }
17068
+ } else if (declaration !== "removed") {
17069
+ issues.push({
17070
+ level: "warning",
17071
+ message: `The shell shows no AdMob ads, but AndroidManifest.xml does not remove ${androidAdIdPermission}; a dependency can still merge it. Add <uses-permission android:name="${androidAdIdPermission}" tools:node="remove" />.`
17072
+ });
16939
17073
  }
16940
- if (value === void 0) {
16941
- throw new Error("Existing ios project Release Info.plist cannot be read safely.");
16942
- }
16943
- return value;
17074
+ return issues;
16944
17075
  }
16945
- function assertIosReleaseProductName(source) {
16946
- const target2 = readIosReleaseConfiguration(source, readIosAppConfigurationList(source));
16947
- let name = readIosProductName(target2);
16948
- if (name === void 0 || name === "$(inherited)") {
16949
- if (/\bbaseConfigurationReference\s*=/u.test(target2)) {
16950
- throw new Error("Existing ios project Release product name xcconfig is unsupported.");
16951
- }
16952
- const projectList = readIosProjectConfigurationList(source);
16953
- if (projectList !== void 0) {
16954
- const project = readIosReleaseConfiguration(source, projectList);
16955
- name = readIosProductName(project);
16956
- if ((name === void 0 || name === "$(inherited)") && /\bbaseConfigurationReference\s*=/u.test(project)) {
16957
- throw new Error("Existing ios project Release product name xcconfig is unsupported.");
16958
- }
16959
- }
17076
+ function inspectIosInfoPlistAds(plist, usesAds) {
17077
+ if (!usesAds) {
17078
+ return [];
16960
17079
  }
16961
- if (name !== void 0 && name !== "$(inherited)" && name !== "App" && name !== "$(TARGET_NAME)") {
16962
- throw new Error("Existing ios project Release PRODUCT_NAME must build App.app.");
17080
+ const issues = [];
17081
+ if (readPlistTopLevelValue(plist, "GADApplicationIdentifier") === void 0) {
17082
+ issues.push({
17083
+ level: "error",
17084
+ message: "The shell installs AdMob but Info.plist has no GADApplicationIdentifier; the Google Mobile Ads SDK stops the app at launch."
17085
+ });
16963
17086
  }
16964
- }
16965
- function assertIosReleaseInfoPlistExpansion(source) {
16966
- const target2 = readIosReleaseConfiguration(source, readIosAppConfigurationList(source));
16967
- assertIosInfoPlistExpansionSetting(target2);
16968
- const projectList = readIosProjectConfigurationList(source);
16969
- if (projectList !== void 0) {
16970
- assertIosInfoPlistExpansionSetting(readIosReleaseConfiguration(source, projectList));
17087
+ const networks = readPlistTopLevelValue(plist, "SKAdNetworkItems");
17088
+ if (networks === void 0 || !/cstr6suwn9\.skadnetwork/u.test(networks.xml)) {
17089
+ issues.push({
17090
+ level: "warning",
17091
+ message: "Info.plist SKAdNetworkItems does not list Google's cstr6suwn9.skadnetwork; copy the current list from https://developers.google.com/admob/ios/3p-skadnetworks."
17092
+ });
16971
17093
  }
17094
+ return issues;
16972
17095
  }
16973
- function assertIosReleasePackagingSettings(source) {
16974
- const target2 = readIosReleaseConfiguration(source, readIosAppConfigurationList(source));
16975
- const projectList = readIosProjectConfigurationList(source);
16976
- const project = projectList === void 0 ? void 0 : readIosReleaseConfiguration(source, projectList);
16977
- const setting = (configuration, key) => {
16978
- if (configuration === void 0) {
16979
- return void 0;
16980
- }
16981
- const conditional = new RegExp(`\\b${key}\\[[^\\]\\r\\n]+\\]`, "u");
16982
- if (conditional.test(configuration)) {
16983
- throw new Error(`Existing ios project conditional Release ${key} is unsupported.`);
16984
- }
16985
- const expression = `(?:^|[\\s{;])["']?${key}["']?\\s*=\\s*([^;]+);`;
16986
- const pattern = new RegExp(expression, "gu");
16987
- const values = [...configuration.matchAll(pattern)].map((match) => match[1]?.trim().replace(/^["']|["']$/gu, ""));
16988
- if (values.length > 1 || /\bbaseConfigurationReference\s*=/u.test(configuration)) {
16989
- throw new Error(`Existing ios project Release ${key} cannot be read safely.`);
17096
+
17097
+ // node_modules/.cache/mpgd-native-build/tools/target/native-store-policy.js
17098
+ function inspectNativeStorePolicy(input) {
17099
+ const gameConfig = input.gameRoot === void 0 ? void 0 : join13(input.gameRoot, "mpgd.game.json");
17100
+ const gameConfigJson = gameConfig !== void 0 && existsSync19(gameConfig) ? readJson2(gameConfig) : void 0;
17101
+ const orientation = readGameNativeOrientation(gameConfigJson);
17102
+ const shellPackage = join13(input.shellApp, "package.json");
17103
+ const usesAds = existsSync19(shellPackage) && shellUsesAdMob(readJson2(shellPackage));
17104
+ if (input.platform === "ios") {
17105
+ const project = join13(input.shellApp, "ios/App/App.xcodeproj/project.pbxproj");
17106
+ if (!existsSync19(project)) {
17107
+ return [];
16990
17108
  }
16991
- return values[0];
16992
- };
16993
- const effective = (key) => {
16994
- const targetValue = setting(target2, key);
16995
- return targetValue === void 0 || targetValue === "$(inherited)" ? setting(project, key) : targetValue;
16996
- };
16997
- const preprocessing = effective("INFOPLIST_PREPROCESS");
16998
- if (preprocessing !== void 0 && preprocessing !== "NO") {
16999
- throw new Error("Existing ios project Release Info.plist preprocessing is unsupported.");
17000
- }
17001
- for (const key of ["INFOPLIST_PREPROCESSOR_DEFINITIONS", "INFOPLIST_PREFIX_HEADER"]) {
17002
- if (effective(key) !== void 0) {
17003
- throw new Error(`Existing ios project Release ${key} is unsupported.`);
17109
+ const projectSource = readFileSync20(project, "utf8");
17110
+ const deviceFamily = readIosDeviceFamily(input.metadata);
17111
+ const issues = [
17112
+ ...inspectIosProjectDeviceFamily(projectSource, deviceFamily),
17113
+ ...inspectIosAppPrivacyManifest(projectSource)
17114
+ ];
17115
+ const plist = join13(dirname9(dirname9(project)), readIosReleaseInfoPlist(projectSource));
17116
+ if (existsSync19(plist)) {
17117
+ const plistSource = readFileSync20(plist, "utf8");
17118
+ issues.push(...inspectIosInfoPlistStorePolicy(plistSource), ...inspectIosInfoPlistOrientation(plistSource, orientation, deviceFamily ?? "universal"), ...inspectIosInfoPlistAds(plistSource, usesAds));
17004
17119
  }
17120
+ return issues;
17005
17121
  }
17006
- const skipInstall = effective("SKIP_INSTALL");
17007
- if (skipInstall !== void 0 && skipInstall !== "NO") {
17008
- throw new Error("Existing ios project Release SKIP_INSTALL must be NO.");
17122
+ const manifest = join13(input.shellApp, "android/app/src/main/AndroidManifest.xml");
17123
+ if (!existsSync19(manifest)) {
17124
+ return [];
17009
17125
  }
17126
+ const manifestSource = readFileSync20(manifest, "utf8");
17127
+ const releaseManifest = join13(input.shellApp, "android/app/src/release/AndroidManifest.xml");
17128
+ const releaseSource = existsSync19(releaseManifest) ? readFileSync20(releaseManifest, "utf8") : "";
17129
+ return [
17130
+ ...inspectAndroidManifestOrientation(manifestSource, orientation),
17131
+ ...inspectAndroidManifestAds(`${manifestSource}
17132
+ ${releaseSource}`, usesAds, usesAds ? readGameAdvertisingIdPolicy(gameConfigJson) : "declared")
17133
+ ];
17010
17134
  }
17011
- function assertIosInfoPlistExpansionSetting(configuration) {
17012
- const key = "INFOPLIST_EXPAND_BUILD_SETTINGS";
17013
- const conditional = /(?:^|[\s{;])["']?INFOPLIST_EXPAND_BUILD_SETTINGS(?:\[[^\]\r\n]+\])+["']?\s*=/u;
17014
- if (conditional.test(configuration)) {
17015
- throw new Error("Existing ios project conditional Release Info.plist expansion is unsupported.");
17016
- }
17017
- const values = [...configuration.matchAll(/(?:^|[\s{;])["']?INFOPLIST_EXPAND_BUILD_SETTINGS["']?\s*=\s*([^;]+);/gu)].map((match) => match[1]?.trim().replace(/^["']|["']$/gu, ""));
17018
- if (values.length === 0 && /\bbaseConfigurationReference\s*=/u.test(configuration)) {
17019
- throw new Error("Existing ios project Release Info.plist expansion xcconfig is unsupported.");
17020
- }
17021
- if (values.length > 1 || values.some((value) => value !== "YES")) {
17022
- throw new Error(`Existing ios project Release ${key} must be enabled.`);
17135
+ function readJson2(file) {
17136
+ const source = readFileSync20(file, "utf8");
17137
+ try {
17138
+ return JSON.parse(source);
17139
+ } catch (error) {
17140
+ throw new Error(`${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
17023
17141
  }
17024
17142
  }
17025
- function readIosProductName(configuration) {
17026
- if (/(?:^|[\s{;])["']?PRODUCT_NAME(?:\[[^\]\r\n]+\])+["']?\s*=/u.test(configuration)) {
17027
- throw new Error("Existing ios project conditional Release PRODUCT_NAME is unsupported.");
17143
+ function reportNativeStorePolicy(issues, production, log = (message) => console.warn(message)) {
17144
+ for (const issue of issues) {
17145
+ log(`[native-store-policy] ${issue.level}: ${issue.message}`);
17028
17146
  }
17029
- const values = [...configuration.matchAll(/(?:^|[\s{;])["']?PRODUCT_NAME["']?\s*=\s*([^;]+);/gu)].map((match) => match[1]?.trim().replace(/^["']|["']$/gu, ""));
17030
- if (values.length > 1) {
17031
- throw new Error("Existing ios project Release PRODUCT_NAME is ambiguous.");
17147
+ const errors = issues.filter((issue) => issue.level === "error");
17148
+ if (production && errors.length > 0) {
17149
+ throw new Error(`Native shell is not ready for store release:
17150
+ ${errors.map((issue) => `- ${issue.message}`).join("\n")}`);
17032
17151
  }
17033
- return values[0];
17034
17152
  }
17035
- function readIosInfoPlistSetting(configuration) {
17036
- if (/(?:^|[\s{;])["']?INFOPLIST_FILE(?:\[[^\]\r\n]+\])+["']?\s*=/u.test(configuration)) {
17037
- throw new Error("Existing ios project conditional Release Info.plist is unsupported.");
17153
+
17154
+ // node_modules/.cache/mpgd-native-build/tools/target/native-ios-inspection.js
17155
+ import { spawnSync as spawnSync3 } from "node:child_process";
17156
+ import { existsSync as existsSync20, mkdtempSync as mkdtempSync3, readFileSync as readFileSync21, rmSync as rmSync10, statSync as statSync4 } from "node:fs";
17157
+ import { tmpdir as tmpdir3 } from "node:os";
17158
+ import path7 from "node:path";
17159
+ function inspectSignedIosArchive(archive, input) {
17160
+ return inspectSignedIosApplication(path7.join(archive, "Products/Applications/App.app"), input);
17161
+ }
17162
+ function inspectSignedIosIpa(ipa, input) {
17163
+ if (!existsSync20(ipa) || !statSync4(ipa).isFile()) {
17164
+ throw new Error("iOS store IPA is missing.");
17038
17165
  }
17039
- const values = [...configuration.matchAll(/(?:^|[\s{;])["']?INFOPLIST_FILE["']?\s*=\s*([^;]+);/gu)].map((match) => match[1]?.trim().replace(/^["']|["']$/gu, ""));
17040
- if (values.length > 1) {
17041
- throw new Error("Existing ios project Release Info.plist is ambiguous.");
17166
+ const runner = input.runner ?? { run: runCommand2 };
17167
+ const listing = runner.run("unzip", ["-Z1", ipa]);
17168
+ if (listing.status !== 0) {
17169
+ throw new Error("iOS store IPA could not be listed.");
17042
17170
  }
17043
- const value = values[0];
17044
- if (value === void 0 || value === "$(inherited)") {
17045
- return void 0;
17171
+ const entries = listing.stdout.split(/\r?\n/u).filter(Boolean);
17172
+ if (entries.some((entry) => entry.startsWith("/") || entry.includes("\\") || entry.split("/").some((segment) => segment === ".." || segment === "."))) {
17173
+ throw new Error("iOS store IPA has an unsafe path.");
17046
17174
  }
17047
- if (value.includes("$") || value.includes("\\") || pathIsUnsafe(value)) {
17048
- throw new Error("Existing ios project Release Info.plist cannot be read safely.");
17175
+ const appManifests = entries.filter((entry) => /^Payload\/[^/]+\.app\/Info\.plist$/u.test(entry));
17176
+ if (appManifests.length !== 1) {
17177
+ throw new Error("iOS store IPA must contain exactly one app manifest.");
17049
17178
  }
17050
- return value;
17051
- }
17052
- function pathIsUnsafe(value) {
17053
- return value.startsWith("/") || value.split("/").some((part) => part === ".." || part === "");
17054
- }
17055
- function readIosAppConfigurationList(source) {
17056
- const target2 = readPbxObject(source, readIosAppTargetId(source));
17057
- const listId = /\bbuildConfigurationList\s*=\s*([A-F0-9]+)\b/u.exec(target2)?.[1];
17058
- if (listId === void 0) {
17059
- throw new Error("Existing ios project App target configuration is missing.");
17179
+ const appRelative = path7.dirname(appManifests[0] ?? "");
17180
+ const extracted = mkdtempSync3(path7.join(tmpdir3(), "mpgd-ios-inspection-"));
17181
+ try {
17182
+ const result = runner.run("unzip", ["-q", ipa, "-d", extracted]);
17183
+ if (result.status !== 0) {
17184
+ throw new Error("iOS store IPA could not be extracted.");
17185
+ }
17186
+ return inspectSignedIosApplication(path7.join(extracted, appRelative), input);
17187
+ } finally {
17188
+ rmSync10(extracted, { recursive: true, force: true });
17060
17189
  }
17061
- return listId;
17062
17190
  }
17063
- function readIosAppTargetId(source) {
17064
- const appTargets = [...source.matchAll(/\b([A-F0-9]+)\s*\/\*\s*App\s*\*\/\s*=\s*\{/gu)].map((match) => match[1] ?? "").filter((id) => {
17065
- const block = readPbxObject(source, id);
17066
- return /\bisa\s*=\s*PBXNativeTarget;/u.test(block) && /\bname\s*=\s*"?App"?\s*;/u.test(block);
17067
- });
17068
- if (appTargets.length !== 1) {
17069
- throw new Error("Existing ios project App target could not be read safely.");
17191
+ function inspectSignedIosApplication(appPath, input) {
17192
+ const plist = path7.join(appPath, "Info.plist");
17193
+ if (!existsSync20(plist) || !statSync4(plist).isFile()) {
17194
+ throw new Error("Signed iOS app manifest is missing.");
17070
17195
  }
17071
- const targetId = appTargets[0] ?? "";
17072
- const target2 = readPbxObject(source, targetId);
17073
- const productId = /\bproductReference\s*=\s*([A-F0-9]+)\b/u.exec(target2)?.[1];
17074
- if (!/\bproductType\s*=\s*"?com\.apple\.product-type\.application"?\s*;/u.test(target2) || productId === void 0) {
17075
- throw new Error("Existing ios App target must produce an application.");
17196
+ if (!existsSync20(path7.join(appPath, "Assets.car")) || existsSync20(path7.join(appPath, "Info-Smoke.plist"))) {
17197
+ throw new Error("Signed iOS app contains smoke assets or omits release icons.");
17076
17198
  }
17077
- const product = readPbxObject(source, productId);
17078
- if (!/\bisa\s*=\s*PBXFileReference\s*;/u.test(product) || !/\b(?:explicitFileType|lastKnownFileType)\s*=\s*wrapper\.application\s*;/u.test(product) || !/\bpath\s*=\s*"?App\.app"?\s*;/u.test(product) || !/\bsourceTree\s*=\s*BUILT_PRODUCTS_DIR\s*;/u.test(product)) {
17079
- throw new Error("Existing ios App target product must be App.app.");
17199
+ const runner = input.runner ?? { run: runCommand2 };
17200
+ const signature = runner.run("codesign", ["--verify", "--deep", "--strict", appPath]);
17201
+ if (signature.status !== 0) {
17202
+ throw new Error("iOS app code signature could not be verified.");
17080
17203
  }
17081
- return targetId;
17082
- }
17083
- function readIosProjectConfigurationList(source) {
17084
- const projects = [...source.matchAll(/\b([A-F0-9]+)\s*\/\*[^*]+\*\/\s*=\s*\{/gu)].map((match) => readPbxObject(source, match[1] ?? "")).filter((block) => /\bisa\s*=\s*PBXProject;/u.test(block));
17085
- return projects.length === 1 ? /\bbuildConfigurationList\s*=\s*([A-F0-9]+)\b/u.exec(projects[0] ?? "")?.[1] : void 0;
17086
- }
17087
- function readIosReleaseConfiguration(source, listId) {
17088
- const list = readPbxObject(source, listId);
17089
- const configurations = /\bbuildConfigurations\s*=\s*\(([^)]*)\)/u.exec(list)?.[1];
17090
- const releaseIds = configurations === void 0 ? [] : [...configurations.matchAll(/\b([A-F0-9]+)\s*\/\*\s*Release\s*\*\//gu)].map((match) => match[1]);
17091
- if (releaseIds.length !== 1) {
17092
- throw new Error("Existing ios project App Release configuration is missing or ambiguous.");
17204
+ const signingInfo = runner.run("codesign", ["-dv", "--verbose=4", appPath]);
17205
+ const signingText = `${signingInfo.stdout}
17206
+ ${signingInfo.stderr}`;
17207
+ const expectedTeamLine = `TeamIdentifier=${input.expectedTeamId}`;
17208
+ if (signingInfo.status !== 0 || /Signature=adhoc/iu.test(signingText) || !signingText.split(/\r?\n/u).some((line) => line.trim() === expectedTeamLine)) {
17209
+ throw new Error("iOS app is not signed for the expected development team.");
17093
17210
  }
17094
- return stripGradleComments(readPbxObject(source, releaseIds[0] ?? ""));
17211
+ const fields = [
17212
+ ["bundleId", "CFBundleIdentifier", input.expectedBundleId],
17213
+ ["marketingVersion", "CFBundleShortVersionString", input.expectedMarketingVersion],
17214
+ ["buildNumber", "CFBundleVersion", input.expectedBuildNumber]
17215
+ ];
17216
+ const observed = Object.fromEntries(fields.map(([name, key, expected]) => {
17217
+ const result = runner.run("/usr/libexec/PlistBuddy", ["-c", `Print :${key}`, plist]);
17218
+ if (result.status !== 0 || result.stdout.trim() !== expected) {
17219
+ throw new Error(`iOS app ${name} does not match release identity.`);
17220
+ }
17221
+ return [name, expected];
17222
+ }));
17223
+ const configFiles = [
17224
+ path7.join(appPath, "capacitor.config.json"),
17225
+ path7.join(appPath, "public/capacitor.config.json")
17226
+ ];
17227
+ if (!configFiles.some((file) => existsSync20(file))) {
17228
+ throw new Error("Signed iOS app Capacitor configuration is missing.");
17229
+ }
17230
+ for (const configFile of configFiles) {
17231
+ if (existsSync20(configFile)) {
17232
+ assertNoDebugServer(configFile);
17233
+ }
17234
+ }
17235
+ return { ...observed, teamId: input.expectedTeamId, signed: true };
17095
17236
  }
17096
- function readPbxObject(source, id) {
17097
- const marker = new RegExp(`\\b${id}\\s*\\/\\*[^*]+\\*\\/\\s*=\\s*\\{`, "u").exec(source);
17098
- if (marker?.index === void 0) {
17099
- throw new Error(`Existing ios project object ${id} could not be read safely.`);
17237
+ function assertNoDebugServer(file) {
17238
+ const value = JSON.parse(readFileSync21(file, "utf8"));
17239
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
17240
+ throw new Error("Signed iOS app has malformed Capacitor configuration.");
17241
+ }
17242
+ const config2 = value;
17243
+ const server = config2.server;
17244
+ const ios = config2.ios;
17245
+ if (typeof server === "object" && server !== null && "url" in server || typeof ios === "object" && ios !== null && "webContentsDebuggingEnabled" in ios && ios.webContentsDebuggingEnabled === true) {
17246
+ throw new Error("Signed iOS app retains a live-reload or debug bridge configuration.");
17100
17247
  }
17101
- const opening = source.indexOf("{", marker.index);
17102
- return readBracedText(source, opening);
17103
17248
  }
17104
- function readBracedText(source, opening) {
17105
- if (opening < 0) {
17106
- throw new Error("Existing native project block is missing.");
17249
+ function runCommand2(command, args) {
17250
+ const result = spawnSync3(command, [...args], {
17251
+ encoding: "utf8",
17252
+ env: nativeInspectionEnvironment(),
17253
+ maxBuffer: 4 * 1024 * 1024
17254
+ });
17255
+ if (result.error !== void 0) {
17256
+ throw new Error(`Required iOS inspection command is unavailable: ${command}.`);
17107
17257
  }
17108
- let depth = 0;
17109
- let quote = false;
17110
- let blockComment = false;
17111
- for (let index = opening; index < source.length; index += 1) {
17112
- const character = source[index];
17113
- const next = source[index + 1];
17114
- if (blockComment) {
17115
- if (character === "*" && next === "/") {
17116
- blockComment = false;
17117
- index += 1;
17118
- }
17119
- continue;
17258
+ return {
17259
+ status: result.status,
17260
+ stdout: result.stdout,
17261
+ stderr: result.stderr
17262
+ };
17263
+ }
17264
+
17265
+ // node_modules/.cache/mpgd-native-build/tools/target/native-ios-signing.js
17266
+ import { spawnSync as spawnSync4 } from "node:child_process";
17267
+ import { existsSync as existsSync21, statSync as statSync5 } from "node:fs";
17268
+ import path8 from "node:path";
17269
+ function resolveIosSigningPlan(environment, mode) {
17270
+ const teamId = environment.MPGD_IOS_TEAM_ID?.trim();
17271
+ if (teamId === void 0 || !/^[A-Z0-9]{10}$/u.test(teamId)) {
17272
+ throw new Error("Signed iOS builds require a 10-character MPGD_IOS_TEAM_ID.");
17273
+ }
17274
+ const style = environment.MPGD_IOS_SIGNING_STYLE ?? "Automatic";
17275
+ if (style !== "Automatic" && style !== "Manual") {
17276
+ throw new Error("MPGD_IOS_SIGNING_STYLE must be Automatic or Manual.");
17277
+ }
17278
+ const identity = environment.MPGD_IOS_SIGNING_IDENTITY?.trim();
17279
+ const profile2 = environment.MPGD_IOS_PROVISIONING_PROFILE_SPECIFIER?.trim();
17280
+ if (style === "Manual" && (identity === void 0 || identity === "" || profile2 === void 0 || profile2 === "")) {
17281
+ throw new Error("Manual iOS signing requires host identity and provisioning profile names.");
17282
+ }
17283
+ if (style === "Automatic" && profile2 !== void 0 && profile2 !== "") {
17284
+ throw new Error("Automatic iOS signing must not set a manual provisioning profile.");
17285
+ }
17286
+ const archiveBuildSettings = [
17287
+ "CODE_SIGNING_ALLOWED=YES",
17288
+ `CODE_SIGN_STYLE=${style}`,
17289
+ `DEVELOPMENT_TEAM=${teamId}`,
17290
+ ...identity === void 0 || identity === "" ? [] : [`CODE_SIGN_IDENTITY=${identity}`],
17291
+ ...profile2 === void 0 || profile2 === "" ? [] : [`PROVISIONING_PROFILE_SPECIFIER=${profile2}`],
17292
+ ...environment.MPGD_IOS_SIGNING_KEYCHAIN === void 0 ? [] : [`OTHER_CODE_SIGN_FLAGS=--keychain ${JSON.stringify(environment.MPGD_IOS_SIGNING_KEYCHAIN)}`]
17293
+ ];
17294
+ if (mode !== "store-export") {
17295
+ return { teamId, archiveBuildSettings };
17296
+ }
17297
+ const configuredExportFile = environment.MPGD_IOS_EXPORT_OPTIONS_PLIST;
17298
+ if (configuredExportFile === void 0 || configuredExportFile.trim() === "") {
17299
+ throw new Error("iOS store export requires MPGD_IOS_EXPORT_OPTIONS_PLIST.");
17300
+ }
17301
+ const exportOptionsPlist = path8.resolve(configuredExportFile);
17302
+ if (!existsSync21(exportOptionsPlist) || !statSync5(exportOptionsPlist).isFile()) {
17303
+ throw new Error("iOS export options plist must be an existing host file.");
17304
+ }
17305
+ const method = spawnSync4("/usr/libexec/PlistBuddy", ["-c", "Print :method", exportOptionsPlist], {
17306
+ encoding: "utf8",
17307
+ maxBuffer: 1024 * 1024
17308
+ });
17309
+ if (method.error !== void 0 || method.status !== 0 || !["app-store", "app-store-connect"].includes(method.stdout.trim())) {
17310
+ throw new Error("iOS export options must select an App Store distribution method.");
17311
+ }
17312
+ return { teamId, archiveBuildSettings, exportOptionsPlist };
17313
+ }
17314
+
17315
+ // node_modules/.cache/mpgd-native-build/tools/target/native-release-identity.js
17316
+ import { existsSync as existsSync23, readFileSync as readFileSync23, writeFileSync as writeFileSync16 } from "node:fs";
17317
+ import { dirname as dirname10, isAbsolute as isAbsolute11, join as join14, relative as relative12, resolve as resolve17 } from "node:path";
17318
+
17319
+ // node_modules/.cache/mpgd-native-build/packages/cli/src/capacitor-shell-starter.js
17320
+ import { chmodSync as chmodSync2, existsSync as existsSync22, lstatSync as lstatSync12, mkdirSync as mkdirSync11, readdirSync as readdirSync6, readFileSync as readFileSync22, realpathSync as realpathSync9, renameSync as renameSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync15 } from "node:fs";
17321
+ import path9 from "node:path";
17322
+ import { DOMParser as DOMParser2 } from "@xmldom/xmldom";
17323
+ import { satisfies, valid, validRange } from "semver";
17324
+
17325
+ // node_modules/.cache/mpgd-native-build/packages/cli/src/notifications-starter.js
17326
+ var notificationsMessagingPlugin = "@capacitor-firebase/messaging";
17327
+ var notificationsGameDependencies = Object.freeze({
17328
+ [notificationsMessagingPlugin]: "8.5.2",
17329
+ "@capacitor/local-notifications": "8.3.1",
17330
+ firebase: "12.19.0"
17331
+ });
17332
+ var notificationsShellPlugins = Object.freeze([
17333
+ notificationsMessagingPlugin,
17334
+ "@capacitor/local-notifications"
17335
+ ]);
17336
+ var gatewayImport = {
17337
+ anchor: "import type { RuntimeConfig } from '../runtimeDetector';\n",
17338
+ replacement: [
17339
+ "import { createStarterCapacitorNotifications } from '../capacitorNotifications';",
17340
+ "import type { RuntimeConfig } from '../runtimeDetector';",
17341
+ ""
17342
+ ].join("\n")
17343
+ };
17344
+ var gatewayProviders = (platform) => ({
17345
+ anchor: " const providers = createStoreProviders(runtime, () => gateway);\n",
17346
+ replacement: [
17347
+ " const providers = [",
17348
+ " ...createStoreProviders(runtime, () => gateway),",
17349
+ " // Notifications v2 (FCM and local reminders); `notifications/topics.json`",
17350
+ " // maps the topics, and the shell needs the two plugins",
17351
+ " // (`mpgd target init capacitor --providers notifications`).",
17352
+ ` ...(await createStarterCapacitorNotifications('${platform}', runtime.configTarget)),`,
17353
+ " ];",
17354
+ ""
17355
+ ].join("\n")
17356
+ });
17357
+ var commonEdits = {
17358
+ "src/platform/buildGateways/capacitorAndroid.ts": [gatewayImport, gatewayProviders("android")],
17359
+ "src/platform/buildGateways/capacitorIos.ts": [gatewayImport, gatewayProviders("ios")],
17360
+ "src/platform/buildGateways/reddit.ts": [
17361
+ {
17362
+ anchor: " createDevvitCommerceProducts,\n createDevvitPlatformGateway,\n",
17363
+ replacement: [
17364
+ " createDevvitCommerceProducts,",
17365
+ " createDevvitNotificationsBackend,",
17366
+ " createDevvitPlatformGateway,",
17367
+ ""
17368
+ ].join("\n")
17369
+ },
17370
+ {
17371
+ anchor: "import { createDevvitWebPaymentsClient } from '@mpgd/adapter-devvit/web';\n",
17372
+ replacement: [
17373
+ "import {",
17374
+ " createDevvitWebLoginPrompt,",
17375
+ " createDevvitWebPaymentsClient,",
17376
+ "} from '@mpgd/adapter-devvit/web';",
17377
+ ""
17378
+ ].join("\n")
17379
+ },
17380
+ {
17381
+ anchor: "import type { RuntimeConfig } from '../runtimeDetector';\n",
17382
+ replacement: [
17383
+ "import { setStarterHostedNotificationBackend } from '../notificationBackend';",
17384
+ "import { starterNotificationTopics } from '../notifications';",
17385
+ "import type { RuntimeConfig } from '../runtimeDetector';",
17386
+ ""
17387
+ ].join("\n")
17388
+ },
17389
+ {
17390
+ anchor: " ...(commerce === undefined ? {} : { commerce }),\n",
17391
+ replacement: [
17392
+ " ...(commerce === undefined ? {} : { commerce }),",
17393
+ " // Notifications v2: the game-rendered opt-in the server bridge serves",
17394
+ " // (a gated beta: `integrations.notifications` stays approval-required",
17395
+ " // until Reddit approves the app), and Reddit login for a logged-out",
17396
+ " // player, who must log in before opting in.",
17397
+ " ...notificationOptions(runtime),",
17398
+ " loginPrompt: createDevvitWebLoginPrompt(),",
17399
+ ""
17400
+ ].join("\n")
17401
+ },
17402
+ {
17403
+ anchor: "function createCommerceOptions(): DevvitGatewayCommerceOptions | undefined {\n",
17404
+ replacement: [
17405
+ "function notificationOptions(",
17406
+ " runtime: RuntimeConfig,",
17407
+ "): Pick<Parameters<typeof createDevvitPlatformGateway>[0], 'notifications'> {",
17408
+ " const topics = starterNotificationTopics(runtime.configTarget).map((topic) => topic.id);",
17409
+ " if (topics.length === 0) {",
17410
+ " return {};",
17411
+ " }",
17412
+ " // The runtime's backend is the Devvit server (consent records, reminders, notices for the",
17413
+ " // logged-in Reddit user), over the same server bridge as the gateway; created on first use.",
17414
+ " setStarterHostedNotificationBackend(runtime.configTarget, () =>",
17415
+ " createDevvitNotificationsBackend({ appVersion: runtime.appVersion, buildId: runtime.buildId }));",
17416
+ " return { notifications: { topics } };",
17417
+ "}",
17418
+ "",
17419
+ "function createCommerceOptions(): DevvitGatewayCommerceOptions | undefined {",
17420
+ ""
17421
+ ].join("\n")
17422
+ }
17423
+ ],
17424
+ "apps/target-devvit/src/server/index.ts": [
17425
+ {
17426
+ anchor: "import { context, createServer, getServerPort, payments, reddit, redis } from '@devvit/web/server';\n",
17427
+ replacement: [
17428
+ "import {",
17429
+ " context,",
17430
+ " createServer,",
17431
+ " getServerPort,",
17432
+ " notifications,",
17433
+ " payments,",
17434
+ " reddit,",
17435
+ " redis,",
17436
+ "} from '@devvit/web/server';",
17437
+ ""
17438
+ ].join("\n")
17439
+ },
17440
+ {
17441
+ anchor: [
17442
+ "import {",
17443
+ " createDevvitCommerceBridgeProducts,",
17444
+ " createDevvitCommerceV2BridgeHandler,",
17445
+ "} from '@mpgd/adapter-devvit/server';",
17446
+ ""
17447
+ ].join("\n"),
17448
+ replacement: [
17449
+ "import {",
17450
+ " createDevvitCommerceBridgeProducts,",
17451
+ " createDevvitCommerceV2BridgeHandler,",
17452
+ " createDevvitNotificationService,",
17453
+ " createDevvitNotificationsBackendBridgeHandler,",
17454
+ " createDevvitNotificationsV2BridgeHandler,",
17455
+ "} from '@mpgd/adapter-devvit/server';",
17456
+ ""
17457
+ ].join("\n")
17458
+ },
17459
+ {
17460
+ anchor: "import type { ProductCatalog } from '@mpgd/catalog';\n",
17461
+ replacement: [
17462
+ "import { resolveNotificationTopicsForTarget, type NotificationTopicCatalog, type ProductCatalog } from '@mpgd/catalog';",
17463
+ "import { assertNotificationDeployment } from '@mpgd/platform/notifications';",
17464
+ ""
17465
+ ].join("\n")
17466
+ },
17467
+ {
17468
+ anchor: "import devvitManifest from '../../devvit.json';\n",
17469
+ replacement: [
17470
+ "import notificationDeploymentJson from '../../../../notifications/deployment.json';",
17471
+ "import notificationTopicsJson from '../../../../notifications/topics.json';",
17472
+ "import devvitManifest from '../../devvit.json';",
17473
+ ""
17474
+ ].join("\n")
17475
+ },
17476
+ {
17477
+ anchor: [
17478
+ "const handleBridgeRequest = createDevvitBridgeHandler({",
17479
+ " redis,",
17480
+ " currentPlayerId,",
17481
+ " currentDisplayName,",
17482
+ " storageKeyNamespace: gameName,",
17483
+ " ...(commerce === undefined ? {} : { commerce }),",
17484
+ "});",
17485
+ ""
17486
+ ].join("\n"),
17487
+ replacement: [
17488
+ "/**",
17489
+ " * Notifications v2 (`notifications/topics.json`, mapped for the `reddit`",
17490
+ " * target): the game-rendered opt-in for the logged-in requester, and the",
17491
+ " * service that records consent and sends server reminders over the Devvit",
17492
+ " * notifications plugin. Devvit notifications are a gated beta: the game",
17493
+ " * shows them only once `integrations.notifications` is `available`.",
17494
+ " */",
17495
+ "const notificationTopicCatalog = notificationTopicsJson as NotificationTopicCatalog;",
17496
+ "const redditNotificationTopics = readRedditNotificationTopics();",
17497
+ "const notificationService: ReturnType<typeof createDevvitNotificationService>['service'] | undefined =",
17498
+ " redditNotificationTopics.length === 0 ? undefined : createNotificationService();",
17499
+ "// The opt-in is served only with the service that sends; a refused catalog turns both off.",
17500
+ "const notificationsBridge = notificationService === undefined",
17501
+ " ? undefined",
17502
+ " : createDevvitNotificationsV2BridgeHandler({",
17503
+ " notifications,",
17504
+ " currentUserId: currentPlayerId,",
17505
+ " topics: redditNotificationTopics,",
17506
+ " });",
17507
+ "// The webview runtime's backend: consent records, reminders, and notices for the request's user.",
17508
+ "const notificationsBackend = notificationService === undefined",
17509
+ " ? undefined",
17510
+ " : createDevvitNotificationsBackendBridgeHandler({ service: notificationService, context: () => context });",
17511
+ "const handleBridgeRequest = createDevvitBridgeHandler({",
17512
+ " redis,",
17513
+ " currentPlayerId,",
17514
+ " currentDisplayName,",
17515
+ " storageKeyNamespace: gameName,",
17516
+ " ...(commerce === undefined ? {} : { commerce }),",
17517
+ " ...(notificationsBridge === undefined ? {} : { notifications: notificationsBridge }),",
17518
+ " ...(notificationsBackend === undefined ? {} : { notificationsBackend }),",
17519
+ "});",
17520
+ ""
17521
+ ].join("\n")
17522
+ },
17523
+ {
17524
+ anchor: [
17525
+ "/**",
17526
+ " * The notifications v2 service (`createDevvitNotificationService` from",
17527
+ " * `@mpgd/adapter-devvit/server`). None until the game declares a topic",
17528
+ " * catalog, so the scheduler tasks below do no work.",
17529
+ " */",
17530
+ "const notificationService: Parameters<typeof handleNotificationTask>[0]['notifications'] = undefined;",
17531
+ ""
17532
+ ].join("\n"),
17533
+ replacement: [
17534
+ "/** The topic ids the catalog maps for Reddit; none when the catalog cannot be read. */",
17535
+ "function readRedditNotificationTopics(): readonly string[] {",
17536
+ " try {",
17537
+ " return resolveNotificationTopicsForTarget(notificationTopicCatalog, 'reddit').map((topic) => topic.id);",
17538
+ " } catch (error) {",
17539
+ " console.error(`devvit notifications disabled: ${errorMessage(error)}`);",
17540
+ " return [];",
17541
+ " }",
17542
+ "}",
17543
+ "",
17544
+ "/**",
17545
+ " * The notifications v2 service over Redis and the Devvit plugin; the",
17546
+ " * scheduler tasks below run its reminders and purges. A catalog or",
17547
+ " * deployment the service refuses turns notifications off, never the game",
17548
+ " * server.",
17549
+ " */",
17550
+ "function createNotificationService(): ReturnType<typeof createDevvitNotificationService>['service'] | undefined {",
17551
+ " try {",
17552
+ " return createDevvitNotificationService({",
17553
+ " redis,",
17554
+ " notifications,",
17555
+ " catalog: notificationTopicCatalog,",
17556
+ " deployment: assertNotificationDeployment(notificationDeploymentJson),",
17557
+ " }).service;",
17558
+ " } catch (error) {",
17559
+ " console.error(`devvit notifications disabled: ${errorMessage(error)}`);",
17560
+ " return undefined;",
17561
+ " }",
17562
+ "}",
17563
+ ""
17564
+ ].join("\n")
17120
17565
  }
17121
- if (quote) {
17122
- if (character === "\\") {
17123
- index += 1;
17124
- } else if (character === '"') {
17125
- quote = false;
17126
- }
17127
- continue;
17566
+ ]
17567
+ };
17568
+ var phaserEdits = {
17569
+ "src/runtime/createGame.ts": [
17570
+ {
17571
+ anchor: "import { LobbyScene } from '../scenes/LobbyScene';\n",
17572
+ replacement: [
17573
+ "import { LobbyScene } from '../scenes/LobbyScene';",
17574
+ "import {",
17575
+ " NotificationSettingsScene,",
17576
+ " routeNotificationSettingsOpens,",
17577
+ "} from '../scenes/NotificationSettingsScene';",
17578
+ ""
17579
+ ].join("\n")
17580
+ },
17581
+ {
17582
+ anchor: " scene: [BootScene, LobbyScene, PlayScene],\n",
17583
+ replacement: " scene: [BootScene, LobbyScene, PlayScene, NotificationSettingsScene],\n"
17584
+ },
17585
+ {
17586
+ anchor: " ownGameAudio(game, input.context);\n",
17587
+ replacement: [
17588
+ " ownGameAudio(game, input.context);",
17589
+ " // The advertising opt-out action and consent notices open the settings screen.",
17590
+ " routeNotificationSettingsOpens(game, input.context);",
17591
+ ""
17592
+ ].join("\n")
17128
17593
  }
17129
- if (character === "/" && next === "*") {
17130
- blockComment = true;
17131
- index += 1;
17132
- } else if (character === '"') {
17133
- quote = true;
17134
- } else if (character === "{") {
17135
- depth += 1;
17136
- } else if (character === "}") {
17137
- depth -= 1;
17138
- if (depth === 0) {
17139
- return source.slice(opening, index + 1);
17140
- }
17594
+ ],
17595
+ "src/scenes/LobbyScene.ts": [
17596
+ {
17597
+ anchor: "import { starterContextKey, type StarterContext } from '../runtime/gameContext';\n",
17598
+ replacement: [
17599
+ "import { starterContextKey, type StarterContext } from '../runtime/gameContext';",
17600
+ "import { notificationSettingsSceneKey } from './NotificationSettingsScene';",
17601
+ ""
17602
+ ].join("\n")
17603
+ },
17604
+ {
17605
+ anchor: " this.input.once('pointerdown', () => this.scene.start('PlayScene'));\n",
17606
+ replacement: [
17607
+ " // The settings button shows only where this target has notifications.",
17608
+ " const notificationsButton = context.platform.notificationsV2 === undefined",
17609
+ " ? undefined",
17610
+ " : this.add",
17611
+ " .text(940, 16, locale === 'ko' ? '\uC54C\uB9BC \uC124\uC815' : 'Notifications', {",
17612
+ " color: '#5eead4',",
17613
+ " fontFamily: 'Arial, sans-serif',",
17614
+ " fontSize: '22px',",
17615
+ " })",
17616
+ " .setOrigin(1, 0)",
17617
+ " .setPadding(16, 12, 16, 12)",
17618
+ " .setInteractive({ useHandCursor: true });",
17619
+ " notificationsButton?.on('pointerup', () => this.scene.start(notificationSettingsSceneKey));",
17620
+ " // A tap anywhere else starts the game; the scene drops this listener on shutdown.",
17621
+ " this.input.on('pointerdown', (_pointer: Phaser.Input.Pointer, over: Phaser.GameObjects.GameObject[]) => {",
17622
+ " if (notificationsButton === undefined || !over.includes(notificationsButton)) {",
17623
+ " this.scene.start('PlayScene');",
17624
+ " }",
17625
+ " });",
17626
+ ""
17627
+ ].join("\n")
17628
+ }
17629
+ ]
17630
+ };
17631
+ var liteEdits = {
17632
+ "src/runtime/createGame.ts": [
17633
+ {
17634
+ anchor: "import { createTapGame } from '../lite/tapGame';\n",
17635
+ replacement: [
17636
+ "import { mountNotificationSettingsPanel } from '../lite/notificationSettingsPanel';",
17637
+ "import { createTapGame } from '../lite/tapGame';",
17638
+ ""
17639
+ ].join("\n")
17640
+ },
17641
+ {
17642
+ anchor: " const shell = createTapGame({ mount, title: '__GAME_TITLE__' });\n",
17643
+ replacement: [
17644
+ " const shell = createTapGame({ mount, title: '__GAME_TITLE__' });",
17645
+ " const disposeNotificationSettings = mountNotificationSettingsPanel(mount, input.context);",
17646
+ ""
17647
+ ].join("\n")
17648
+ },
17649
+ {
17650
+ anchor: " audio.dispose();\n",
17651
+ replacement: [" audio.dispose();", " disposeNotificationSettings();", ""].join("\n")
17141
17652
  }
17653
+ ]
17654
+ };
17655
+ var readmeSection = [
17656
+ "",
17657
+ "## Notifications",
17658
+ "",
17659
+ "This game was created with `--providers notifications`. Its topics live in",
17660
+ "`notifications/topics.json` and the backend settings (`advertisingRules`,",
17661
+ "the advertising sender) in `notifications/deployment.json`; run",
17662
+ "`pnpm validate:notifications` after every change and before every release.",
17663
+ "Replace the sender contact placeholder in `notifications/deployment.json`",
17664
+ "before any advertising is sent. The notification settings screen asks for the",
17665
+ "OS or host permission and records reminder, advertising (\uAD11\uACE0\uC131 \uC815\uBCF4",
17666
+ "\uC218\uC2E0\uB3D9\uC758), and night consent through the notifications runtime. See",
17667
+ "`docs/NOTIFICATIONS.md` and, for Android and iOS, the mpgd-kit Firebase setup",
17668
+ "guide; `mpgd target init capacitor --providers notifications` installs the two",
17669
+ "Capacitor plugins into a game-owned shell.",
17670
+ ""
17671
+ ].join("\n");
17672
+ var notificationsCapacitorGateways = Object.freeze({
17673
+ android: "src/platform/buildGateways/capacitorAndroid.ts",
17674
+ ios: "src/platform/buildGateways/capacitorIos.ts"
17675
+ });
17676
+ var notificationsCapacitorGatewayModules = Object.freeze([
17677
+ "src/platform/capacitorNotifications.ts",
17678
+ "src/platform/notifications.ts",
17679
+ "src/platform/notificationBackend.ts",
17680
+ "src/platform/notificationSettings.ts",
17681
+ "src/platform/notificationOpens.ts"
17682
+ ]);
17683
+ var notificationsCapacitorSupportFiles = Object.freeze([
17684
+ ...notificationsCapacitorGatewayModules,
17685
+ "notifications/topics.json",
17686
+ "notifications/deployment.json",
17687
+ "tools/validate-notifications.ts",
17688
+ "docs/NOTIFICATIONS.md"
17689
+ ]);
17690
+ var notificationsCapacitorTemplateSupportFiles = Object.freeze([
17691
+ "src/platform/serverSession.ts",
17692
+ "src/platform/developmentServerSession.ts"
17693
+ ]);
17694
+ var notificationsCapacitorTemplateSupportImporters = Object.freeze({
17695
+ "src/platform/developmentServerSession.ts": {
17696
+ importer: "src/platform/serverSession.ts",
17697
+ requires: { packageName: "@mpgd/game-services", exportName: "createDevelopmentServerSession" },
17698
+ warning: "src/platform/serverSession.ts predates development notification sessions (VITE_MPGD_DEV_NOTIFICATION_SESSION); to try notifications locally, copy serverSession.ts and developmentServerSession.ts from the starter template."
17142
17699
  }
17143
- throw new Error("Existing native project block is incomplete.");
17144
- }
17700
+ });
17145
17701
 
17146
17702
  // node_modules/.cache/mpgd-native-build/packages/cli/src/capacitor-shell-starter.js
17147
17703
  var shellPath = "apps/mobile-capacitor";
@@ -17233,7 +17789,7 @@ function assertAndroidLauncherClasses(nativeDirectory, gradle) {
17233
17789
  const manifestPackage = manifestRoot?.getAttribute("package");
17234
17790
  const namespace = /\bnamespace\s*(?:=\s*)?["']([A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)+)["']/u.exec(stripGradleComments(gradle))?.[1] ?? (manifestPackage === null || manifestPackage === "" ? void 0 : manifestPackage);
17235
17791
  const releaseFile = path9.join(nativeDirectory, "app/src/release/AndroidManifest.xml");
17236
- const release = existsSync21(releaseFile) ? readAndroidApplication(releaseFile, false) : void 0;
17792
+ const release = existsSync22(releaseFile) ? readAndroidApplication(releaseFile, false) : void 0;
17237
17793
  if (release !== void 0) {
17238
17794
  assertAndroidLauncherEnabled(release, "Release application");
17239
17795
  }
@@ -17343,7 +17899,7 @@ function resolvesAndroidClass(nativeDirectory, qualified, sourceFiles, visited,
17343
17899
  if (!isNativeFile(nativeDirectory, relative14)) {
17344
17900
  continue;
17345
17901
  }
17346
- const source = stripSourceCommentsAndStrings(readFileSync21(path9.join(nativeDirectory, relative14), "utf8"));
17902
+ const source = stripSourceCommentsAndStrings(readFileSync22(path9.join(nativeDirectory, relative14), "utf8"));
17347
17903
  const declaredPackage = /\bpackage\s+([A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)+)\s*;?/u.exec(source)?.[1];
17348
17904
  if (declaredPackage !== packageName) {
17349
17905
  continue;
@@ -17400,7 +17956,7 @@ function listNativeFiles(nativeDirectory, relativeRoot, extension) {
17400
17956
  while (pending.length > 0) {
17401
17957
  const relative14 = pending.pop() ?? "";
17402
17958
  const full = path9.join(nativeDirectory, relative14);
17403
- if (!existsSync21(full) || lstatSync12(full).isSymbolicLink()) {
17959
+ if (!existsSync22(full) || lstatSync12(full).isSymbolicLink()) {
17404
17960
  continue;
17405
17961
  }
17406
17962
  for (const entry of readdirSync6(full, { withFileTypes: true })) {
@@ -17613,7 +18169,7 @@ function assertReferencedIosFiles(nativeDirectory, project, infoRelative) {
17613
18169
  throw new Error(`Existing ios project is incomplete; referenced app file ${relative14} is missing.`);
17614
18170
  }
17615
18171
  }
17616
- const info = readFileSync21(path9.join(nativeDirectory, infoRelative), "utf8");
18172
+ const info = readFileSync22(path9.join(nativeDirectory, infoRelative), "utf8");
17617
18173
  assertSceneDelegateFiles(nativeDirectory, info, "Release", compiledSources);
17618
18174
  return compiledSources;
17619
18175
  }
@@ -17801,7 +18357,7 @@ function isNativeEntry(directory, relative14) {
17801
18357
  let current = directory;
17802
18358
  for (const segment of within.split(path9.sep)) {
17803
18359
  current = path9.join(current, segment);
17804
- if (!existsSync21(current) || lstatSync12(current).isSymbolicLink()) {
18360
+ if (!existsSync22(current) || lstatSync12(current).isSymbolicLink()) {
17805
18361
  return false;
17806
18362
  }
17807
18363
  }
@@ -17834,7 +18390,7 @@ function assertSceneDelegateFiles(nativeDirectory, source, label, compiledSource
17834
18390
  if (!compiledSources.has(relative14)) {
17835
18391
  return false;
17836
18392
  }
17837
- const text = stripSourceCommentsAndStrings(readFileSync21(path9.join(nativeDirectory, relative14), "utf8"));
18393
+ const text = stripSourceCommentsAndStrings(readFileSync22(path9.join(nativeDirectory, relative14), "utf8"));
17838
18394
  return hasUsableTopLevelSwiftSceneDelegate(text, className);
17839
18395
  })) {
17840
18396
  throw new Error(`Existing ios project ${label} scene delegate ${delegate} is missing from App Sources.`);
@@ -17859,17 +18415,17 @@ function hasUsableTopLevelSwiftSceneDelegate(source, className) {
17859
18415
  return false;
17860
18416
  }
17861
18417
  function assertAndroidManifestResources(nativeDirectory) {
17862
- const manifestFiles = ["main", "release"].map((sourceSet) => path9.join(nativeDirectory, `app/src/${sourceSet}/AndroidManifest.xml`)).filter((file) => existsSync21(file));
18418
+ const manifestFiles = ["main", "release"].map((sourceSet) => path9.join(nativeDirectory, `app/src/${sourceSet}/AndroidManifest.xml`)).filter((file) => existsSync22(file));
17863
18419
  const resourceRoots = ["main", "release"].map((sourceSet) => path9.join(nativeDirectory, `app/src/${sourceSet}/res`));
17864
18420
  const references = manifestFiles.flatMap((file) => {
17865
- const manifest = readFileSync21(file, "utf8").replace(/<!--[\s\S]*?-->/gu, "");
18421
+ const manifest = readFileSync22(file, "utf8").replace(/<!--[\s\S]*?-->/gu, "");
17866
18422
  return [...manifest.matchAll(/(?<![\w+])@([a-z]+)\/([A-Za-z_][A-Za-z0-9_.]*)/gu)];
17867
18423
  });
17868
18424
  for (const reference of references) {
17869
18425
  const type = reference[1] ?? "";
17870
18426
  const name = reference[2] ?? "";
17871
18427
  const found = resourceRoots.some((resourceRoot) => {
17872
- if (!existsSync21(resourceRoot)) {
18428
+ if (!existsSync22(resourceRoot)) {
17873
18429
  return false;
17874
18430
  }
17875
18431
  const folders = readdirSync6(resourceRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
@@ -17894,7 +18450,7 @@ function assertNativeDisplayName(nativeDirectory, platform, expectedDisplayName,
17894
18450
  if (platform === "ios" && !isNativeFile(nativeDirectory, relative14)) {
17895
18451
  throw new Error(`Existing ${platform} project is incomplete; native display name resource is missing.`);
17896
18452
  }
17897
- const source = platform === "ios" ? readFileSync21(path9.join(nativeDirectory, relative14), "utf8") : "";
18453
+ const source = platform === "ios" ? readFileSync22(path9.join(nativeDirectory, relative14), "utf8") : "";
17898
18454
  const labels = platform === "ios" ? [parsePlistDictionary(source, "Release").get("CFBundleDisplayName")].filter((value) => value?.tagName === "string").map((value) => value?.textContent ?? "") : [];
17899
18455
  if (platform === "ios" && (labels.length !== 1 || labels[0] !== expectedDisplayName)) {
17900
18456
  throw new Error(`Existing ${platform} project display name differs or cannot be read safely.`);
@@ -17920,7 +18476,7 @@ function assertNativeDisplayName(nativeDirectory, platform, expectedDisplayName,
17920
18476
  }
17921
18477
  }
17922
18478
  const overlayFile = path9.join(nativeDirectory, "app/src/release/AndroidManifest.xml");
17923
- if (existsSync21(overlayFile)) {
18479
+ if (existsSync22(overlayFile)) {
17924
18480
  const release = readAndroidApplication(overlayFile, false);
17925
18481
  if (release !== void 0) {
17926
18482
  if (hasAndroidMergerDirective(release)) {
@@ -17930,8 +18486,8 @@ function assertNativeDisplayName(nativeDirectory, platform, expectedDisplayName,
17930
18486
  if (releaseLabel !== null && resolveAndroidLabel(nativeDirectory, releaseLabel) !== expectedDisplayName) {
17931
18487
  throw new Error("Existing android Release application label differs from the requested name.");
17932
18488
  }
17933
- const appBuildFile = ["app/build.gradle", "app/build.gradle.kts"].map((relative15) => path9.join(nativeDirectory, relative15)).find((file) => existsSync21(file));
17934
- const appGradle = appBuildFile === void 0 ? "" : readFileSync21(appBuildFile, "utf8");
18489
+ const appBuildFile = ["app/build.gradle", "app/build.gradle.kts"].map((relative15) => path9.join(nativeDirectory, relative15)).find((file) => existsSync22(file));
18490
+ const appGradle = appBuildFile === void 0 ? "" : readFileSync22(appBuildFile, "utf8");
17935
18491
  const manifestPackage = main.parentNode?.getAttribute("package");
17936
18492
  const namespace = /\bnamespace\s*(?:=\s*)?["']([A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)+)["']/u.exec(stripGradleComments(appGradle))?.[1] ?? (manifestPackage === null || manifestPackage === "" ? void 0 : manifestPackage);
17937
18493
  const launcherNames = /* @__PURE__ */ new Set();
@@ -17974,13 +18530,13 @@ function assertAndroidReleaseDisplayName(shellApp, expectedDisplayName) {
17974
18530
  function assertAndroidReleaseLauncherIntegrity(shellApp) {
17975
18531
  const nativeDirectory = path9.join(shellApp, "android");
17976
18532
  const gradleRelative = requireOneNativeFile(nativeDirectory, ["app/build.gradle", "app/build.gradle.kts"], "android");
17977
- const gradle = readFileSync21(path9.join(nativeDirectory, gradleRelative), "utf8");
18533
+ const gradle = readFileSync22(path9.join(nativeDirectory, gradleRelative), "utf8");
17978
18534
  assertAndroidLauncherClasses(nativeDirectory, gradle);
17979
18535
  }
17980
18536
  function assertIosReleaseDisplayName(shellApp, expectedDisplayName) {
17981
18537
  const nativeDirectory = path9.join(shellApp, "ios");
17982
18538
  const projectFile = path9.join(nativeDirectory, "App/App.xcodeproj/project.pbxproj");
17983
- const project = readFileSync21(projectFile, "utf8");
18539
+ const project = readFileSync22(projectFile, "utf8");
17984
18540
  const infoRelative = path9.join("App", readIosReleaseInfoPlist(project));
17985
18541
  if (!isNativeFile(nativeDirectory, infoRelative)) {
17986
18542
  throw new Error("Existing ios project Release Info.plist is missing or unsafe.");
@@ -18002,9 +18558,9 @@ function assertIosArchiveScheme(shellApp) {
18002
18558
  if (!isNativeFile(nativeDirectory, projectRelative) || !isNativeFile(nativeDirectory, schemeRelative)) {
18003
18559
  throw new Error("Existing ios project shared App archive scheme is missing or unsafe.");
18004
18560
  }
18005
- const project = readFileSync21(path9.join(nativeDirectory, projectRelative), "utf8");
18561
+ const project = readFileSync22(path9.join(nativeDirectory, projectRelative), "utf8");
18006
18562
  const appTargetId = readIosAppTargetId(project);
18007
- const schemeSource = readFileSync21(path9.join(nativeDirectory, schemeRelative), "utf8");
18563
+ const schemeSource = readFileSync22(path9.join(nativeDirectory, schemeRelative), "utf8");
18008
18564
  const scheme = parseXml(schemeSource, "ios project App archive scheme").documentElement;
18009
18565
  if (scheme?.tagName !== "Scheme") {
18010
18566
  throw new Error("Existing ios project App archive scheme is malformed.");
@@ -18031,7 +18587,7 @@ function hasAndroidMergerDirectiveDeep(element) {
18031
18587
  return hasAndroidMergerDirective(element) || childElements(element).some(hasAndroidMergerDirectiveDeep);
18032
18588
  }
18033
18589
  function readAndroidApplication(file, required = true) {
18034
- const root = parseXml(readFileSync21(file, "utf8"), "android manifest").documentElement;
18590
+ const root = parseXml(readFileSync22(file, "utf8"), "android manifest").documentElement;
18035
18591
  const application = root === null ? void 0 : childElements(root).find((element) => element.tagName === "application");
18036
18592
  if (root?.tagName !== "manifest" || required && application === void 0) {
18037
18593
  throw new Error("Existing android project manifest application is missing.");
@@ -18060,9 +18616,9 @@ function readAndroidString(nativeDirectory, key) {
18060
18616
  throw new Error("Existing android project application label is unsupported.");
18061
18617
  }
18062
18618
  const overlay = path9.join(nativeDirectory, "app/src/release/res/values");
18063
- const overlayValues = existsSync21(overlay) && lstatSync12(overlay).isDirectory() ? readdirSync6(overlay).filter((file) => file.endsWith(".xml")).flatMap((file) => readAndroidValueResource(path9.join(overlay, file), "string", key)) : [];
18619
+ const overlayValues = existsSync22(overlay) && lstatSync12(overlay).isDirectory() ? readdirSync6(overlay).filter((file) => file.endsWith(".xml")).flatMap((file) => readAndroidValueResource(path9.join(overlay, file), "string", key)) : [];
18064
18620
  const main = path9.join(nativeDirectory, "app/src/main/res/values");
18065
- const mainValues = existsSync21(main) && lstatSync12(main).isDirectory() ? readdirSync6(main).filter((file) => file.endsWith(".xml")).flatMap((file) => readAndroidValueResource(path9.join(main, file), "string", key)) : [];
18621
+ const mainValues = existsSync22(main) && lstatSync12(main).isDirectory() ? readdirSync6(main).filter((file) => file.endsWith(".xml")).flatMap((file) => readAndroidValueResource(path9.join(main, file), "string", key)) : [];
18066
18622
  const values = overlayValues.length > 0 ? overlayValues : mainValues;
18067
18623
  if (values.length !== 1) {
18068
18624
  throw new Error("Existing android project application label resource is missing or ambiguous.");
@@ -18074,7 +18630,7 @@ function readAndroidString(nativeDirectory, key) {
18074
18630
  return values[0] ?? "";
18075
18631
  }
18076
18632
  function readAndroidValueResource(file, type, name) {
18077
- const root = parseXml(readFileSync21(file, "utf8"), "android values resource").documentElement;
18633
+ const root = parseXml(readFileSync22(file, "utf8"), "android values resource").documentElement;
18078
18634
  if (root?.tagName !== "resources") {
18079
18635
  throw new Error("Existing android values resource is malformed.");
18080
18636
  }
@@ -18142,30 +18698,61 @@ function isNativeFile(directory, relative14) {
18142
18698
  let current = directory;
18143
18699
  for (const segment of within.split(path9.sep)) {
18144
18700
  current = path9.join(current, segment);
18145
- if (!existsSync21(current) || lstatSync12(current).isSymbolicLink()) {
18701
+ if (!existsSync22(current) || lstatSync12(current).isSymbolicLink()) {
18146
18702
  return false;
18147
18703
  }
18148
18704
  }
18149
18705
  return lstatSync12(file).isFile();
18150
18706
  }
18707
+ var androidAdvertisingRendererService = "dev.mpgd.capacitor.AdvertisingMessagingService";
18708
+ var androidMessagingPluginService = "io.capawesome.capacitorjs.plugins.firebase.messaging.MessagingService";
18709
+ var androidAdvertisingRendererMetaData = "dev.mpgd.notifications.advertising_renderer";
18710
+ var androidAdvertisingRendererServiceEntry = [
18711
+ `<service android:name="${androidAdvertisingRendererService}" android:exported="false">`,
18712
+ " <intent-filter>",
18713
+ ' <action android:name="com.google.firebase.MESSAGING_EVENT" />',
18714
+ " </intent-filter>",
18715
+ ` <meta-data android:name="${androidAdvertisingRendererMetaData}" android:value="true" />`,
18716
+ "</service>"
18717
+ ];
18718
+ var androidMessagingPluginServiceRemoval = `<service android:name="${androidMessagingPluginService}" tools:node="remove" />`;
18719
+ var androidAdvertisingRendererEntries = [
18720
+ ...androidAdvertisingRendererServiceEntry,
18721
+ androidMessagingPluginServiceRemoval
18722
+ ];
18151
18723
 
18152
18724
  // node_modules/.cache/mpgd-native-build/tools/target/native-release-identity.js
18725
+ var nativeVersionStampEnvironmentName = "MPGD_NATIVE_VERSION_STAMP";
18726
+ function resolveNativeVersionSource(environment) {
18727
+ const value = environment[nativeVersionStampEnvironmentName]?.trim();
18728
+ if (value === void 0 || value === "" || value === "0") {
18729
+ return "source";
18730
+ }
18731
+ if (value === "1") {
18732
+ return "stamped";
18733
+ }
18734
+ throw new Error(`${nativeVersionStampEnvironmentName} must be 1 or 0, received ${value}.`);
18735
+ }
18153
18736
  function assertNativeReleaseIdentity(input) {
18154
18737
  const expected = resolveExpectedNativeIdentity(input.platform, input.metadata, input.environment, input.required);
18155
18738
  if (expected === void 0) {
18739
+ if (input.versionSource === "stamped") {
18740
+ throw new Error(`${nativeVersionStampEnvironmentName}=1 requires the reserved native version numbers.`);
18741
+ }
18156
18742
  return;
18157
18743
  }
18158
18744
  assertNativeVersionMatchesGameVersion(expected, input.environment, input.required);
18745
+ const checkVersions = input.versionSource !== "stamped";
18159
18746
  if (expected.kind === "android") {
18160
18747
  const candidates = [
18161
- join13(input.shellApp, "android/app/build.gradle"),
18162
- join13(input.shellApp, "android/app/build.gradle.kts")
18163
- ].filter((file) => existsSync22(file));
18748
+ join14(input.shellApp, "android/app/build.gradle"),
18749
+ join14(input.shellApp, "android/app/build.gradle.kts")
18750
+ ].filter((file) => existsSync23(file));
18164
18751
  if (candidates.length !== 1) {
18165
18752
  throw new Error("Android native release requires exactly one Gradle app build file.");
18166
18753
  }
18167
- assertAndroidIdentity(candidates[0] ?? "", expected);
18168
- assertAndroidAppliedScripts(candidates[0] ?? "", join13(input.shellApp, "android"));
18754
+ assertAndroidIdentity(candidates[0] ?? "", expected, checkVersions);
18755
+ assertAndroidAppliedScripts(candidates[0] ?? "", join14(input.shellApp, "android"));
18169
18756
  if (input.required) {
18170
18757
  const name = requireValue(input.metadata?.displayName, "Android target metadata displayName");
18171
18758
  assertAndroidReleaseDisplayName(input.shellApp, name);
@@ -18173,7 +18760,7 @@ function assertNativeReleaseIdentity(input) {
18173
18760
  }
18174
18761
  return;
18175
18762
  }
18176
- assertIosIdentity(join13(input.shellApp, "ios/App/App.xcodeproj/project.pbxproj"), expected);
18763
+ assertIosIdentity(join14(input.shellApp, "ios/App/App.xcodeproj/project.pbxproj"), expected, checkVersions);
18177
18764
  if (input.required) {
18178
18765
  const name = requireValue(input.metadata?.displayName, "iOS target metadata displayName");
18179
18766
  assertIosReleaseDisplayName(input.shellApp, name);
@@ -18184,6 +18771,161 @@ function runNativeSyncWithIdentityCheck(input, synchronize) {
18184
18771
  synchronize();
18185
18772
  assertNativeReleaseIdentity(input);
18186
18773
  }
18774
+ function stampNativeReleaseVersions(input) {
18775
+ if (input.platform === "android") {
18776
+ const versionCode = requirePositiveInteger2(optional(input.environment.MPGD_TARGET_VERSION_CODE), "MPGD_TARGET_VERSION_CODE");
18777
+ const versionName = requireFinalSemVer2(optional(input.environment.MPGD_TARGET_VERSION_NAME), "MPGD_TARGET_VERSION_NAME");
18778
+ const candidates = [
18779
+ join14(input.stagedShellApp, "android/app/build.gradle"),
18780
+ join14(input.stagedShellApp, "android/app/build.gradle.kts")
18781
+ ].filter((file3) => existsSync23(file3));
18782
+ if (candidates.length !== 1) {
18783
+ throw new Error("Android native release requires exactly one Gradle app build file.");
18784
+ }
18785
+ const file2 = candidates[0] ?? "";
18786
+ writeFileSync16(file2, stampAndroidGradleVersions(readFileSync23(file2, "utf8"), {
18787
+ versionCode,
18788
+ versionName
18789
+ }, file2));
18790
+ return;
18791
+ }
18792
+ const marketingVersion = requireFinalSemVer2(optional(input.environment.MPGD_TARGET_MARKETING_VERSION), "MPGD_TARGET_MARKETING_VERSION");
18793
+ const buildNumber = requirePositiveInteger2(optional(input.environment.MPGD_TARGET_BUILD_NUMBER), "MPGD_TARGET_BUILD_NUMBER");
18794
+ const file = join14(input.stagedShellApp, "ios/App/App.xcodeproj/project.pbxproj");
18795
+ writeFileSync16(file, stampIosProjectVersions(readRequiredFile(file, "iOS Xcode project configuration"), { marketingVersion, buildNumber }, file));
18796
+ }
18797
+ function stampAndroidGradleVersions(source, versions, file) {
18798
+ const end = "(?=\\s*(?:;|\\r?\\n|\\}|$))";
18799
+ const code = new RegExp(`(\\bversionCode\\s*(?:=\\s*)?)\\d+${end}`, "gu");
18800
+ const name = new RegExp(`(\\bversionName\\s*(?:=\\s*)?)(?:"[^"']+"|'[^"']+')${end}`, "gu");
18801
+ const masked = blankComments(source);
18802
+ const codeEdits = [...masked.matchAll(code)].map((match) => ({
18803
+ start: (match.index ?? 0) + (match[1]?.length ?? 0),
18804
+ end: (match.index ?? 0) + match[0].length,
18805
+ text: versions.versionCode
18806
+ }));
18807
+ const nameEdits = [...masked.matchAll(name)].map((match) => ({
18808
+ start: (match.index ?? 0) + (match[1]?.length ?? 0) + 1,
18809
+ end: (match.index ?? 0) + match[0].length - 1,
18810
+ text: versions.versionName
18811
+ }));
18812
+ if (codeEdits.length === 0 || nameEdits.length === 0) {
18813
+ throw new Error(`Native version stamping found no literal versionCode and versionName in ${file}.`);
18814
+ }
18815
+ const edits = [...codeEdits, ...nameEdits];
18816
+ return applyTextEdits(source, edits);
18817
+ }
18818
+ function stampIosProjectVersions(source, versions, file) {
18819
+ const { appTarget: appTarget2 } = findIosBlocks(source, file);
18820
+ if (appTarget2 === void 0) {
18821
+ throw new Error(`Native release preflight could not find the App target in ${file}.`);
18822
+ }
18823
+ const listId = readPbxReference(appTarget2, /\bbuildConfigurationList\s*=\s*([A-F0-9]+)\b/u, "the App target build configuration list", file);
18824
+ const releaseId = readPbxReference(readPbxObject2(source, listId, file), /\b([A-F0-9]+)\s*\/\*\s*Release\s*\*\//u, "App target Release build configuration", file);
18825
+ const marker = source.match(new RegExp(`\\b${releaseId}\\s*\\/\\*\\s*[^*]+\\s*\\*\\/\\s*=\\s*\\{`, "u"));
18826
+ if (marker?.index === void 0) {
18827
+ throw new Error(`Native release preflight could not find Xcode object ${releaseId} in ${file}.`);
18828
+ }
18829
+ const objectOpen = source.indexOf("{", marker.index);
18830
+ const objectBody = readBracedBlock(source, objectOpen, `Xcode object ${releaseId}`, file);
18831
+ const settingsIndex = objectBody.search(/\bbuildSettings\s*=\s*\{/u);
18832
+ if (settingsIndex === -1) {
18833
+ throw new Error(`Native release preflight could not find App target Release build settings in ${file}.`);
18834
+ }
18835
+ const settingsOpen = objectOpen + 1 + objectBody.indexOf("{", settingsIndex);
18836
+ const settings = readBracedBlock(source, settingsOpen, "App target Release build settings", file);
18837
+ const masked = blankComments(settings);
18838
+ const edits = [];
18839
+ const additions = [];
18840
+ for (const [key, value] of [
18841
+ ["MARKETING_VERSION", versions.marketingVersion],
18842
+ ["CURRENT_PROJECT_VERSION", versions.buildNumber]
18843
+ ]) {
18844
+ const assignment = new RegExp(`((?:^|[\\s{;])["']?${key}["']?\\s*=\\s*)[^;]+;`, "gu");
18845
+ const matches = [...masked.matchAll(assignment)];
18846
+ for (const match of matches) {
18847
+ edits.push({
18848
+ start: (match.index ?? 0) + (match[1]?.length ?? 0),
18849
+ end: (match.index ?? 0) + match[0].length - 1,
18850
+ text: value
18851
+ });
18852
+ }
18853
+ if (matches.length === 0) {
18854
+ additions.push(`${key} = ${value};`);
18855
+ }
18856
+ }
18857
+ const indentation = /\n([ \t]*)\S/u.exec(settings)?.[1] ?? " ";
18858
+ const stamped = `${additions.map((line) => `
18859
+ ${indentation}${line}`).join("")}${applyTextEdits(settings, edits)}`;
18860
+ return `${source.slice(0, settingsOpen + 1)}${stamped}${source.slice(settingsOpen + 1 + settings.length)}`;
18861
+ }
18862
+ function applyTextEdits(source, edits) {
18863
+ let result = source;
18864
+ for (const edit of [...edits].sort((left, right) => right.start - left.start)) {
18865
+ result = `${result.slice(0, edit.start)}${edit.text}${result.slice(edit.end)}`;
18866
+ }
18867
+ return result;
18868
+ }
18869
+ function blankComments(source) {
18870
+ const stripped = stripComments(source);
18871
+ if (stripped.length === source.length) {
18872
+ return source;
18873
+ }
18874
+ let result = "";
18875
+ let quote;
18876
+ let lineComment = false;
18877
+ let blockComment = false;
18878
+ for (let index = 0; index < source.length; index += 1) {
18879
+ const character = source[index] ?? "";
18880
+ const next = source[index + 1];
18881
+ if (lineComment) {
18882
+ if (character === "\n") {
18883
+ lineComment = false;
18884
+ result += character;
18885
+ } else {
18886
+ result += character === "\r" ? character : " ";
18887
+ }
18888
+ continue;
18889
+ }
18890
+ if (blockComment) {
18891
+ if (character === "*" && next === "/") {
18892
+ blockComment = false;
18893
+ result += " ";
18894
+ index += 1;
18895
+ } else {
18896
+ result += character === "\n" || character === "\r" ? character : " ";
18897
+ }
18898
+ continue;
18899
+ }
18900
+ if (quote !== void 0) {
18901
+ result += character;
18902
+ if (character === "\\") {
18903
+ result += next ?? "";
18904
+ index += 1;
18905
+ } else if (character === quote) {
18906
+ quote = void 0;
18907
+ }
18908
+ continue;
18909
+ }
18910
+ if (character === "/" && next === "/") {
18911
+ lineComment = true;
18912
+ result += " ";
18913
+ index += 1;
18914
+ continue;
18915
+ }
18916
+ if (character === "/" && next === "*") {
18917
+ blockComment = true;
18918
+ result += " ";
18919
+ index += 1;
18920
+ continue;
18921
+ }
18922
+ if (character === '"' || character === "'") {
18923
+ quote = character;
18924
+ }
18925
+ result += character;
18926
+ }
18927
+ return result;
18928
+ }
18187
18929
  function resolveExpectedNativeIdentity(platform, metadata, environment, required) {
18188
18930
  if (platform === "android") {
18189
18931
  const versionName = optional(environment.MPGD_TARGET_VERSION_NAME);
@@ -18210,7 +18952,7 @@ function resolveExpectedNativeIdentity(platform, metadata, environment, required
18210
18952
  marketingVersion: requireFinalSemVer2(marketingVersion, "MPGD_TARGET_MARKETING_VERSION")
18211
18953
  };
18212
18954
  }
18213
- function assertAndroidIdentity(file, expected) {
18955
+ function assertAndroidIdentity(file, expected, checkVersions) {
18214
18956
  const source = stripComments(readRequiredFile(file, "Android Gradle configuration"));
18215
18957
  const code = maskGradleStrings(source);
18216
18958
  if (hasGradleBracketIdentityWrite(source)) {
@@ -18240,8 +18982,8 @@ function assertAndroidIdentity(file, expected) {
18240
18982
  const codePattern = new RegExp(`\\bversionCode\\s*(?:=\\s*)?(\\d+)${end}`, "u");
18241
18983
  const namePattern = new RegExp(`\\bversionName\\s*(?:=\\s*)?["']([^"']+)["']${end}`, "u");
18242
18984
  assertAndroidSetting(source, "applicationId", appIdPattern, expected.packageId, file);
18243
- assertAndroidSetting(source, "versionCode", codePattern, expected.versionCode, file);
18244
- assertAndroidSetting(source, "versionName", namePattern, expected.versionName, file);
18985
+ assertAndroidSetting(source, "versionCode", codePattern, checkVersions ? expected.versionCode : void 0, file);
18986
+ assertAndroidSetting(source, "versionName", namePattern, checkVersions ? expected.versionName : void 0, file);
18245
18987
  }
18246
18988
  function assertAndroidSetting(source, key, expression, expected, file) {
18247
18989
  const mentions = countGradleIdentityWrites(source, key);
@@ -18312,7 +19054,7 @@ function assertAndroidAppliedScripts(appBuild, androidRoot) {
18312
19054
  throw new Error("Native release preflight cannot resolve an applied Gradle script.");
18313
19055
  }
18314
19056
  };
18315
- const rootFiles = ["build.gradle", "build.gradle.kts"].map((name) => join13(androidRoot, name)).filter((file) => existsSync22(file));
19057
+ const rootFiles = ["build.gradle", "build.gradle.kts"].map((name) => join14(androidRoot, name)).filter((file) => existsSync23(file));
18316
19058
  if (rootFiles.length !== 1) {
18317
19059
  throw new Error("Native release preflight requires one Android root Gradle build file.");
18318
19060
  }
@@ -18322,7 +19064,7 @@ function assertAndroidAppliedScripts(appBuild, androidRoot) {
18322
19064
  if (/\b(?:afterEvaluate|projectsEvaluated)\b/u.test(rootCode) && /\b(?:project|subprojects|allprojects|android)\b/u.test(rootCode)) {
18323
19065
  throw new Error("Native release preflight cannot resolve root Gradle app callbacks.");
18324
19066
  }
18325
- const settingsFiles = ["settings.gradle", "settings.gradle.kts"].map((name) => join13(androidRoot, name)).filter((file) => existsSync22(file));
19067
+ const settingsFiles = ["settings.gradle", "settings.gradle.kts"].map((name) => join14(androidRoot, name)).filter((file) => existsSync23(file));
18326
19068
  if (settingsFiles.length !== 1) {
18327
19069
  throw new Error("Native release preflight requires one Android settings Gradle file.");
18328
19070
  }
@@ -18334,19 +19076,19 @@ function assertAndroidAppliedScripts(appBuild, androidRoot) {
18334
19076
  }
18335
19077
  inspect(settingsFile, true, androidRoot);
18336
19078
  inspect(rootFile, false, androidRoot);
18337
- inspect(appBuild, false, dirname9(appBuild));
19079
+ inspect(appBuild, false, dirname10(appBuild));
18338
19080
  }
18339
- function assertIosIdentity(file, expected) {
19081
+ function assertIosIdentity(file, expected, checkVersions) {
18340
19082
  const source = readRequiredFile(file, "iOS Xcode project configuration");
18341
19083
  assertIosReleaseProductName(source);
18342
19084
  assertIosReleaseInfoPlistExpansion(source);
18343
19085
  assertIosReleasePackagingSettings(source);
18344
19086
  const releaseSettings = readIosAppReleaseSettings(source, file);
18345
19087
  assertIosSetting(releaseSettings, "PRODUCT_BUNDLE_IDENTIFIER", expected.bundleId, file);
18346
- assertIosSetting(releaseSettings, "MARKETING_VERSION", expected.marketingVersion, file);
18347
- assertIosSetting(releaseSettings, "CURRENT_PROJECT_VERSION", expected.buildNumber, file);
19088
+ assertIosSetting(releaseSettings, "MARKETING_VERSION", checkVersions ? expected.marketingVersion : void 0, file);
19089
+ assertIosSetting(releaseSettings, "CURRENT_PROJECT_VERSION", checkVersions ? expected.buildNumber : void 0, file);
18348
19090
  const plistRelative = readIosReleaseInfoPlist(source);
18349
- const plist = join13(dirname9(dirname9(file)), plistRelative);
19091
+ const plist = join14(dirname10(dirname10(file)), plistRelative);
18350
19092
  assertIosReleasePlistIdentity(readRequiredFile(plist, "iOS Release Info.plist"));
18351
19093
  }
18352
19094
  function assertIosSetting(settings, key, expected, file) {
@@ -18355,6 +19097,15 @@ function assertIosSetting(settings, key, expected, file) {
18355
19097
  const conditional = new RegExp(conditionalPattern, "u");
18356
19098
  const target2 = stripComments(settings.target.values);
18357
19099
  const project = stripComments(settings.project.values);
19100
+ if (expected === void 0 && key !== "PRODUCT_BUNDLE_IDENTIFIER") {
19101
+ if (conditional.test(target2) || conditional.test(project)) {
19102
+ throw new Error(`Native release preflight does not support conditional ${key} in ${file}.`);
19103
+ }
19104
+ if (readSettingValues(target2, expression).length > 1) {
19105
+ throw new Error(`Native release preflight has ambiguous App Release ${key} in ${file}.`);
19106
+ }
19107
+ return;
19108
+ }
18358
19109
  const targetRaw = readSettingValues(target2, expression);
18359
19110
  if (targetRaw.length > 1) {
18360
19111
  throw new Error(`Native release preflight has ambiguous App Release ${key} in ${file}.`);
@@ -18381,6 +19132,9 @@ function assertSettingValues(values, expression, expected, file) {
18381
19132
  if (values.length === 0) {
18382
19133
  throw new Error(`Native release preflight could not find ${expression.source} in ${file}.`);
18383
19134
  }
19135
+ if (expected === void 0) {
19136
+ return;
19137
+ }
18384
19138
  if (values.some((value) => value !== expected)) {
18385
19139
  throw new Error(`Native release identity mismatch in ${file}: expected ${expected}, received ${values.join(", ")}.`);
18386
19140
  }
@@ -18646,10 +19400,10 @@ function stripOptionalQuotes(value) {
18646
19400
  return value;
18647
19401
  }
18648
19402
  function readRequiredFile(file, label) {
18649
- if (!existsSync22(file)) {
19403
+ if (!existsSync23(file)) {
18650
19404
  throw new Error(`Missing ${label}: ${file}`);
18651
19405
  }
18652
- return readFileSync22(file, "utf8");
19406
+ return readFileSync23(file, "utf8");
18653
19407
  }
18654
19408
  function requireFinalSemVer2(value, label) {
18655
19409
  const normalized = requireValue(value, label);
@@ -18697,12 +19451,22 @@ async function executeNativeTargetBuild(input) {
18697
19451
  required: profile2 === "production",
18698
19452
  shellApp
18699
19453
  };
18700
- assertNativeReleaseIdentity(identityInput);
19454
+ const versionSource = resolveNativeVersionSource(environment);
19455
+ assertNativeReleaseIdentity({ ...identityInput, versionSource });
19456
+ reportNativeStorePolicy(inspectNativeStorePolicy({
19457
+ platform,
19458
+ shellApp,
19459
+ gameRoot: input.targetPath(target2.gameApp),
19460
+ metadata: target2.metadata
19461
+ }), profile2 === "production");
18701
19462
  const stage = createNativeShellStage({ shellApp, webDir });
18702
19463
  try {
18703
19464
  input.replaceDirectory(webBundle, stage.webDir);
18704
19465
  applyCapacitorShellCsp(stage.webDir);
18705
19466
  input.ensureCapacitorPlatform(stage.shellApp, platform, environment);
19467
+ if (versionSource === "stamped") {
19468
+ stampNativeReleaseVersions({ platform, stagedShellApp: stage.shellApp, environment });
19469
+ }
18706
19470
  await stageNativeIconResources(input.generatedIcons, stage.shellApp);
18707
19471
  runNativeSyncWithIdentityCheck({ ...identityInput, shellApp: stage.shellApp }, () => {
18708
19472
  input.run("pnpm", ["--dir", stage.shellApp, "cap", "sync", platform], environment);
@@ -18750,7 +19514,7 @@ function androidSignedReleaseGradleArgs(environment) {
18750
19514
  if (initScript === void 0 || initScript === "") {
18751
19515
  return ["bundleRelease", "--no-daemon"];
18752
19516
  }
18753
- if (!isAbsolute12(initScript) || !existsSync23(initScript) || !statSync6(initScript).isFile()) {
19517
+ if (!isAbsolute12(initScript) || !existsSync24(initScript) || !statSync6(initScript).isFile()) {
18754
19518
  throw new Error("Android upload signing init script must be an existing absolute file.");
18755
19519
  }
18756
19520
  return ["bundleRelease", "--no-daemon", "--init-script", initScript];
@@ -18775,7 +19539,7 @@ function executeIosBuildWithEnvironment(input, stagedShell, environment) {
18775
19539
  let releaseArtifact;
18776
19540
  if (plan.mode === "unsigned-archive") {
18777
19541
  releaseArtifact = `${artifactRoot}/App.xcarchive`;
18778
- mkdirSync12(dirname10(input.targetPath(releaseArtifact)), { recursive: true });
19542
+ mkdirSync12(dirname11(input.targetPath(releaseArtifact)), { recursive: true });
18779
19543
  input.run("xcodebuild", [
18780
19544
  "archive",
18781
19545
  "-project",
@@ -18791,7 +19555,7 @@ function executeIosBuildWithEnvironment(input, stagedShell, environment) {
18791
19555
  "CODE_SIGNING_ALLOWED=NO"
18792
19556
  ], environment, `${stagedShell}/ios`);
18793
19557
  } else if (plan.mode === "simulator") {
18794
- const buildRoot = mkdtempSync4(join14(tmpdir4(), "mpgd-ios-simulator-"));
19558
+ const buildRoot = mkdtempSync4(join15(tmpdir4(), "mpgd-ios-simulator-"));
18795
19559
  releaseArtifact = `${artifactRoot}/App.app`;
18796
19560
  try {
18797
19561
  input.run("xcodebuild", [
@@ -18805,7 +19569,7 @@ function executeIosBuildWithEnvironment(input, stagedShell, environment) {
18805
19569
  "-sdk",
18806
19570
  "iphonesimulator",
18807
19571
  `SYMROOT=${buildRoot}`,
18808
- `OBJROOT=${join14(buildRoot, "Intermediates.noindex")}`,
19572
+ `OBJROOT=${join15(buildRoot, "Intermediates.noindex")}`,
18809
19573
  "INFOPLIST_FILE=App/Info-Smoke.plist",
18810
19574
  "EXCLUDED_SOURCE_FILE_NAMES=Main.storyboard LaunchScreen.storyboard Assets.xcassets",
18811
19575
  "ASSETCATALOG_COMPILER_APPICON_NAME=",
@@ -18825,7 +19589,7 @@ function executeIosBuildWithEnvironment(input, stagedShell, environment) {
18825
19589
  } else if (plan.mode === "signed-archive" || plan.mode === "store-export") {
18826
19590
  const signing = resolveIosSigningPlan(environment, plan.mode);
18827
19591
  const archiveArtifact = `${artifactRoot}/App.xcarchive`;
18828
- mkdirSync12(dirname10(input.targetPath(archiveArtifact)), { recursive: true });
19592
+ mkdirSync12(dirname11(input.targetPath(archiveArtifact)), { recursive: true });
18829
19593
  input.run("xcodebuild", [
18830
19594
  "archive",
18831
19595
  "-project",
@@ -18849,7 +19613,7 @@ function executeIosBuildWithEnvironment(input, stagedShell, environment) {
18849
19613
  inspectSignedIosArchive(input.targetPath(archiveArtifact), expectedIosIdentity);
18850
19614
  releaseArtifact = archiveArtifact;
18851
19615
  if (plan.mode === "store-export") {
18852
- const exportRoot = mkdtempSync4(join14(tmpdir4(), "mpgd-ios-export-"));
19616
+ const exportRoot = mkdtempSync4(join15(tmpdir4(), "mpgd-ios-export-"));
18853
19617
  try {
18854
19618
  input.run("xcodebuild", [
18855
19619
  "-exportArchive",
@@ -18865,7 +19629,7 @@ function executeIosBuildWithEnvironment(input, stagedShell, environment) {
18865
19629
  throw new Error("iOS store export must produce exactly one IPA.");
18866
19630
  }
18867
19631
  releaseArtifact = `${artifactRoot}/App.ipa`;
18868
- input.copyFile(join14(exportRoot, exportedIpas[0] ?? ""), input.targetPath(releaseArtifact));
19632
+ input.copyFile(join15(exportRoot, exportedIpas[0] ?? ""), input.targetPath(releaseArtifact));
18869
19633
  inspectSignedIosIpa(input.targetPath(releaseArtifact), expectedIosIdentity);
18870
19634
  } finally {
18871
19635
  rmSync11(exportRoot, { recursive: true, force: true });
@@ -18978,7 +19742,7 @@ var releaseManifestEnvKeys = [
18978
19742
  var platformTargets = loadPlatformTargetsConfig();
18979
19743
  var configBaseDir = platformTargets.baseDir;
18980
19744
  var kitLinkCheck = resolve18("tools/workspace/relink-kit-workspace.mjs");
18981
- if (existsSync24(kitLinkCheck)) {
19745
+ if (existsSync25(kitLinkCheck)) {
18982
19746
  spawnSync5(process.execPath, [kitLinkCheck, "--warn-unless-owner", configBaseDir], {
18983
19747
  stdio: "inherit"
18984
19748
  });
@@ -19060,11 +19824,11 @@ assertProductionTargetReadiness({
19060
19824
  ...runtimeTargetConfigMatrix.targets[targetName] === void 0 ? {} : { targetPolicy: runtimeTargetConfigMatrix.targets[targetName] },
19061
19825
  ...gameServicesUrl === void 0 ? {} : { gameServicesUrl }
19062
19826
  });
19063
- var nativeWebBundle = nativePlan === void 0 ? void 0 : mkdtempSync5(join15(tmpdir5(), "mpgd-native-web-"));
19827
+ var nativeWebBundle = nativePlan === void 0 ? void 0 : mkdtempSync5(join16(tmpdir5(), "mpgd-native-web-"));
19064
19828
  var nativeArtifactRoot = nativeAttempt === void 0 ? void 0 : `release-output/native/${targetName}/${safeArtifactFileStem(profile)}/${nativeAttempt.runId}`;
19065
19829
  var nativeCompleted = false;
19066
19830
  var runtimeTargetConfigMatrixFile = createRuntimeTargetConfigMatrixFile(runtimeTargetConfigMatrix);
19067
- var assetTargetTemporary = target.assetPacks === void 0 ? void 0 : mkdtempSync5(join15(tmpdir5(), "mpgd-target-assets-"));
19831
+ var assetTargetTemporary = target.assetPacks === void 0 ? void 0 : mkdtempSync5(join16(tmpdir5(), "mpgd-target-assets-"));
19068
19832
  var assetTargetReport;
19069
19833
  var env = {
19070
19834
  ...process.env,
@@ -19093,7 +19857,7 @@ try {
19093
19857
  assetTargetReport = await buildAssetPackTarget({
19094
19858
  policy: target.assetPacks,
19095
19859
  baseDir: configBaseDir,
19096
- outDir: join15(assetTargetTemporary, "deployment")
19860
+ outDir: join16(assetTargetTemporary, "deployment")
19097
19861
  });
19098
19862
  }
19099
19863
  if (targetName === "microsoft-store" && target.kind === "web" && profile === "production") {
@@ -19126,10 +19890,10 @@ try {
19126
19890
  case "wechat-minigame": {
19127
19891
  const outputConfigPath = requireString2(target.output, `${targetName}.output`);
19128
19892
  const output = targetPath(outputConfigPath);
19129
- const bundleRoot = mkdtempSync5(join15(tmpdir5(), "mpgd-wechat-bundles-"));
19893
+ const bundleRoot = mkdtempSync5(join16(tmpdir5(), "mpgd-wechat-bundles-"));
19130
19894
  try {
19131
- const runtimeBundleRoot = join15(bundleRoot, "runtime");
19132
- const gameBundleRoot = join15(bundleRoot, "game");
19895
+ const runtimeBundleRoot = join16(bundleRoot, "runtime");
19896
+ const gameBundleRoot = join16(bundleRoot, "game");
19133
19897
  buildMiniGameBundle("runtime", runtimeBundleRoot, bundleRoot, gameApp, profile, env);
19134
19898
  buildMiniGameBundle("game", gameBundleRoot, bundleRoot, gameApp, profile, env);
19135
19899
  const effectiveTargetConfig = generateEffectiveTargetConfigArtifact(targetName, env);
@@ -19222,7 +19986,7 @@ try {
19222
19986
  mirrorAitRuntimeAssets(gameApp, wrapperApp);
19223
19987
  run("pnpm", ["--dir", wrapperApp, "exec", "vite", "build", "--mode", profile], env);
19224
19988
  if (assetTargetReport !== void 0) {
19225
- await assertAssetPackTargetArtifact(assetTargetReport, `${wrapperApp}/dist`, join15(relative13(join15(wrapperApp, "public"), webDir), assetPackTargetNamespace));
19989
+ await assertAssetPackTargetArtifact(assetTargetReport, `${wrapperApp}/dist`, join16(relative13(join16(wrapperApp, "public"), webDir), assetPackTargetNamespace));
19226
19990
  }
19227
19991
  let releaseArtifact = webDirConfigPath;
19228
19992
  if (process.env.MPGD_AIT_PACKAGE_MODE !== "skip") {
@@ -19256,7 +20020,7 @@ try {
19256
20020
  await stageTargetAssetPacks(webDir);
19257
20021
  stageWebIconEvidence(generatedIcons, webDir);
19258
20022
  if (assetTargetReport !== void 0) {
19259
- await assertAssetPackTargetArtifact(assetTargetReport, `${wrapperApp}/dist`, join15(relative13(join15(wrapperApp, "dist"), webDir), assetPackTargetNamespace));
20023
+ await assertAssetPackTargetArtifact(assetTargetReport, `${wrapperApp}/dist`, join16(relative13(join16(wrapperApp, "dist"), webDir), assetPackTargetNamespace));
19260
20024
  }
19261
20025
  await writeRemoteAssetPacks();
19262
20026
  writeManifest(targetName, profile, `${wrapperAppConfigPath}/dist`, env);
@@ -19303,12 +20067,12 @@ try {
19303
20067
  if (nativeWebBundle !== void 0) {
19304
20068
  rmSync12(nativeWebBundle, { force: true, recursive: true });
19305
20069
  }
19306
- rmSync12(dirname11(runtimeTargetConfigMatrixFile), { force: true, recursive: true });
20070
+ rmSync12(dirname12(runtimeTargetConfigMatrixFile), { force: true, recursive: true });
19307
20071
  }
19308
20072
  function createRuntimeTargetConfigMatrixFile(matrix) {
19309
- const directory = mkdtempSync5(join15(tmpdir5(), "mpgd-target-config-matrix-"));
19310
- const file = join15(directory, "matrix.json");
19311
- writeFileSync16(file, `${JSON.stringify(matrix)}
20073
+ const directory = mkdtempSync5(join16(tmpdir5(), "mpgd-target-config-matrix-"));
20074
+ const file = join16(directory, "matrix.json");
20075
+ writeFileSync17(file, `${JSON.stringify(matrix)}
19312
20076
  `);
19313
20077
  return file;
19314
20078
  }
@@ -19328,7 +20092,7 @@ function replaceDirectory(source, destination) {
19328
20092
  function mirrorAitRuntimeAssets(gameApp2, wrapperApp) {
19329
20093
  const sourceAssets = `${gameApp2}/dist/assets`;
19330
20094
  const destinationAssets = `${wrapperApp}/public/assets`;
19331
- if (existsSync24(sourceAssets)) {
20095
+ if (existsSync25(sourceAssets)) {
19332
20096
  replaceDirectory(sourceAssets, destinationAssets);
19333
20097
  } else {
19334
20098
  rmSync12(destinationAssets, { recursive: true, force: true });
@@ -19338,8 +20102,8 @@ async function stageTargetAssetPacks(webOutput) {
19338
20102
  if (assetTargetReport === void 0) {
19339
20103
  return;
19340
20104
  }
19341
- const destination = join15(webOutput, assetPackTargetNamespace);
19342
- if (existsSync24(destination)) {
20105
+ const destination = join16(webOutput, assetPackTargetNamespace);
20106
+ if (existsSync25(destination)) {
19343
20107
  throw new Error("mpgd-asset-packs is owned by the target policy; remove source/public copies from the game");
19344
20108
  }
19345
20109
  cpSync4(assetTargetReport.packagedDir, destination, {
@@ -19362,7 +20126,7 @@ async function writeRemoteAssetPacks() {
19362
20126
  });
19363
20127
  replaceDirectory(assetTargetReport.remoteDir, `${remoteOutput}/remote`);
19364
20128
  mkdirSync13(remoteOutput, { recursive: true });
19365
- writeFileSync16(`${remoteOutput}/report.json`, JSON.stringify({
20129
+ writeFileSync17(`${remoteOutput}/report.json`, JSON.stringify({
19366
20130
  ...assetTargetReport,
19367
20131
  outDir: remoteOutput,
19368
20132
  packagedDir: assetPackTargetNamespace,
@@ -19433,7 +20197,7 @@ function copyIosSyncSwiftPackage(shellApp, releaseArtifact, packageName, swiftPa
19433
20197
  const shellReference = readSwiftPackagePath(shellPackageFile, swiftPackageName);
19434
20198
  let referencedPackage;
19435
20199
  try {
19436
- referencedPackage = realpathSync10(resolve18(dirname11(shellPackageFile), shellReference));
20200
+ referencedPackage = realpathSync10(resolve18(dirname12(shellPackageFile), shellReference));
19437
20201
  } catch {
19438
20202
  throw new Error(`Missing iOS Swift package reference: ${swiftPackageName}`);
19439
20203
  }
@@ -19445,14 +20209,14 @@ function copyIosSyncSwiftPackage(shellApp, releaseArtifact, packageName, swiftPa
19445
20209
  }
19446
20210
  function rewriteIosSyncSwiftPackagePath(releaseArtifact, packageName, swiftPackageName) {
19447
20211
  const packageFile = targetPath(`${releaseArtifact}/App/CapApp-SPM/Package.swift`);
19448
- const contents = readFileSync23(packageFile, "utf8");
20212
+ const contents = readFileSync24(packageFile, "utf8");
19449
20213
  const originalPath = readSwiftPackagePath(packageFile, swiftPackageName);
19450
20214
  const rewrittenPath = `../../node_modules/${packageName}`;
19451
20215
  const marker = `.package(name: "${swiftPackageName}", path: "`;
19452
- writeFileSync16(packageFile, contents.replace(`${marker}${originalPath}"`, `${marker}${rewrittenPath}"`));
20216
+ writeFileSync17(packageFile, contents.replace(`${marker}${originalPath}"`, `${marker}${rewrittenPath}"`));
19453
20217
  }
19454
20218
  function readSwiftPackagePath(packageFile, swiftPackageName) {
19455
- const contents = readFileSync23(packageFile, "utf8");
20219
+ const contents = readFileSync24(packageFile, "utf8");
19456
20220
  const marker = `.package(name: "${swiftPackageName}", path: "`;
19457
20221
  const index = contents.indexOf(marker);
19458
20222
  if (index < 0 || contents.indexOf(marker, index + marker.length) >= 0) {
@@ -19466,11 +20230,11 @@ function readSwiftPackagePath(packageFile, swiftPackageName) {
19466
20230
  return contents.slice(pathStart, pathEnd);
19467
20231
  }
19468
20232
  function copyFile2(source, destination) {
19469
- mkdirSync13(dirname11(destination), { recursive: true });
20233
+ mkdirSync13(dirname12(destination), { recursive: true });
19470
20234
  cpSync4(source, destination);
19471
20235
  }
19472
20236
  function removeFilesByExtension(directory, extension) {
19473
- if (!existsSync24(directory)) {
20237
+ if (!existsSync25(directory)) {
19474
20238
  return;
19475
20239
  }
19476
20240
  for (const file of readdirSync8(directory)) {
@@ -19485,7 +20249,7 @@ function safeArtifactFileStem(value) {
19485
20249
  return stem.length === 0 ? "mpgd-kit" : stem;
19486
20250
  }
19487
20251
  function ensureCapacitorPlatform(shellApp, platform, commandEnv) {
19488
- if (!existsSync24(`${shellApp}/${platform}`)) {
20252
+ if (!existsSync25(`${shellApp}/${platform}`)) {
19489
20253
  run("pnpm", ["--dir", shellApp, "cap", "add", platform], commandEnv);
19490
20254
  }
19491
20255
  }
@@ -19522,7 +20286,7 @@ function findEmbeddedIconManifestArtifactPath(artifact) {
19522
20286
  function findNamedArtifactFiles(root, fileName) {
19523
20287
  const matches = [];
19524
20288
  for (const entry of readdirSync8(root)) {
19525
- const path10 = join15(root, entry);
20289
+ const path10 = join16(root, entry);
19526
20290
  const stat2 = lstatSync13(path10);
19527
20291
  if (stat2.isSymbolicLink()) {
19528
20292
  continue;
@@ -19572,8 +20336,8 @@ function isMiniGameTarget(target2) {
19572
20336
  return target2.kind === "wechat-minigame" || target2.kind === "tiktok-minigame";
19573
20337
  }
19574
20338
  function buildMiniGameBundle(bundleKind, outputDir, stagingRoot, gameApp2, profile2, commandEnv) {
19575
- const viteConfig = join15(gameApp2, "vite.minigame.config.ts");
19576
- if (!existsSync24(viteConfig)) {
20339
+ const viteConfig = join16(gameApp2, "vite.minigame.config.ts");
20340
+ if (!existsSync25(viteConfig)) {
19577
20341
  throw new Error(`Mini-game target requires a game-owned vite.minigame.config.ts: ${viteConfig}`);
19578
20342
  }
19579
20343
  run("pnpm", ["exec", "vite", "build", "--config", "vite.minigame.config.ts", "--mode", profile2], {