@mpgd/cli 0.59.0 → 0.61.1

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 (33) hide show
  1. package/dist/capacitor-shell-starter.d.ts +24 -0
  2. package/dist/capacitor-shell-starter.js +100 -8
  3. package/dist/crash-reporting-starter.d.ts +99 -0
  4. package/dist/crash-reporting-starter.js +271 -0
  5. package/dist/index.d.ts +5 -2
  6. package/dist/index.js +221 -14
  7. package/dist/ios-ipa-inspection.js +120 -1
  8. package/dist/native-build-info.json +2 -2
  9. package/dist/native-build-target.js +1617 -1185
  10. package/dist/native-deploy-run.d.ts +6 -0
  11. package/dist/native-deploy-run.js +19 -1
  12. package/dist/native-deploy-submission.d.ts +20 -1
  13. package/dist/native-deploy-submission.js +94 -7
  14. package/dist/native-shell-ios-push-files.d.ts +9 -0
  15. package/dist/native-shell-ios-push-files.js +65 -0
  16. package/dist/native-shell-store-policy.d.ts +109 -0
  17. package/dist/native-shell-store-policy.js +553 -7
  18. package/dist/play-internal-submission.d.ts +40 -4
  19. package/dist/play-internal-submission.js +215 -48
  20. package/dist/play-publisher-port.d.ts +26 -1
  21. package/dist/play-release-options.d.ts +97 -0
  22. package/dist/play-release-options.js +307 -0
  23. package/dist/play-release-promote.d.ts +59 -0
  24. package/dist/play-release-promote.js +235 -0
  25. package/dist/play-release-rollout.d.ts +57 -0
  26. package/dist/play-release-rollout.js +237 -0
  27. package/dist/play-sdk-adapter.js +26 -3
  28. package/dist/release-state.d.ts +6 -0
  29. package/dist/release-state.js +8 -0
  30. package/package.json +17 -17
  31. package/templates/phaser-game/apps/target-ait/package.json +2 -2
  32. package/templates/phaser-game/package.json +1 -1
  33. package/templates/phaser-game/vite.runtime-target-config.ts +4 -0
@@ -1,6 +1,6 @@
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 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";
3
+ import { cpSync as cpSync4, existsSync as existsSync27, lstatSync as lstatSync15, mkdirSync as mkdirSync13, mkdtempSync as mkdtempSync5, readdirSync as readdirSync9, readFileSync as readFileSync26, realpathSync as realpathSync10, rmSync as rmSync12, statSync as statSync7, writeFileSync as writeFileSync18 } from "node:fs";
4
4
  import { tmpdir as tmpdir5 } from "node:os";
5
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";
@@ -43,8 +43,8 @@ var knownMediaTypes = /* @__PURE__ */ new Map([
43
43
  ["aac", { mediaType: "audio/aac", defaultMethod: "store" }],
44
44
  ["flac", { mediaType: "audio/flac", defaultMethod: "store" }]
45
45
  ]);
46
- function phaserPackMediaTypeForPath(path10) {
47
- const extension = path10.slice(path10.lastIndexOf(".") + 1).toLowerCase();
46
+ function phaserPackMediaTypeForPath(path12) {
47
+ const extension = path12.slice(path12.lastIndexOf(".") + 1).toLowerCase();
48
48
  return knownMediaTypes.get(extension) ?? null;
49
49
  }
50
50
  function parsePhaserPackEntryPath(input) {
@@ -677,9 +677,9 @@ var manifestFileName = "asset-pack-delivery.json";
677
677
  var stagingSequence = 0;
678
678
  var sha256Of = (data) => createHash("sha256").update(data).digest("hex");
679
679
  function realpathBestEffort(target2) {
680
- const exists = (path10) => {
680
+ const exists = (path12) => {
681
681
  try {
682
- lstatSync(path10);
682
+ lstatSync(path12);
683
683
  return true;
684
684
  } catch {
685
685
  return false;
@@ -915,11 +915,11 @@ function buildAssetPacks(options) {
915
915
  `, "utf8");
916
916
  validatePhaserPackDeliveryManifest(JSON.parse(manifestBytes.toString("utf8")));
917
917
  const outputRecords = [];
918
- for (const [path10, data] of outputs) {
919
- const target2 = join(outPath, path10);
918
+ for (const [path12, data] of outputs) {
919
+ const target2 = join(outPath, path12);
920
920
  if (!existsSync(target2)) {
921
921
  outputRecords.push({
922
- path: path10,
922
+ path: path12,
923
923
  bytes: data.length,
924
924
  sha256: sha256Of(data),
925
925
  action: "written",
@@ -931,13 +931,13 @@ function buildAssetPacks(options) {
931
931
  try {
932
932
  existing = readFileSync(target2);
933
933
  } catch {
934
- throw new Error(`Output path is not a readable file: ${path10}`);
934
+ throw new Error(`Output path is not a readable file: ${path12}`);
935
935
  }
936
936
  if (!existing.equals(data)) {
937
- throw new Error(`Output path already holds different bytes (immutable conflict): ${path10}. Change the pack content or revision, or build into a new directory.`);
937
+ throw new Error(`Output path already holds different bytes (immutable conflict): ${path12}. Change the pack content or revision, or build into a new directory.`);
938
938
  }
939
939
  outputRecords.push({
940
- path: path10,
940
+ path: path12,
941
941
  bytes: data.length,
942
942
  sha256: sha256Of(data),
943
943
  action: "unchanged",
@@ -1566,7 +1566,7 @@ var positiveIntegerOption = (failures, name, value) => {
1566
1566
  };
1567
1567
  var VerifyDeadlineError = class extends Error {
1568
1568
  };
1569
- var streamSha256 = async (path10, handle, hasher, deadline) => {
1569
+ var streamSha256 = async (path12, handle, hasher, deadline) => {
1570
1570
  let bytes = 0;
1571
1571
  const stream = handle.createReadStream({
1572
1572
  highWaterMark: STREAM_CHUNK_BYTES,
@@ -1586,7 +1586,7 @@ var streamSha256 = async (path10, handle, hasher, deadline) => {
1586
1586
  if (error instanceof VerifyDeadlineError) {
1587
1587
  throw error;
1588
1588
  }
1589
- throw new Error(`Could not read ${path10}: ${errorText(error)}`);
1589
+ throw new Error(`Could not read ${path12}: ${errorText(error)}`);
1590
1590
  } finally {
1591
1591
  clearTimeout(timer);
1592
1592
  }
@@ -1802,20 +1802,20 @@ var readManifestCapped = async (manifestPath, cap, deadline) => {
1802
1802
  }
1803
1803
  return Buffer.concat(chunks);
1804
1804
  };
1805
- var readExact = async (path10, handle, bytes, deadline) => {
1805
+ var readExact = async (path12, handle, bytes, deadline) => {
1806
1806
  const buffer = Buffer.alloc(bytes);
1807
1807
  let received = 0;
1808
1808
  while (received < bytes) {
1809
1809
  const { bytesRead } = await deadline.race(handle.read(buffer, received, bytes - received, received));
1810
1810
  if (bytesRead === 0) {
1811
- throw new Error(`Archive shrank while being read: ${path10}`);
1811
+ throw new Error(`Archive shrank while being read: ${path12}`);
1812
1812
  }
1813
1813
  received += bytesRead;
1814
1814
  }
1815
1815
  const tail = Buffer.alloc(1);
1816
1816
  const { bytesRead: tailBytes } = await deadline.race(handle.read(tail, 0, 1, bytes));
1817
1817
  if (tailBytes !== 0) {
1818
- throw new Error(`Archive grew while being read: ${path10}`);
1818
+ throw new Error(`Archive grew while being read: ${path12}`);
1819
1819
  }
1820
1820
  return buffer;
1821
1821
  };
@@ -1882,7 +1882,7 @@ async function verifyAssetPackDelivery(options) {
1882
1882
  positiveIntegerOption(failures, "maxEntryBytes", maxEntryBytes),
1883
1883
  positiveIntegerOption(failures, "maxExpandedBytes", maxExpandedBytes),
1884
1884
  ...Object.entries(hostLimits ?? {}).map(([name, value]) => positiveIntegerOption(failures, `--${name.replace(/[A-Z]/gu, (letter) => `-${letter.toLowerCase()}`)}`, value))
1885
- ].every((valid2) => valid2);
1885
+ ].every((valid3) => valid3);
1886
1886
  const startedAt = performance.now();
1887
1887
  const deadlineBreached = () => {
1888
1888
  if (performance.now() - startedAt <= verifyTimeoutMs) {
@@ -2269,9 +2269,9 @@ var assetPackTargetNamespace = "mpgd-asset-packs";
2269
2269
  var assetPackTargetManifestName = "asset-pack-delivery.json";
2270
2270
  var assetPackTargetPolicyName = "target-policy.json";
2271
2271
  var digest = (bytes) => createHash3("sha256").update(bytes).digest("hex");
2272
- async function fileDigest(path10) {
2272
+ async function fileDigest(path12) {
2273
2273
  const hash = createHash3("sha256");
2274
- for await (const chunk of createReadStream(path10)) {
2274
+ for await (const chunk of createReadStream(path12)) {
2275
2275
  hash.update(chunk);
2276
2276
  }
2277
2277
  return hash.digest("hex");
@@ -2280,8 +2280,8 @@ function inside(parent, child) {
2280
2280
  const step = relative3(parent, child);
2281
2281
  return step === "" || !isAbsolute3(step) && step !== ".." && !step.startsWith(`..${sep3}`);
2282
2282
  }
2283
- async function noSymlink(path10) {
2284
- let current = resolve3(path10);
2283
+ async function noSymlink(path12) {
2284
+ let current = resolve3(path12);
2285
2285
  while (true) {
2286
2286
  const stat2 = await lstat2(current).catch((error) => {
2287
2287
  if (error.code === "ENOENT") {
@@ -2292,7 +2292,7 @@ async function noSymlink(path10) {
2292
2292
  if (stat2?.isSymbolicLink()) {
2293
2293
  const systemAlias = process.platform === "darwin" && (current === "/var" && await realpath2(current) === "/private/var" || current === "/tmp" && await realpath2(current) === "/private/tmp");
2294
2294
  if (!systemAlias) {
2295
- throw new Error(`Asset target path must not traverse a symbolic link: ${path10}`);
2295
+ throw new Error(`Asset target path must not traverse a symbolic link: ${path12}`);
2296
2296
  }
2297
2297
  }
2298
2298
  const parent = dirname2(current);
@@ -2302,12 +2302,12 @@ async function noSymlink(path10) {
2302
2302
  current = parent;
2303
2303
  }
2304
2304
  }
2305
- async function canonicalOutput(path10) {
2306
- let ancestor = path10;
2305
+ async function canonicalOutput(path12) {
2306
+ let ancestor = path12;
2307
2307
  while (true) {
2308
2308
  try {
2309
2309
  const canonical = await realpath2(ancestor);
2310
- return resolve3(canonical, relative3(ancestor, path10));
2310
+ return resolve3(canonical, relative3(ancestor, path12));
2311
2311
  } catch (error) {
2312
2312
  if (error.code !== "ENOENT") {
2313
2313
  throw error;
@@ -2459,18 +2459,18 @@ async function assertAssetPackTargetArtifact(report, artifactRoot, namespacePath
2459
2459
  while (pending.length) {
2460
2460
  const directory = pending.pop();
2461
2461
  for (const entry of await readdir(directory, { withFileTypes: true })) {
2462
- const path10 = join3(directory, entry.name);
2462
+ const path12 = join3(directory, entry.name);
2463
2463
  if (++count > 1e5 || entry.isSymbolicLink()) {
2464
2464
  throw new Error("Asset artifact inventory exceeds its limit or contains a symbolic link");
2465
2465
  }
2466
2466
  if (entry.isDirectory()) {
2467
- pending.push(path10);
2467
+ pending.push(path12);
2468
2468
  } else if (entry.isFile()) {
2469
- if (inside(namespace, path10)) {
2470
- namespaceBytes += (await lstat2(path10)).size;
2469
+ if (inside(namespace, path12)) {
2470
+ namespaceBytes += (await lstat2(path12)).size;
2471
2471
  }
2472
- if (remoteHashes.has(await fileDigest(path10))) {
2473
- throw new Error(`Remote-only asset object is present in the packaged artifact: ${relative3(root, path10)}`);
2472
+ if (remoteHashes.has(await fileDigest(path12))) {
2473
+ throw new Error(`Remote-only asset object is present in the packaged artifact: ${relative3(root, path12)}`);
2474
2474
  }
2475
2475
  } else {
2476
2476
  throw new Error("Asset artifact contains a non-regular entry");
@@ -2512,14 +2512,14 @@ function expectedNamespace(artifact, namespace) {
2512
2512
  }
2513
2513
  }
2514
2514
  async function assertAssetPackNativeArtifact(report, artifact) {
2515
- const path10 = resolve4(artifact);
2516
- const stat2 = await lstat3(path10);
2515
+ const path12 = resolve4(artifact);
2516
+ const stat2 = await lstat3(path12);
2517
2517
  if (stat2.isSymbolicLink()) {
2518
2518
  throw new Error("Native asset artifact must not be a symbolic link");
2519
2519
  }
2520
2520
  if (stat2.isDirectory()) {
2521
2521
  const candidates = [];
2522
- const pending = [path10];
2522
+ const pending = [path12];
2523
2523
  let entries = 0;
2524
2524
  while (pending.length) {
2525
2525
  const directory = pending.pop();
@@ -2531,17 +2531,17 @@ async function assertAssetPackNativeArtifact(report, artifact) {
2531
2531
  if (entry.isDirectory()) {
2532
2532
  pending.push(file);
2533
2533
  } else if (entry.name === assetPackTargetManifestName && basename(directory) === assetPackTargetNamespace) {
2534
- candidates.push(relative4(path10, directory));
2534
+ candidates.push(relative4(path12, directory));
2535
2535
  }
2536
2536
  }
2537
2537
  }
2538
2538
  if (candidates.length !== 1) {
2539
2539
  throw new Error("Native asset artifact requires exactly one packaged pack namespace");
2540
2540
  }
2541
- if (!expectedNamespace(path10, candidates[0].replaceAll("\\", "/") + "/")) {
2541
+ if (!expectedNamespace(path12, candidates[0].replaceAll("\\", "/") + "/")) {
2542
2542
  throw new Error("Native asset namespace is outside the platform web bundle");
2543
2543
  }
2544
- await assertAssetPackTargetArtifact(report, path10, candidates[0]);
2544
+ await assertAssetPackTargetArtifact(report, path12, candidates[0]);
2545
2545
  return;
2546
2546
  }
2547
2547
  if (!stat2.isFile() || stat2.size > 2 * 1024 ** 3) {
@@ -2552,7 +2552,7 @@ async function assertAssetPackNativeArtifact(report, artifact) {
2552
2552
  const files = /* @__PURE__ */ new Map();
2553
2553
  let expanded = 0;
2554
2554
  let entryCount = 0;
2555
- const archive = await openPromise(path10, {
2555
+ const archive = await openPromise(path12, {
2556
2556
  lazyEntries: true,
2557
2557
  strictFileNames: true,
2558
2558
  validateEntrySizes: true
@@ -2600,7 +2600,7 @@ async function assertAssetPackNativeArtifact(report, artifact) {
2600
2600
  throw new Error("Native asset package requires exactly one packaged pack namespace");
2601
2601
  }
2602
2602
  const namespace = namespaces[0];
2603
- if (!expectedNamespace(path10, namespace)) {
2603
+ if (!expectedNamespace(path12, namespace)) {
2604
2604
  throw new Error("Native asset namespace is outside the platform web bundle");
2605
2605
  }
2606
2606
  const namespaceBytes = [...files].filter(([name]) => name.startsWith(namespace)).reduce((sum, [, file]) => sum + file.bytes, 0);
@@ -2942,8 +2942,8 @@ function resolveSecureGamePath(gameRoot, configuredPath) {
2942
2942
  }
2943
2943
  return real;
2944
2944
  }
2945
- function toGameRelativePath(gameRoot, path10) {
2946
- return relative5(realpathSync3(gameRoot), realpathSync3(path10)).split("\\").join("/");
2945
+ function toGameRelativePath(gameRoot, path12) {
2946
+ return relative5(realpathSync3(gameRoot), realpathSync3(path12)).split("\\").join("/");
2947
2947
  }
2948
2948
  function readAppIconConfig(input, label) {
2949
2949
  assertRecord(input, label);
@@ -2964,9 +2964,9 @@ function readVariants(input, label) {
2964
2964
  assertRecord(input, label);
2965
2965
  const variants = {};
2966
2966
  for (const name of variantNames) {
2967
- const path10 = readOptionalString(input[name], `${label}.${name}`);
2968
- if (path10 !== void 0) {
2969
- variants[name] = path10;
2967
+ const path12 = readOptionalString(input[name], `${label}.${name}`);
2968
+ if (path12 !== void 0) {
2969
+ variants[name] = path12;
2970
2970
  }
2971
2971
  }
2972
2972
  for (const key of Object.keys(input)) {
@@ -3008,38 +3008,38 @@ import sharp from "sharp";
3008
3008
  var pngSignature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
3009
3009
  var maximumDimension = 8192;
3010
3010
  var maximumPixels = maximumDimension * maximumDimension;
3011
- async function validateBrandImage(path10, options) {
3012
- const bytes = readFileSync4(path10);
3011
+ async function validateBrandImage(path12, options) {
3012
+ const bytes = readFileSync4(path12);
3013
3013
  const format = sniffFormat(bytes);
3014
3014
  if (format === "png" && hasPngChunk(bytes, "acTL")) {
3015
- throw new Error(`Animated or multi-page brand images are not supported: ${path10}`);
3015
+ throw new Error(`Animated or multi-page brand images are not supported: ${path12}`);
3016
3016
  }
3017
3017
  const warnings = format === "svg" ? validateSvg(bytes, options.strict) : [];
3018
3018
  let metadata;
3019
3019
  try {
3020
3020
  metadata = await sharp(bytes, { animated: true, limitInputPixels: maximumPixels }).metadata();
3021
3021
  } catch (error) {
3022
- throw new Error(`Invalid brand image ${path10}: ${formatError2(error)}`);
3022
+ throw new Error(`Invalid brand image ${path12}: ${formatError2(error)}`);
3023
3023
  }
3024
3024
  const width = metadata.width;
3025
3025
  const height = metadata.height;
3026
3026
  if (width === void 0 || height === void 0 || width < 1 || height < 1) {
3027
- throw new Error(`Brand image has no usable dimensions: ${path10}`);
3027
+ throw new Error(`Brand image has no usable dimensions: ${path12}`);
3028
3028
  }
3029
3029
  if (width !== height) {
3030
- throw new Error(`Brand image must be square, got ${width}x${height}: ${path10}`);
3030
+ throw new Error(`Brand image must be square, got ${width}x${height}: ${path12}`);
3031
3031
  }
3032
3032
  if (width > maximumDimension || height > maximumDimension) {
3033
- throw new Error(`Brand image exceeds ${maximumDimension}x${maximumDimension}: ${path10}`);
3033
+ throw new Error(`Brand image exceeds ${maximumDimension}x${maximumDimension}: ${path12}`);
3034
3034
  }
3035
3035
  if ((metadata.pages ?? 1) > 1) {
3036
- throw new Error(`Animated or multi-page brand images are not supported: ${path10}`);
3036
+ throw new Error(`Animated or multi-page brand images are not supported: ${path12}`);
3037
3037
  }
3038
3038
  if (format === "png" && options.minimumSize !== void 0 && width < options.minimumSize) {
3039
- throw new Error(`PNG brand image must be at least ${options.minimumSize}x${options.minimumSize}; got ${width}x${height}: ${path10}`);
3039
+ throw new Error(`PNG brand image must be at least ${options.minimumSize}x${options.minimumSize}; got ${width}x${height}: ${path12}`);
3040
3040
  }
3041
3041
  return {
3042
- path: path10,
3042
+ path: path12,
3043
3043
  bytes,
3044
3044
  format,
3045
3045
  width,
@@ -3464,28 +3464,28 @@ async function verifyGeneratedTargetIcons(result) {
3464
3464
  }
3465
3465
  assertManifestOutputsMatchProfile(result);
3466
3466
  for (const output of result.manifest.outputs) {
3467
- const path10 = resolve6(result.outputDir, output.path.slice("icons/".length));
3468
- const bytes = readFileSync5(path10);
3467
+ const path12 = resolve6(result.outputDir, output.path.slice("icons/".length));
3468
+ const bytes = readFileSync5(path12);
3469
3469
  if (sha256(bytes) !== output.sha256) {
3470
- throw new Error(`Stale generated icon output: ${path10}`);
3470
+ throw new Error(`Stale generated icon output: ${path12}`);
3471
3471
  }
3472
3472
  let metadata;
3473
3473
  try {
3474
3474
  metadata = await sharp2(bytes).metadata();
3475
3475
  } catch {
3476
- throw new Error(`Generated icon is not a decodable PNG: ${path10}`);
3476
+ throw new Error(`Generated icon is not a decodable PNG: ${path12}`);
3477
3477
  }
3478
3478
  if (metadata.format !== "png" || metadata.width !== output.width || metadata.height !== output.height || (metadata.pages ?? 1) !== 1) {
3479
- throw new Error(`Generated icon must be a ${output.width}x${output.height} single-page PNG: ${path10}`);
3479
+ throw new Error(`Generated icon must be a ${output.width}x${output.height} single-page PNG: ${path12}`);
3480
3480
  }
3481
3481
  if (await pixelSha256(bytes) !== output.pixelSha256) {
3482
- throw new Error(`Generated icon pixel digest mismatch: ${path10}`);
3482
+ throw new Error(`Generated icon pixel digest mismatch: ${path12}`);
3483
3483
  }
3484
3484
  if (output.opaque && !await isPngOpaque(bytes)) {
3485
- throw new Error(`Generated icon expected to be opaque: ${path10}`);
3485
+ throw new Error(`Generated icon expected to be opaque: ${path12}`);
3486
3486
  }
3487
3487
  if (result.profile.id === "devvit" && bytes.byteLength > devvitIconMaximumBytes) {
3488
- throw new Error(`Devvit marketing icon must be at most ${devvitIconMaximumBytes} bytes: ${path10}`);
3488
+ throw new Error(`Devvit marketing icon must be at most ${devvitIconMaximumBytes} bytes: ${path12}`);
3489
3489
  }
3490
3490
  }
3491
3491
  }
@@ -3497,13 +3497,13 @@ async function resolveOutputImage(input) {
3497
3497
  }
3498
3498
  async function loadVariantSources(gameRoot, appIcon, strict) {
3499
3499
  const entries = await Promise.all(brandImageVariants.map(async (variant) => {
3500
- const path10 = appIcon.variants?.[variant];
3501
- if (path10 === void 0) {
3500
+ const path12 = appIcon.variants?.[variant];
3501
+ if (path12 === void 0) {
3502
3502
  return void 0;
3503
3503
  }
3504
3504
  return [
3505
3505
  variant,
3506
- await validateBrandImage(resolveSecureGamePath(gameRoot, path10), { strict })
3506
+ await validateBrandImage(resolveSecureGamePath(gameRoot, path12), { strict })
3507
3507
  ];
3508
3508
  }));
3509
3509
  return Object.fromEntries(entries.filter((entry) => entry !== void 0));
@@ -3548,14 +3548,14 @@ function assertManifestOutputsMatchProfile(result) {
3548
3548
  throw new Error(`Generated icon output count does not match ${result.profile.id} profile.`);
3549
3549
  }
3550
3550
  for (const expected of expectedOutputs) {
3551
- const path10 = `icons/${expected.file}`;
3552
- const matches = result.manifest.outputs.filter((output2) => output2.path === path10);
3551
+ const path12 = `icons/${expected.file}`;
3552
+ const matches = result.manifest.outputs.filter((output2) => output2.path === path12);
3553
3553
  if (matches.length !== 1) {
3554
- throw new Error(`Generated icon manifest must contain exactly one ${path10} output.`);
3554
+ throw new Error(`Generated icon manifest must contain exactly one ${path12} output.`);
3555
3555
  }
3556
3556
  const output = matches[0];
3557
3557
  if (output === void 0 || output.target !== result.target || output.purpose !== expected.purpose || output.width !== expected.width || output.height !== expected.height || output.format !== "png" || output.opaque !== expected.opaque || !/^[0-9a-f]{64}$/u.test(output.sha256) || !/^[0-9a-f]{64}$/u.test(output.pixelSha256)) {
3558
- throw new Error(`Generated icon manifest output does not match profile: ${path10}`);
3558
+ throw new Error(`Generated icon manifest output does not match profile: ${path12}`);
3559
3559
  }
3560
3560
  }
3561
3561
  }
@@ -3833,8 +3833,8 @@ function indexHtmlLinksExistingManifest(indexFile, manifests) {
3833
3833
  }
3834
3834
  function rootManifestNameFromHref(href) {
3835
3835
  const pathEnd = href.search(/[?#]/u);
3836
- const path10 = (pathEnd < 0 ? href : href.slice(0, pathEnd)).trim();
3837
- const rootRelativePath = path10.startsWith("./") ? path10.slice(2) : path10.startsWith("/") ? path10.slice(1) : path10;
3836
+ const path12 = (pathEnd < 0 ? href : href.slice(0, pathEnd)).trim();
3837
+ const rootRelativePath = path12.startsWith("./") ? path12.slice(2) : path12.startsWith("/") ? path12.slice(1) : path12;
3838
3838
  return rootRelativePath.length > 0 && !rootRelativePath.includes("/") && !rootRelativePath.includes("\\") && installableManifestNames.has(rootRelativePath.toLowerCase()) ? rootRelativePath : void 0;
3839
3839
  }
3840
3840
  function readHtmlAttribute(tag, expectedName) {
@@ -4196,25 +4196,25 @@ function assertExistingWebOutputsAreGeneratedArtifacts(outputs) {
4196
4196
  }
4197
4197
  }
4198
4198
  function isPathWithin(root, candidate) {
4199
- const path10 = relative7(root, candidate);
4200
- return path10 === "" || path10 !== ".." && !path10.startsWith(`..${sep4}`) && !isAbsolute5(path10);
4199
+ const path12 = relative7(root, candidate);
4200
+ return path12 === "" || path12 !== ".." && !path12.startsWith(`..${sep4}`) && !isAbsolute5(path12);
4201
4201
  }
4202
4202
  function pathsOverlap(first, second) {
4203
4203
  return isPathWithin(first, second) || isPathWithin(second, first);
4204
4204
  }
4205
- function canonicalizeWebArtifactOutput(path10) {
4206
- const status = lstatIfPresent(path10);
4205
+ function canonicalizeWebArtifactOutput(path12) {
4206
+ const status = lstatIfPresent(path12);
4207
4207
  if (status?.isSymbolicLink() === true) {
4208
- throw new Error(`Web artifact output must not be a symbolic link: ${path10}`);
4208
+ throw new Error(`Web artifact output must not be a symbolic link: ${path12}`);
4209
4209
  }
4210
4210
  if (status !== void 0 && !status.isDirectory()) {
4211
- throw new Error(`Web artifact output must be a directory when it already exists: ${path10}`);
4211
+ throw new Error(`Web artifact output must be a directory when it already exists: ${path12}`);
4212
4212
  }
4213
- return canonicalizeThroughExistingAncestor(path10);
4213
+ return canonicalizeThroughExistingAncestor(path12);
4214
4214
  }
4215
- function lstatIfPresent(path10) {
4215
+ function lstatIfPresent(path12) {
4216
4216
  try {
4217
- return lstatSync3(path10);
4217
+ return lstatSync3(path12);
4218
4218
  } catch (error) {
4219
4219
  if (error.code === "ENOENT") {
4220
4220
  return void 0;
@@ -4222,14 +4222,14 @@ function lstatIfPresent(path10) {
4222
4222
  throw error;
4223
4223
  }
4224
4224
  }
4225
- function canonicalizeThroughExistingAncestor(path10) {
4226
- const normalizedPath = resolve7(path10);
4225
+ function canonicalizeThroughExistingAncestor(path12) {
4226
+ const normalizedPath = resolve7(path12);
4227
4227
  const suffix = [];
4228
4228
  let existingAncestor = normalizedPath;
4229
4229
  while (!existsSync5(existingAncestor)) {
4230
4230
  const parent = dirname4(existingAncestor);
4231
4231
  if (parent === existingAncestor) {
4232
- throw new Error(`Cannot resolve an existing ancestor for web output: ${path10}`);
4232
+ throw new Error(`Cannot resolve an existing ancestor for web output: ${path12}`);
4233
4233
  }
4234
4234
  suffix.unshift(basename2(existingAncestor));
4235
4235
  existingAncestor = parent;
@@ -4285,18 +4285,18 @@ function stageWrapperIcon(result, wrapperApp) {
4285
4285
  }
4286
4286
  async function stageNativeIconResources(result, shellApp) {
4287
4287
  const snapshots = /* @__PURE__ */ new Map();
4288
- const write = (path10, contents) => {
4289
- if (!snapshots.has(path10)) {
4290
- snapshots.set(path10, existsSync6(path10) ? readFileSync7(path10) : void 0);
4288
+ const write = (path12, contents) => {
4289
+ if (!snapshots.has(path12)) {
4290
+ snapshots.set(path12, existsSync6(path12) ? readFileSync7(path12) : void 0);
4291
4291
  }
4292
- mkdirSync4(dirname5(path10), { recursive: true });
4293
- writeFileSync4(path10, contents);
4292
+ mkdirSync4(dirname5(path12), { recursive: true });
4293
+ writeFileSync4(path12, contents);
4294
4294
  };
4295
- const remove = (path10) => {
4296
- if (!snapshots.has(path10)) {
4297
- snapshots.set(path10, existsSync6(path10) ? readFileSync7(path10) : void 0);
4295
+ const remove = (path12) => {
4296
+ if (!snapshots.has(path12)) {
4297
+ snapshots.set(path12, existsSync6(path12) ? readFileSync7(path12) : void 0);
4298
4298
  }
4299
- rmSync4(path10, { force: true });
4299
+ rmSync4(path12, { force: true });
4300
4300
  };
4301
4301
  if (result.profile.id === "android-adaptive") {
4302
4302
  await stageAndroid(result, shellApp, write, remove);
@@ -4304,11 +4304,11 @@ async function stageNativeIconResources(result, shellApp) {
4304
4304
  stageIos(result, shellApp, write);
4305
4305
  }
4306
4306
  return () => {
4307
- for (const [path10, contents] of [...snapshots.entries()].reverse()) {
4307
+ for (const [path12, contents] of [...snapshots.entries()].reverse()) {
4308
4308
  if (contents === void 0) {
4309
- rmSync4(path10, { force: true });
4309
+ rmSync4(path12, { force: true });
4310
4310
  } else {
4311
- writeFileSync4(path10, contents);
4311
+ writeFileSync4(path12, contents);
4312
4312
  }
4313
4313
  }
4314
4314
  };
@@ -4381,7 +4381,7 @@ function stageWebManifest(result, gameDist, manifestSourceDirectory) {
4381
4381
  stagedPath,
4382
4382
  publicWebManifestPath,
4383
4383
  publicJsonManifestPath
4384
- ].find((path10) => existsSync6(path10));
4384
+ ].find((path12) => existsSync6(path12));
4385
4385
  const manifest = manifestPath !== void 0 ? readWebManifest(manifestPath) : {
4386
4386
  name: basename3(result.gameRoot),
4387
4387
  short_name: basename3(result.gameRoot),
@@ -4404,10 +4404,10 @@ function stageWebManifest(result, gameDist, manifestSourceDirectory) {
4404
4404
  }
4405
4405
  ensureInstallableWebManifestLink(gameDist);
4406
4406
  }
4407
- function readWebManifest(path10) {
4408
- const parsed = JSON.parse(readFileSync7(path10, "utf8"));
4407
+ function readWebManifest(path12) {
4408
+ const parsed = JSON.parse(readFileSync7(path12, "utf8"));
4409
4409
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
4410
- throw new Error(`Web app manifest must contain a JSON object: ${path10}`);
4410
+ throw new Error(`Web app manifest must contain a JSON object: ${path12}`);
4411
4411
  }
4412
4412
  return parsed;
4413
4413
  }
@@ -4426,8 +4426,8 @@ function androidAdaptiveXml(hasMonochrome) {
4426
4426
  </adaptive-icon>
4427
4427
  `;
4428
4428
  }
4429
- async function resizePng(path10, size) {
4430
- return sharp3(path10).resize(size, size, { kernel: sharp3.kernel.lanczos3 }).png({ compressionLevel: 9, effort: 10 }).toBuffer();
4429
+ async function resizePng(path12, size) {
4430
+ return sharp3(path12).resize(size, size, { kernel: sharp3.kernel.lanczos3 }).png({ compressionLevel: 9, effort: 10 }).toBuffer();
4431
4431
  }
4432
4432
  function cacheOutputPath(result, output) {
4433
4433
  return resolve8(result.outputDir, basename3(output.path));
@@ -4566,10 +4566,10 @@ function instantOrNaN(at) {
4566
4566
  function isNotificationTimestamp(at) {
4567
4567
  return !Number.isNaN(instantOrNaN(at));
4568
4568
  }
4569
- function notificationTimeFieldIssue(value, fields, path10 = "") {
4569
+ function notificationTimeFieldIssue(value, fields, path12 = "") {
4570
4570
  if (Array.isArray(value)) {
4571
4571
  for (const [index, item] of value.entries()) {
4572
- const issue = notificationTimeFieldIssue(item, fields, `${path10}[${index}]`);
4572
+ const issue = notificationTimeFieldIssue(item, fields, `${path12}[${index}]`);
4573
4573
  if (issue !== void 0) {
4574
4574
  return issue;
4575
4575
  }
@@ -4584,7 +4584,7 @@ function notificationTimeFieldIssue(value, fields, path10 = "") {
4584
4584
  continue;
4585
4585
  }
4586
4586
  const field = value[key];
4587
- const at = path10 === "" ? key : `${path10}.${key}`;
4587
+ const at = path12 === "" ? key : `${path12}.${key}`;
4588
4588
  if (typeof format !== "string") {
4589
4589
  const issue = notificationTimeFieldIssue(field, format, at);
4590
4590
  if (issue !== void 0) {
@@ -8010,7 +8010,8 @@ var targetIntegrations = [
8010
8010
  "presentation",
8011
8011
  "sharing",
8012
8012
  "inboundShare",
8013
- "notifications"
8013
+ "notifications",
8014
+ "reviews"
8014
8015
  ];
8015
8016
  var integrationUpperBoundsByRuntime = {
8016
8017
  "crazygames-web": {
@@ -8018,42 +8019,48 @@ var integrationUpperBoundsByRuntime = {
8018
8019
  presentation: "available",
8019
8020
  sharing: "unsupported",
8020
8021
  inboundShare: "unsupported",
8021
- notifications: "unsupported"
8022
+ notifications: "unsupported",
8023
+ reviews: "disabled"
8022
8024
  },
8023
8025
  "oneplay-web": {
8024
8026
  identityUpgrade: "unsupported",
8025
8027
  presentation: "available",
8026
8028
  sharing: "unsupported",
8027
8029
  inboundShare: "unsupported",
8028
- notifications: "unsupported"
8030
+ notifications: "unsupported",
8031
+ reviews: "available"
8029
8032
  },
8030
8033
  "tiktok-minigame": {
8031
8034
  identityUpgrade: "unsupported",
8032
8035
  presentation: "available",
8033
8036
  sharing: "available",
8034
8037
  inboundShare: "unsupported",
8035
- notifications: "unsupported"
8038
+ notifications: "unsupported",
8039
+ reviews: "disabled"
8036
8040
  },
8037
8041
  "verse8-web": {
8038
8042
  identityUpgrade: "unsupported",
8039
8043
  presentation: "available",
8040
8044
  sharing: "unsupported",
8041
8045
  inboundShare: "unsupported",
8042
- notifications: "unsupported"
8046
+ notifications: "unsupported",
8047
+ reviews: "available"
8043
8048
  },
8044
8049
  "web-preview": {
8045
8050
  identityUpgrade: "disabled",
8046
8051
  presentation: "available",
8047
8052
  sharing: "available",
8048
8053
  inboundShare: "available",
8049
- notifications: "unsupported"
8054
+ notifications: "unsupported",
8055
+ reviews: "disabled"
8050
8056
  },
8051
8057
  "wechat-minigame": {
8052
8058
  identityUpgrade: "unsupported",
8053
8059
  presentation: "available",
8054
8060
  sharing: "available",
8055
8061
  inboundShare: "unsupported",
8056
- notifications: "unsupported"
8062
+ notifications: "unsupported",
8063
+ reviews: "disabled"
8057
8064
  }
8058
8065
  };
8059
8066
  var integrationAvailabilityStateValues = [
@@ -8077,6 +8084,7 @@ var defaultTargetIntegrationConfig = {
8077
8084
  sharing: "disabled",
8078
8085
  inboundShare: "disabled",
8079
8086
  notifications: "unsupported",
8087
+ reviews: "disabled",
8080
8088
  presentationMode: "fullscreen"
8081
8089
  };
8082
8090
  var integrationAvailabilityStateSet = new Set(integrationAvailabilityStates);
@@ -8091,6 +8099,7 @@ function normalizeTargetIntegrationConfig(config2) {
8091
8099
  sharing: normalizeIntegrationAvailabilityState(config2.sharing, defaultTargetIntegrationConfig.sharing),
8092
8100
  inboundShare: normalizeIntegrationAvailabilityState(config2.inboundShare, defaultTargetIntegrationConfig.inboundShare),
8093
8101
  notifications: normalizeIntegrationAvailabilityState(config2.notifications, defaultTargetIntegrationConfig.notifications),
8102
+ reviews: normalizeIntegrationAvailabilityState(config2.reviews, "disabled"),
8094
8103
  presentationMode: normalizePresentationMode(config2.presentationMode)
8095
8104
  };
8096
8105
  }
@@ -8418,10 +8427,10 @@ function assertCrazyGamesArtifact(root) {
8418
8427
  let bytes = 0;
8419
8428
  function visit(directory) {
8420
8429
  for (const name of readdirSync2(directory)) {
8421
- const path10 = join8(directory, name);
8422
- const stat2 = lstatSync5(path10);
8430
+ const path12 = join8(directory, name);
8431
+ const stat2 = lstatSync5(path12);
8423
8432
  if (stat2.isDirectory()) {
8424
- visit(path10);
8433
+ visit(path12);
8425
8434
  } else if (stat2.isFile()) {
8426
8435
  files += 1;
8427
8436
  bytes += stat2.size;
@@ -8436,8 +8445,8 @@ function assertCrazyGamesArtifact(root) {
8436
8445
  }
8437
8446
  const index = readFileSync8(join8(root, "index.html"), "utf8");
8438
8447
  for (const match of index.matchAll(/\b(?:src|href)\s*=\s*(?:["']([^"']+)["']|([^\s"'`=<>]+))/giu)) {
8439
- const path10 = match[1] ?? match[2];
8440
- if (path10?.startsWith("/") || /^https?:/iu.test(path10 ?? "")) {
8448
+ const path12 = match[1] ?? match[2];
8449
+ if (path12?.startsWith("/") || /^https?:/iu.test(path12 ?? "")) {
8441
8450
  throw new Error("CrazyGames index assets must use relative paths.");
8442
8451
  }
8443
8452
  }
@@ -8449,9 +8458,9 @@ import { existsSync as existsSync7, readFileSync as readFileSync9, writeFileSync
8449
8458
  import { join as join9 } from "node:path";
8450
8459
  var onePlayFrameAncestors = "https://*.onestore.co.kr https://*.onestore.net";
8451
8460
  function writeOnePlayHostingHeaders(root) {
8452
- const path10 = join9(root, "_headers");
8453
- if (!existsSync7(path10)) {
8454
- writeFileSync5(path10, `/*
8461
+ const path12 = join9(root, "_headers");
8462
+ if (!existsSync7(path12)) {
8463
+ writeFileSync5(path12, `/*
8455
8464
  Content-Security-Policy: frame-ancestors ${onePlayFrameAncestors}
8456
8465
  `);
8457
8466
  }
@@ -8946,8 +8955,8 @@ function readConfiguredPath(value) {
8946
8955
  import { readFileSync as readFileSync10 } from "node:fs";
8947
8956
  import { resolve as resolve9 } from "node:path";
8948
8957
  import { pathToFileURL } from "node:url";
8949
- function readJsonFile(path10) {
8950
- return JSON.parse(readFileSync10(resolve9(path10), "utf8"));
8958
+ function readJsonFile(path12) {
8959
+ return JSON.parse(readFileSync10(resolve9(path12), "utf8"));
8951
8960
  }
8952
8961
  function isCliEntrypoint(metaUrl) {
8953
8962
  const scriptPath = process.argv[1];
@@ -9101,20 +9110,20 @@ function listMiniGameArtifactFiles(artifactRoot) {
9101
9110
  throw new Error("Mini-game artifact traversal lost its current directory.");
9102
9111
  }
9103
9112
  for (const entry of readdirSync3(directory, { withFileTypes: true })) {
9104
- const path10 = resolve10(directory, entry.name);
9105
- const stat2 = lstatSync6(path10);
9113
+ const path12 = resolve10(directory, entry.name);
9114
+ const stat2 = lstatSync6(path12);
9106
9115
  if (stat2.isSymbolicLink()) {
9107
- throw new Error(`Mini-game artifacts must not contain symbolic links: ${path10}`);
9116
+ throw new Error(`Mini-game artifacts must not contain symbolic links: ${path12}`);
9108
9117
  }
9109
- const artifactPath = toArtifactRelativePath(root, path10);
9118
+ const artifactPath = toArtifactRelativePath(root, path12);
9110
9119
  assertMiniGameArtifactPathAllowed(artifactPath);
9111
9120
  artifactPaths.push(artifactPath);
9112
9121
  if (stat2.isDirectory()) {
9113
- pending.push(path10);
9122
+ pending.push(path12);
9114
9123
  continue;
9115
9124
  }
9116
9125
  if (!stat2.isFile()) {
9117
- throw new Error(`Mini-game artifacts must contain regular files only: ${path10}`);
9126
+ throw new Error(`Mini-game artifacts must contain regular files only: ${path12}`);
9118
9127
  }
9119
9128
  files.push({ path: artifactPath, bytes: stat2.size });
9120
9129
  }
@@ -9124,22 +9133,22 @@ function listMiniGameArtifactFiles(artifactRoot) {
9124
9133
  return left.path < right.path ? -1 : left.path > right.path ? 1 : 0;
9125
9134
  });
9126
9135
  }
9127
- function assertMiniGameArtifactRelativePath(path10, label) {
9128
- const segments = path10.split("/");
9129
- if (path10.length === 0 || path10.includes("\\") || path10.startsWith("/") || /^[A-Za-z]:/u.test(path10) || isAbsolute6(path10) || segments.some((segment) => segment.length === 0 || segment === "." || segment === "..") || segments.some((segment) => /[. ]$/u.test(segment) || /[<>:"|?*\u0000-\u001f]/u.test(segment) || /^(?:con|prn|aux|nul|clock\$|conin\$|conout\$|com[1-9\u00b9\u00b2\u00b3]|lpt[1-9\u00b9\u00b2\u00b3])(?:\..*)?$/iu.test(segment))) {
9130
- throw new Error(`${label} must be a safe artifact-relative path: ${path10}`);
9136
+ function assertMiniGameArtifactRelativePath(path12, label) {
9137
+ const segments = path12.split("/");
9138
+ if (path12.length === 0 || path12.includes("\\") || path12.startsWith("/") || /^[A-Za-z]:/u.test(path12) || isAbsolute6(path12) || segments.some((segment) => segment.length === 0 || segment === "." || segment === "..") || segments.some((segment) => /[. ]$/u.test(segment) || /[<>:"|?*\u0000-\u001f]/u.test(segment) || /^(?:con|prn|aux|nul|clock\$|conin\$|conout\$|com[1-9\u00b9\u00b2\u00b3]|lpt[1-9\u00b9\u00b2\u00b3])(?:\..*)?$/iu.test(segment))) {
9139
+ throw new Error(`${label} must be a safe artifact-relative path: ${path12}`);
9131
9140
  }
9132
9141
  }
9133
9142
  function assertMiniGameArtifactPathsPortable(paths) {
9134
9143
  const seen = /* @__PURE__ */ new Map();
9135
- for (const path10 of paths) {
9136
- assertMiniGameArtifactRelativePath(path10, "Mini-game artifact path");
9137
- const portableKey = miniGamePortablePathKey(path10);
9144
+ for (const path12 of paths) {
9145
+ assertMiniGameArtifactRelativePath(path12, "Mini-game artifact path");
9146
+ const portableKey = miniGamePortablePathKey(path12);
9138
9147
  const previous = seen.get(portableKey);
9139
9148
  if (previous !== void 0) {
9140
- throw new Error(`Mini-game artifact paths collide on portable filesystems: ${previous}, ${path10}.`);
9149
+ throw new Error(`Mini-game artifact paths collide on portable filesystems: ${previous}, ${path12}.`);
9141
9150
  }
9142
- seen.set(portableKey, path10);
9151
+ seen.set(portableKey, path12);
9143
9152
  }
9144
9153
  }
9145
9154
  function readMiniGameSubpackages(gameConfig) {
@@ -9176,40 +9185,40 @@ function assertSubpackageRoots(subpackages) {
9176
9185
  }
9177
9186
  }
9178
9187
  }
9179
- function resolveArtifactPath2(root, path10) {
9180
- const resolved = resolve10(root, path10);
9188
+ function resolveArtifactPath2(root, path12) {
9189
+ const resolved = resolve10(root, path12);
9181
9190
  const relativePath = relative8(root, resolved);
9182
9191
  if (relativePath.length === 0 || relativePath === ".." || relativePath.startsWith(`..${sep5}`) || isAbsolute6(relativePath)) {
9183
- throw new Error(`Mini-game path escapes its artifact root: ${path10}`);
9192
+ throw new Error(`Mini-game path escapes its artifact root: ${path12}`);
9184
9193
  }
9185
9194
  try {
9186
9195
  const stat2 = lstatSync6(resolved);
9187
9196
  if (stat2.isSymbolicLink()) {
9188
- throw new Error(`Mini-game path must not be a symbolic link: ${path10}`);
9197
+ throw new Error(`Mini-game path must not be a symbolic link: ${path12}`);
9189
9198
  }
9190
9199
  } catch (error) {
9191
- throw new Error(`Mini-game subpackage root does not exist: ${path10} (${formatError4(error)})`);
9200
+ throw new Error(`Mini-game subpackage root does not exist: ${path12} (${formatError4(error)})`);
9192
9201
  }
9193
9202
  return resolved;
9194
9203
  }
9195
- function toArtifactRelativePath(root, path10) {
9196
- const artifactPath = relative8(root, path10).replaceAll("\\", "/");
9204
+ function toArtifactRelativePath(root, path12) {
9205
+ const artifactPath = relative8(root, path12).replaceAll("\\", "/");
9197
9206
  assertMiniGameArtifactRelativePath(artifactPath, "Mini-game artifact file");
9198
9207
  return artifactPath;
9199
9208
  }
9200
- function isInsideRoot(path10, root) {
9201
- return path10 === root || path10.startsWith(`${root}/`);
9209
+ function isInsideRoot(path12, root) {
9210
+ return path12 === root || path12.startsWith(`${root}/`);
9202
9211
  }
9203
- function miniGamePortablePathKey(path10) {
9204
- return path10.normalize("NFC").toLowerCase();
9212
+ function miniGamePortablePathKey(path12) {
9213
+ return path12.normalize("NFC").toLowerCase();
9205
9214
  }
9206
9215
  function assertWithinBudget(actual, maximum, label) {
9207
9216
  if (actual > maximum) {
9208
9217
  throw new Error(`Mini-game ${label} exceeds its package budget: ${String(actual)} > ${String(maximum)} bytes.`);
9209
9218
  }
9210
9219
  }
9211
- function assertMiniGameArtifactPathAllowed(path10) {
9212
- const segments = path10.split("/");
9220
+ function assertMiniGameArtifactPathAllowed(path12) {
9221
+ const segments = path12.split("/");
9213
9222
  const fileName = segments.at(-1) ?? "";
9214
9223
  const normalizedFileName = fileName.toLowerCase();
9215
9224
  const normalizedSegments = segments.map((segment) => segment.toLowerCase());
@@ -9223,16 +9232,16 @@ function assertMiniGameArtifactPathAllowed(path10) {
9223
9232
  "tests"
9224
9233
  ]);
9225
9234
  if (normalizedSegments.some((segment) => forbiddenSegments.has(segment))) {
9226
- throw new Error(`Mini-game artifact contains a forbidden development path: ${path10}`);
9235
+ throw new Error(`Mini-game artifact contains a forbidden development path: ${path12}`);
9227
9236
  }
9228
9237
  if (normalizedFileName === ".ds_store" || normalizedFileName === ".env" || normalizedFileName.startsWith(".env.") || normalizedFileName === ".npmrc" || normalizedFileName === "credentials.json" || normalizedFileName === "project.private.config.json" || /^service-account.*\.json$/iu.test(fileName) || /\.(?:map|jsx|ts|tsx|mts|cts|pem|key|p12|pfx)$/iu.test(fileName)) {
9229
- throw new Error(`Mini-game artifact contains a forbidden development or credential file: ${path10}`);
9238
+ throw new Error(`Mini-game artifact contains a forbidden development or credential file: ${path12}`);
9230
9239
  }
9231
9240
  if (/\.(?:html?|css|svg)$/iu.test(fileName)) {
9232
- throw new Error(`Mini-game artifact contains a browser-only asset: ${path10}`);
9241
+ throw new Error(`Mini-game artifact contains a browser-only asset: ${path12}`);
9233
9242
  }
9234
9243
  if (/\.(?:aac|m4a|mp3|ogg|opus|wav|webm)$/iu.test(fileName)) {
9235
- throw new Error(`Mini-game Canvas MVP does not support audio assets yet: ${path10}`);
9244
+ throw new Error(`Mini-game Canvas MVP does not support audio assets yet: ${path12}`);
9236
9245
  }
9237
9246
  }
9238
9247
  function isRecord3(input) {
@@ -9667,6 +9676,11 @@ var assertPlatformTargetsConfig = /* @__PURE__ */ (() => {
9667
9676
  path: _path + ".notifications",
9668
9677
  expected: _ae0,
9669
9678
  value: input.notifications
9679
+ }, _errorFactory)) && (void 0 === input.reviews || "action-required" === input.reviews || "approval-required" === input.reviews || "available" === input.reviews || "configuration-required" === input.reviews || "disabled" === input.reviews || "temporarily-unavailable" === input.reviews || "unsupported" === input.reviews || _assertGuard_16._assertGuard(_exceptionable, {
9680
+ method: "typia.createAssert",
9681
+ path: _path + ".reviews",
9682
+ expected: _ae0,
9683
+ value: input.reviews
9670
9684
  }, _errorFactory)) && (void 0 === input.presentationMode || "fullscreen" === input.presentationMode || "inline-expanded" === input.presentationMode || _assertGuard_16._assertGuard(_exceptionable, {
9671
9685
  method: "typia.createAssert",
9672
9686
  path: _path + ".presentationMode",
@@ -9792,7 +9806,7 @@ var assertPlatformTargetsConfig = /* @__PURE__ */ (() => {
9792
9806
  return "packaged" === value || "remote" === value;
9793
9807
  });
9794
9808
  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);
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);
9809
+ 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.reviews || "action-required" === input.reviews || "approval-required" === input.reviews || "available" === input.reviews || "configuration-required" === input.reviews || "disabled" === input.reviews || "temporarily-unavailable" === input.reviews || "unsupported" === input.reviews) && (void 0 === input.presentationMode || "fullscreen" === input.presentationMode || "inline-expanded" === input.presentationMode);
9796
9810
  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));
9797
9811
  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);
9798
9812
  const _io9 = (input) => (void 0 === input.withBackButton || "boolean" === typeof input.withBackButton) && (void 0 === input.withHomeButton || "boolean" === typeof input.withHomeButton) && (void 0 === input.withTitle || "boolean" === typeof input.withTitle) && (void 0 === input.transparentBackground || "boolean" === typeof input.transparentBackground) && (void 0 === input.theme || "dark" === input.theme || "light" === input.theme);
@@ -9838,19 +9852,19 @@ var effectiveTargetConfigOutputDirEnv = "MPGD_EFFECTIVE_TARGET_CONFIG_OUTPUT_DIR
9838
9852
  var targetIntegrationConfigKeys = /* @__PURE__ */ new Set([...targetIntegrations, "presentationMode"]);
9839
9853
  var integrationAvailabilityStateSet2 = new Set(integrationAvailabilityStates);
9840
9854
  var presentationModeSet2 = new Set(presentationModes);
9841
- function platformTargetsFilePath(path10 = process.env[platformTargetsFileEnv] ?? "mpgd.targets.json") {
9842
- return resolve11(path10);
9855
+ function platformTargetsFilePath(path12 = process.env[platformTargetsFileEnv] ?? "mpgd.targets.json") {
9856
+ return resolve11(path12);
9843
9857
  }
9844
- function loadPlatformTargetsConfig(path10 = platformTargetsFilePath()) {
9845
- const resolvedPath = platformTargetsFilePath(path10);
9858
+ function loadPlatformTargetsConfig(path12 = platformTargetsFilePath()) {
9859
+ const resolvedPath = platformTargetsFilePath(path12);
9846
9860
  return {
9847
9861
  path: resolvedPath,
9848
9862
  baseDir: dirname6(resolvedPath),
9849
9863
  config: assertPlatformTargetsConfigShape(readJsonFile(resolvedPath))
9850
9864
  };
9851
9865
  }
9852
- function resolveFromPlatformTargetsBase(baseDir, path10) {
9853
- return isAbsolute7(path10) ? path10 : resolve11(baseDir, path10);
9866
+ function resolveFromPlatformTargetsBase(baseDir, path12) {
9867
+ return isAbsolute7(path12) ? path12 : resolve11(baseDir, path12);
9854
9868
  }
9855
9869
  function releaseManifestPath(baseDir = dirname6(platformTargetsFilePath())) {
9856
9870
  return resolveFromPlatformTargetsBase(baseDir, process.env[releaseManifestFileEnv] ?? "artifacts/release-manifest.json");
@@ -10169,6 +10183,7 @@ var assertTargetConfigMatrix = /* @__PURE__ */ (() => {
10169
10183
  const _av1 = /* @__PURE__ */ new Set(["app-store", "apps-in-toss", "crazygames", "devvit", "google-play", "microsoft-store", "oneplay", "tiktok-minigame", "verse8", "web", "web-preview", "wechat-minigame"]);
10170
10184
  const _ae1 = '("app-store" | "apps-in-toss" | "crazygames" | "devvit" | "google-play" | "microsoft-store" | "oneplay" | "tiktok-minigame" | "verse8" | "web" | "web-preview" | "wechat-minigame")';
10171
10185
  const _ae2 = '("action-required" | "approval-required" | "available" | "configuration-required" | "disabled" | "temporarily-unavailable" | "unsupported")';
10186
+ const _ae3 = '("action-required" | "approval-required" | "available" | "configuration-required" | "disabled" | "temporarily-unavailable" | "unsupported" | undefined)';
10172
10187
  const _ao0 = (input, _path, _exceptionable = true) => ("string" === typeof input.version || _assertGuard_17._assertGuard(_exceptionable, {
10173
10188
  method: "typia.createAssert",
10174
10189
  path: _path + ".version",
@@ -10226,6 +10241,11 @@ var assertTargetConfigMatrix = /* @__PURE__ */ (() => {
10226
10241
  path: _path + ".notifications",
10227
10242
  expected: _ae2,
10228
10243
  value: input.notifications
10244
+ }, _errorFactory)) && (void 0 === input.reviews || "action-required" === input.reviews || "approval-required" === input.reviews || "available" === input.reviews || "configuration-required" === input.reviews || "disabled" === input.reviews || "temporarily-unavailable" === input.reviews || "unsupported" === input.reviews || _assertGuard_17._assertGuard(_exceptionable, {
10245
+ method: "typia.createAssert",
10246
+ path: _path + ".reviews",
10247
+ expected: _ae3,
10248
+ value: input.reviews
10229
10249
  }, _errorFactory)) && ("fullscreen" === input.presentationMode || "inline-expanded" === input.presentationMode || _assertGuard_17._assertGuard(_exceptionable, {
10230
10250
  method: "typia.createAssert",
10231
10251
  path: _path + ".presentationMode",
@@ -10402,7 +10422,7 @@ var assertTargetConfigMatrix = /* @__PURE__ */ (() => {
10402
10422
  return true;
10403
10423
  return "object" === typeof value && null !== value && _io2(value);
10404
10424
  });
10405
- const _io10 = (input) => ("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) && ("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) && ("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) && ("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) && ("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) && ("fullscreen" === input.presentationMode || "inline-expanded" === input.presentationMode);
10425
+ const _io10 = (input) => ("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) && ("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) && ("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) && ("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) && ("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.reviews || "action-required" === input.reviews || "approval-required" === input.reviews || "available" === input.reviews || "configuration-required" === input.reviews || "disabled" === input.reviews || "temporarily-unavailable" === input.reviews || "unsupported" === input.reviews) && ("fullscreen" === input.presentationMode || "inline-expanded" === input.presentationMode);
10406
10426
  const _io2 = (input) => true === _iv0.has(input.runtime) && ("object" === typeof input.features && null !== input.features && _io3(input.features)) && ("object" === typeof input.capabilities && null !== input.capabilities && _io4(input.capabilities)) && ("object" === typeof input.localization && null !== input.localization && _io5(input.localization)) && ("object" === typeof input.monetization && null !== input.monetization && _io6(input.monetization)) && ("object" === typeof input.leaderboard && null !== input.leaderboard && _io7(input.leaderboard)) && ("object" === typeof input.release && null !== input.release && _io8(input.release)) && ("object" === typeof input.policy && null !== input.policy && _io9(input.policy)) && (void 0 === input.integrations || "object" === typeof input.integrations && null !== input.integrations && _io10(input.integrations));
10407
10427
  const _io3 = (input) => _ip0(input) && (void 0 === input.subscriptions || "boolean" === typeof input.subscriptions) && _ip1(input) && _ip2(input) && _ip3(input) && "boolean" === typeof input.leaderboard && (void 0 === input.nativeLeaderboard || "boolean" === typeof input.nativeLeaderboard) && (void 0 === input.remoteLeaderboard || "boolean" === typeof input.remoteLeaderboard) && _ip4(input);
10408
10428
  const _io4 = (input) => ("local" === input.storage || "native" === input.storage || "none" === input.storage) && _ip4(input);
@@ -10474,6 +10494,7 @@ var assertTargetConfigExtensions = /* @__PURE__ */ (() => {
10474
10494
  const _av1 = /* @__PURE__ */ new Set(["app-store", "apps-in-toss", "crazygames", "devvit", "google-play", "microsoft-store", "oneplay", "tiktok-minigame", "verse8", "web", "web-preview", "wechat-minigame"]);
10475
10495
  const _ae1 = '("app-store" | "apps-in-toss" | "crazygames" | "devvit" | "google-play" | "microsoft-store" | "oneplay" | "tiktok-minigame" | "verse8" | "web" | "web-preview" | "wechat-minigame")';
10476
10496
  const _ae2 = '("action-required" | "approval-required" | "available" | "configuration-required" | "disabled" | "temporarily-unavailable" | "unsupported")';
10497
+ const _ae3 = '("action-required" | "approval-required" | "available" | "configuration-required" | "disabled" | "temporarily-unavailable" | "unsupported" | undefined)';
10477
10498
  const _ao0 = (input, _path, _exceptionable = true) => (1 === input.schemaVersion || _assertGuard_17._assertGuard(_exceptionable, {
10478
10499
  method: "typia.createAssert",
10479
10500
  path: _path + ".schemaVersion",
@@ -10531,6 +10552,11 @@ var assertTargetConfigExtensions = /* @__PURE__ */ (() => {
10531
10552
  path: _path + ".notifications",
10532
10553
  expected: _ae2,
10533
10554
  value: input.notifications
10555
+ }, _errorFactory)) && (void 0 === input.reviews || "action-required" === input.reviews || "approval-required" === input.reviews || "available" === input.reviews || "configuration-required" === input.reviews || "disabled" === input.reviews || "temporarily-unavailable" === input.reviews || "unsupported" === input.reviews || _assertGuard_17._assertGuard(_exceptionable, {
10556
+ method: "typia.createAssert",
10557
+ path: _path + ".reviews",
10558
+ expected: _ae3,
10559
+ value: input.reviews
10534
10560
  }, _errorFactory)) && ("fullscreen" === input.presentationMode || "inline-expanded" === input.presentationMode || _assertGuard_17._assertGuard(_exceptionable, {
10535
10561
  method: "typia.createAssert",
10536
10562
  path: _path + ".presentationMode",
@@ -10707,7 +10733,7 @@ var assertTargetConfigExtensions = /* @__PURE__ */ (() => {
10707
10733
  return true;
10708
10734
  return "object" === typeof value && null !== value && _io2(value);
10709
10735
  });
10710
- const _io10 = (input) => ("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) && ("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) && ("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) && ("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) && ("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) && ("fullscreen" === input.presentationMode || "inline-expanded" === input.presentationMode);
10736
+ const _io10 = (input) => ("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) && ("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) && ("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) && ("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) && ("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.reviews || "action-required" === input.reviews || "approval-required" === input.reviews || "available" === input.reviews || "configuration-required" === input.reviews || "disabled" === input.reviews || "temporarily-unavailable" === input.reviews || "unsupported" === input.reviews) && ("fullscreen" === input.presentationMode || "inline-expanded" === input.presentationMode);
10711
10737
  const _io2 = (input) => true === _iv0.has(input.runtime) && ("object" === typeof input.features && null !== input.features && _io3(input.features)) && ("object" === typeof input.capabilities && null !== input.capabilities && _io4(input.capabilities)) && ("object" === typeof input.localization && null !== input.localization && _io5(input.localization)) && ("object" === typeof input.monetization && null !== input.monetization && _io6(input.monetization)) && ("object" === typeof input.leaderboard && null !== input.leaderboard && _io7(input.leaderboard)) && ("object" === typeof input.release && null !== input.release && _io8(input.release)) && ("object" === typeof input.policy && null !== input.policy && _io9(input.policy)) && (void 0 === input.integrations || "object" === typeof input.integrations && null !== input.integrations && _io10(input.integrations));
10712
10738
  const _io3 = (input) => _ip0(input) && (void 0 === input.subscriptions || "boolean" === typeof input.subscriptions) && _ip1(input) && _ip2(input) && _ip3(input) && "boolean" === typeof input.leaderboard && (void 0 === input.nativeLeaderboard || "boolean" === typeof input.nativeLeaderboard) && (void 0 === input.remoteLeaderboard || "boolean" === typeof input.remoteLeaderboard) && _ip4(input);
10713
10739
  const _io4 = (input) => ("local" === input.storage || "native" === input.storage || "none" === input.storage) && _ip4(input);
@@ -10927,14 +10953,14 @@ function writeEffectiveTargetConfigs(options = {}) {
10927
10953
  writeFileSync6(indexPath, indexContent);
10928
10954
  return index;
10929
10955
  }
10930
- function readExistingArtifactIndex(path10, version) {
10931
- if (!existsSync8(path10)) {
10956
+ function readExistingArtifactIndex(path12, version) {
10957
+ if (!existsSync8(path12)) {
10932
10958
  return {
10933
10959
  version,
10934
10960
  artifacts: []
10935
10961
  };
10936
10962
  }
10937
- const parsed = JSON.parse(readFileSync12(path10, "utf8"));
10963
+ const parsed = JSON.parse(readFileSync12(path12, "utf8"));
10938
10964
  if (parsed.version !== version) {
10939
10965
  return {
10940
10966
  version,
@@ -10945,14 +10971,14 @@ function readExistingArtifactIndex(path10, version) {
10945
10971
  }
10946
10972
  function writeEffectiveTargetConfig(target2, config2, outputDir) {
10947
10973
  assertDeploymentTargetName(target2);
10948
- const path10 = join10(outputDir, `${target2}.json`);
10974
+ const path12 = join10(outputDir, `${target2}.json`);
10949
10975
  const content = `${JSON.stringify(config2, null, 2)}
10950
10976
  `;
10951
- mkdirSync5(dirname7(path10), { recursive: true });
10952
- writeFileSync6(path10, content);
10977
+ mkdirSync5(dirname7(path12), { recursive: true });
10978
+ writeFileSync6(path12, content);
10953
10979
  return {
10954
10980
  target: target2,
10955
- path: path10,
10981
+ path: path12,
10956
10982
  digest: sha2562(content),
10957
10983
  version: config2.version
10958
10984
  };
@@ -12088,51 +12114,51 @@ function readNativeVersionEvidence(target2) {
12088
12114
  return {};
12089
12115
  }
12090
12116
  function readIconManifestEvidence(artifactPath) {
12091
- const path10 = readOptionalString2(process.env.MPGD_ICON_MANIFEST_PATH);
12092
- if (path10 === void 0) {
12117
+ const path12 = readOptionalString2(process.env.MPGD_ICON_MANIFEST_PATH);
12118
+ if (path12 === void 0) {
12093
12119
  throw new Error("MPGD_ICON_MANIFEST_PATH is required when generating a release manifest.");
12094
12120
  }
12095
- const bytes = readFileBytes(path10);
12121
+ const bytes = readFileBytes(path12);
12096
12122
  const parsed = JSON.parse(bytes.toString("utf8"));
12097
12123
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
12098
- throw new Error(`Invalid icon manifest: ${path10}`);
12124
+ throw new Error(`Invalid icon manifest: ${path12}`);
12099
12125
  }
12100
12126
  const manifest = parsed;
12101
12127
  const canonicalSource = manifest.canonicalSource;
12102
12128
  if (typeof canonicalSource !== "object" || canonicalSource === null || Array.isArray(canonicalSource)) {
12103
- throw new Error(`Invalid icon manifest canonicalSource: ${path10}`);
12129
+ throw new Error(`Invalid icon manifest canonicalSource: ${path12}`);
12104
12130
  }
12105
12131
  const canonicalSourceRecord = canonicalSource;
12106
12132
  return {
12107
12133
  path: requirePortableArtifactPath(artifactPath),
12108
12134
  digest: createHash8("sha256").update(bytes).digest("hex"),
12109
- sourceSha256: requireManifestString(canonicalSourceRecord, "sha256", path10),
12110
- sharedConfigSha256: requireManifestString(manifest, "sharedConfigSha256", path10),
12111
- renderConfigSha256: requireManifestString(manifest, "renderConfigSha256", path10),
12112
- generatorVersion: requireManifestString(manifest, "generatorVersion", path10),
12113
- targetProfile: requireManifestString(manifest, "targetProfile", path10),
12114
- targetProfileVersion: requireManifestString(manifest, "targetProfileVersion", path10)
12135
+ sourceSha256: requireManifestString(canonicalSourceRecord, "sha256", path12),
12136
+ sharedConfigSha256: requireManifestString(manifest, "sharedConfigSha256", path12),
12137
+ renderConfigSha256: requireManifestString(manifest, "renderConfigSha256", path12),
12138
+ generatorVersion: requireManifestString(manifest, "generatorVersion", path12),
12139
+ targetProfile: requireManifestString(manifest, "targetProfile", path12),
12140
+ targetProfileVersion: requireManifestString(manifest, "targetProfileVersion", path12)
12115
12141
  };
12116
12142
  }
12117
- function requirePortableArtifactPath(path10) {
12118
- const normalized = path10.replaceAll("\\", "/");
12143
+ function requirePortableArtifactPath(path12) {
12144
+ const normalized = path12.replaceAll("\\", "/");
12119
12145
  const segments = normalized.split("/");
12120
- if (normalized.length === 0 || isAbsolute8(path10) || isAbsolute8(normalized) || /^[A-Za-z]:\//u.test(normalized) || segments.some((segment) => segment.length === 0 || segment === "." || segment === "..")) {
12121
- throw new Error(`Invalid icon manifest artifact path: ${path10}`);
12146
+ if (normalized.length === 0 || isAbsolute8(path12) || isAbsolute8(normalized) || /^[A-Za-z]:\//u.test(normalized) || segments.some((segment) => segment.length === 0 || segment === "." || segment === "..")) {
12147
+ throw new Error(`Invalid icon manifest artifact path: ${path12}`);
12122
12148
  }
12123
12149
  return normalized;
12124
12150
  }
12125
- function readFileBytes(path10) {
12151
+ function readFileBytes(path12) {
12126
12152
  try {
12127
- return readFileSync14(path10);
12153
+ return readFileSync14(path12);
12128
12154
  } catch (error) {
12129
- throw new Error(`Failed to read icon manifest ${path10}: ${formatError5(error)}`);
12155
+ throw new Error(`Failed to read icon manifest ${path12}: ${formatError5(error)}`);
12130
12156
  }
12131
12157
  }
12132
- function requireManifestString(record2, key, path10) {
12158
+ function requireManifestString(record2, key, path12) {
12133
12159
  const value = record2[key];
12134
12160
  if (typeof value !== "string" || value.length === 0) {
12135
- throw new Error(`Invalid icon manifest ${key}: ${path10}`);
12161
+ throw new Error(`Invalid icon manifest ${key}: ${path12}`);
12136
12162
  }
12137
12163
  return value;
12138
12164
  }
@@ -12214,15 +12240,15 @@ function makeEffectiveConfigPathsPortable(manifest, baseDir) {
12214
12240
  ]))
12215
12241
  });
12216
12242
  }
12217
- function toPortablePath(baseDir, path10) {
12218
- if (!isAbsolute8(path10)) {
12219
- return path10;
12243
+ function toPortablePath(baseDir, path12) {
12244
+ if (!isAbsolute8(path12)) {
12245
+ return path12;
12220
12246
  }
12221
- const relativePath = relative9(baseDir, path10);
12247
+ const relativePath = relative9(baseDir, path12);
12222
12248
  if (relativePath.length > 0 && !relativePath.startsWith("..") && !isAbsolute8(relativePath)) {
12223
12249
  return relativePath;
12224
12250
  }
12225
- return path10;
12251
+ return path12;
12226
12252
  }
12227
12253
  function createBuildId() {
12228
12254
  const now = /* @__PURE__ */ new Date();
@@ -12622,20 +12648,20 @@ function listPrecacheEntries(artifactRoot) {
12622
12648
  throw new Error("PWA artifact traversal lost its directory.");
12623
12649
  }
12624
12650
  for (const entry of readdirSync4(directory, { withFileTypes: true })) {
12625
- const path10 = resolve13(directory, entry.name);
12651
+ const path12 = resolve13(directory, entry.name);
12626
12652
  if (entry.isSymbolicLink()) {
12627
- throw new Error(`PWA artifact must not contain symbolic links: ${path10}`);
12653
+ throw new Error(`PWA artifact must not contain symbolic links: ${path12}`);
12628
12654
  }
12629
12655
  if (entry.isDirectory()) {
12630
- pendingDirectories.push(path10);
12656
+ pendingDirectories.push(path12);
12631
12657
  } else if (entry.isFile()) {
12632
- files.push(path10);
12658
+ files.push(path12);
12633
12659
  }
12634
12660
  }
12635
12661
  }
12636
- return files.filter((path10) => !path10.endsWith(".map") && !path10.endsWith(`${sep6}pwa-release.json`) && !path10.endsWith(`${sep6}service-worker.js`)).map((path10) => ({
12637
- url: toPrecacheUrl(artifactRoot, path10),
12638
- source: readFileSync15(path10)
12662
+ return files.filter((path12) => !path12.endsWith(".map") && !path12.endsWith(`${sep6}pwa-release.json`) && !path12.endsWith(`${sep6}service-worker.js`)).map((path12) => ({
12663
+ url: toPrecacheUrl(artifactRoot, path12),
12664
+ source: readFileSync15(path12)
12639
12665
  })).sort((left, right) => compareCodeUnits(left.url, right.url));
12640
12666
  }
12641
12667
  function normalizePrecacheEntries(entries) {
@@ -12659,8 +12685,8 @@ function normalizePrecacheUrls(urls) {
12659
12685
  }
12660
12686
  return normalized.sort(compareCodeUnits);
12661
12687
  }
12662
- function toPrecacheUrl(root, path10) {
12663
- const portablePath = relative10(root, path10).split(sep6).join("/");
12688
+ function toPrecacheUrl(root, path12) {
12689
+ const portablePath = relative10(root, path12).split(sep6).join("/");
12664
12690
  return assertMicrosoftStorePwaPrecacheUrl(`./${portablePath}`);
12665
12691
  }
12666
12692
  function requireGameSpecificPwaId(value) {
@@ -13061,23 +13087,23 @@ function pathsOverlap2(first, second) {
13061
13087
  return isPathWithin2(first, second) || isPathWithin2(second, first);
13062
13088
  }
13063
13089
  function isPathWithin2(root, candidate) {
13064
- const path10 = relative11(root, candidate);
13065
- return path10 === "" || path10 !== ".." && !path10.startsWith(`..${sep7}`) && !isAbsolute9(path10);
13090
+ const path12 = relative11(root, candidate);
13091
+ return path12 === "" || path12 !== ".." && !path12.startsWith(`..${sep7}`) && !isAbsolute9(path12);
13066
13092
  }
13067
- function portablePathComparisonKey(path10) {
13068
- const portablePath = path10.replaceAll("\\", "/").split("/").map((segment) => {
13093
+ function portablePathComparisonKey(path12) {
13094
+ const portablePath = path12.replaceAll("\\", "/").split("/").map((segment) => {
13069
13095
  return segment === "." || segment === ".." ? segment : segment.replace(/[. ]+$/u, "");
13070
13096
  }).join("/");
13071
13097
  return canonicalizeThroughExistingAncestor2(portablePath).normalize("NFC").toLowerCase();
13072
13098
  }
13073
- function canonicalizeThroughExistingAncestor2(path10) {
13074
- const normalizedPath = resolve15(path10);
13099
+ function canonicalizeThroughExistingAncestor2(path12) {
13100
+ const normalizedPath = resolve15(path12);
13075
13101
  const suffix = [];
13076
13102
  let existingAncestor = normalizedPath;
13077
13103
  while (!existsSync12(existingAncestor)) {
13078
13104
  const parent = dirname8(existingAncestor);
13079
13105
  if (parent === existingAncestor) {
13080
- throw new Error(`Cannot resolve an existing ancestor for mini-game output: ${path10}`);
13106
+ throw new Error(`Cannot resolve an existing ancestor for mini-game output: ${path12}`);
13081
13107
  }
13082
13108
  suffix.unshift(basename4(existingAncestor));
13083
13109
  existingAncestor = parent;
@@ -13131,8 +13157,8 @@ function cleanupMiniGameArtifactBackup(backupRoot, removeDirectory = removeMiniG
13131
13157
  reportWarning(`The new mini-game artifact is active, but its prior backup could not be removed: ${backupRoot} (${formatError6(error)}). Remove this backup manually.`);
13132
13158
  }
13133
13159
  }
13134
- function removeMiniGameArtifactBackupDirectory(path10) {
13135
- rmSync6(path10, { force: true, maxRetries: 3, recursive: true, retryDelay: 100 });
13160
+ function removeMiniGameArtifactBackupDirectory(path12) {
13161
+ rmSync6(path12, { force: true, maxRetries: 3, recursive: true, retryDelay: 100 });
13136
13162
  }
13137
13163
  function assertMiniGameRequiredFiles(artifactRoot) {
13138
13164
  for (const file of [
@@ -13172,17 +13198,17 @@ function assertMiniGameJavaScriptSafety(artifactRoot, forbiddenMarkers, forbidde
13172
13198
  }
13173
13199
  }
13174
13200
  }
13175
- function assertMiniGameJavaScriptAstSafety(source, path10, forbiddenGlobals) {
13201
+ function assertMiniGameJavaScriptAstSafety(source, path12, forbiddenGlobals) {
13176
13202
  let ast;
13177
13203
  try {
13178
13204
  ast = parse(source, {
13179
13205
  ecmaVersion: "latest",
13180
- sourceType: path10.toLowerCase().endsWith(".mjs") ? "module" : "script",
13206
+ sourceType: path12.toLowerCase().endsWith(".mjs") ? "module" : "script",
13181
13207
  allowHashBang: true,
13182
13208
  allowReturnOutsideFunction: true
13183
13209
  });
13184
13210
  } catch (error) {
13185
- throw new Error(`Mini-game ${path10} is not valid JavaScript: ${formatError6(error)}`);
13211
+ throw new Error(`Mini-game ${path12} is not valid JavaScript: ${formatError6(error)}`);
13186
13212
  }
13187
13213
  const scopeAnalysis = createMiniGameScopeAnalysis(ast);
13188
13214
  const ancestors = [];
@@ -13197,7 +13223,7 @@ function assertMiniGameJavaScriptAstSafety(source, path10, forbiddenGlobals) {
13197
13223
  if (!isAstRecord(input)) {
13198
13224
  return;
13199
13225
  }
13200
- assertSafeNode(input, ancestors, path10, scopeAnalysis, forbiddenGlobals);
13226
+ assertSafeNode(input, ancestors, path12, scopeAnalysis, forbiddenGlobals);
13201
13227
  for (const [key, value] of Object.entries(input)) {
13202
13228
  if (key !== "start" && key !== "end" && key !== "loc" && key !== "range") {
13203
13229
  ancestors.push({ node: input, childKey: key });
@@ -13207,57 +13233,57 @@ function assertMiniGameJavaScriptAstSafety(source, path10, forbiddenGlobals) {
13207
13233
  }
13208
13234
  }
13209
13235
  }
13210
- function assertSafeNode(node, ancestors, path10, scopeAnalysis, forbiddenGlobals) {
13236
+ function assertSafeNode(node, ancestors, path12, scopeAnalysis, forbiddenGlobals) {
13211
13237
  if (node.type === "ImportExpression") {
13212
- throw new Error(`Mini-game ${path10} contains forbidden dynamic import.`);
13238
+ throw new Error(`Mini-game ${path12} contains forbidden dynamic import.`);
13213
13239
  }
13214
13240
  const parent = ancestors.at(-1)?.node;
13215
13241
  if (forbiddenGlobals.size > 0 && isMiniGameProtectedIntrinsicMutation(node, ancestors, scopeAnalysis)) {
13216
- throw new Error(`Mini-game ${path10} contains forbidden protected intrinsic mutation.`);
13242
+ throw new Error(`Mini-game ${path12} contains forbidden protected intrinsic mutation.`);
13217
13243
  }
13218
13244
  if (isMiniGameFunctionNode(node) && (parent?.type === "MethodDefinition" && parent.value === node || parent?.type === "PropertyDefinition" && parent.value === node || parent?.type === "Property" && parent.value === node) && readMiniGameFunctionReturnValues(node).some((value) => {
13219
13245
  return isReturnedDynamicCodeConstructorSource(value, ancestors, scopeAnalysis);
13220
13246
  })) {
13221
- throw new Error(`Mini-game ${path10} contains forbidden dynamic-code constructor.`);
13247
+ throw new Error(`Mini-game ${path12} contains forbidden dynamic-code constructor.`);
13222
13248
  }
13223
13249
  if (node.type === "Identifier" && typeof node.name === "string" && isExecutableIdentifierReference(ancestors)) {
13224
13250
  const scope = scopeAnalysis.scopeByNode.get(node) ?? scopeAnalysis.programScope;
13225
13251
  const binding = resolveMiniGameBinding(node.name, scope);
13226
13252
  if (binding === void 0 && forbiddenGlobals.has(node.name)) {
13227
- throw new Error(`Mini-game ${path10} contains forbidden platform global ${node.name}.`);
13253
+ throw new Error(`Mini-game ${path12} contains forbidden platform global ${node.name}.`);
13228
13254
  }
13229
13255
  if (binding !== void 0 && isInvocationArgument(node, ancestors) && (scopeAnalysis.dynamicCodeConstructorAliases.has(binding) || scopeAnalysis.dynamicCodeConstructorContainers.has(binding) || scopeAnalysis.dynamicCodeConstructorFactories.has(binding) || scopeAnalysis.dynamicCodeConstructorContainerFactories.has(binding))) {
13230
- throw new Error(`Mini-game ${path10} contains forbidden dynamic-code constructor.`);
13256
+ throw new Error(`Mini-game ${path12} contains forbidden dynamic-code constructor.`);
13231
13257
  }
13232
13258
  if (node.name === "eval") {
13233
- throw new Error(`Mini-game ${path10} contains forbidden eval.`);
13259
+ throw new Error(`Mini-game ${path12} contains forbidden eval.`);
13234
13260
  }
13235
13261
  if (node.name === "Function") {
13236
- throw new Error(`Mini-game ${path10} contains forbidden Function constructor.`);
13262
+ throw new Error(`Mini-game ${path12} contains forbidden Function constructor.`);
13237
13263
  }
13238
13264
  if (node.name === "importScripts" && !isTypeofReference(node, ancestors)) {
13239
- throw new Error(`Mini-game ${path10} contains forbidden importScripts.`);
13265
+ throw new Error(`Mini-game ${path12} contains forbidden importScripts.`);
13240
13266
  }
13241
13267
  }
13242
13268
  if (node.type === "MemberExpression") {
13243
13269
  const propertyNames = readMiniGameMemberNames(node, ancestors, scopeAnalysis);
13244
13270
  if (node.computed === true && propertyNames.size === 0 && isGlobalObjectAliasSource(node.object, ancestors, scopeAnalysis) && !isMiniGameSymbolValue(node.property, ancestors, scopeAnalysis)) {
13245
- throw new Error(`Mini-game ${path10} contains forbidden computed global read.`);
13271
+ throw new Error(`Mini-game ${path12} contains forbidden computed global read.`);
13246
13272
  }
13247
13273
  const forbiddenPlatformGlobal = [...propertyNames].find((propertyName) => {
13248
13274
  return forbiddenGlobals.has(propertyName);
13249
13275
  });
13250
13276
  if (forbiddenPlatformGlobal !== void 0) {
13251
- throw new Error(`Mini-game ${path10} contains forbidden platform global ${forbiddenPlatformGlobal}.`);
13277
+ throw new Error(`Mini-game ${path12} contains forbidden platform global ${forbiddenPlatformGlobal}.`);
13252
13278
  }
13253
13279
  const forbiddenDynamicCodeProperty = [...propertyNames].find((propertyName) => {
13254
13280
  return ["eval", "Function", "importScripts"].includes(propertyName);
13255
13281
  });
13256
13282
  if (forbiddenDynamicCodeProperty !== void 0 && (node.computed === true || isGlobalObjectAliasSource(node.object, ancestors, scopeAnalysis)) && !isTypeofReference(node, ancestors)) {
13257
- throw new Error(`Mini-game ${path10} contains forbidden ${forbiddenDynamicCodeProperty}.`);
13283
+ throw new Error(`Mini-game ${path12} contains forbidden ${forbiddenDynamicCodeProperty}.`);
13258
13284
  }
13259
13285
  if (isInvocationArgument(node, ancestors) && isDynamicCodeConstructorSource(node, ancestors, scopeAnalysis)) {
13260
- throw new Error(`Mini-game ${path10} contains forbidden dynamic-code constructor.`);
13286
+ throw new Error(`Mini-game ${path12} contains forbidden dynamic-code constructor.`);
13261
13287
  }
13262
13288
  }
13263
13289
  if (node.type === "Property") {
@@ -13267,17 +13293,17 @@ function assertSafeNode(node, ancestors, path10, scopeAnalysis, forbiddenGlobals
13267
13293
  return forbiddenGlobals.has(propertyName);
13268
13294
  });
13269
13295
  if (isObjectPatternProperty && forbiddenPlatformGlobal !== void 0) {
13270
- throw new Error(`Mini-game ${path10} contains forbidden platform global ${forbiddenPlatformGlobal}.`);
13296
+ throw new Error(`Mini-game ${path12} contains forbidden platform global ${forbiddenPlatformGlobal}.`);
13271
13297
  }
13272
13298
  if (isGlobalObjectDestructuring(ancestors, scopeAnalysis)) {
13273
13299
  if (node.computed === true) {
13274
- throw new Error(`Mini-game ${path10} contains forbidden computed destructuring.`);
13300
+ throw new Error(`Mini-game ${path12} contains forbidden computed destructuring.`);
13275
13301
  }
13276
13302
  const forbiddenDynamicCodeProperty = [...propertyNames].find((propertyName) => {
13277
13303
  return ["eval", "Function", "importScripts"].includes(propertyName);
13278
13304
  });
13279
13305
  if (forbiddenDynamicCodeProperty !== void 0) {
13280
- throw new Error(`Mini-game ${path10} contains forbidden ${forbiddenDynamicCodeProperty} destructuring.`);
13306
+ throw new Error(`Mini-game ${path12} contains forbidden ${forbiddenDynamicCodeProperty} destructuring.`);
13281
13307
  }
13282
13308
  }
13283
13309
  }
@@ -13286,35 +13312,35 @@ function assertSafeNode(node, ancestors, path10, scopeAnalysis, forbiddenGlobals
13286
13312
  const arguments_ = Array.isArray(node.arguments) ? node.arguments : [];
13287
13313
  const firstArgument = arguments_[0];
13288
13314
  if (isDynamicCodeConstructorInvocation(node.callee, arguments_, ancestors, scopeAnalysis)) {
13289
- throw new Error(`Mini-game ${path10} contains forbidden dynamic-code constructor.`);
13315
+ throw new Error(`Mini-game ${path12} contains forbidden dynamic-code constructor.`);
13290
13316
  }
13291
13317
  if (isUnknownComputedGlobalMember(node.callee, ancestors, scopeAnalysis)) {
13292
- throw new Error(`Mini-game ${path10} contains forbidden computed global call.`);
13318
+ throw new Error(`Mini-game ${path12} contains forbidden computed global call.`);
13293
13319
  }
13294
13320
  if (isReflectiveDynamicCodeGlobalRead(node, ancestors, scopeAnalysis, forbiddenGlobals)) {
13295
- throw new Error(`Mini-game ${path10} contains forbidden reflective global lookup.`);
13321
+ throw new Error(`Mini-game ${path12} contains forbidden reflective global lookup.`);
13296
13322
  }
13297
13323
  if (calleeNames.has("createElement") && resolveMiniGameStaticStrings(firstArgument, ancestors, scopeAnalysis).has("script")) {
13298
- throw new Error(`Mini-game ${path10} contains forbidden script element creation.`);
13324
+ throw new Error(`Mini-game ${path12} contains forbidden script element creation.`);
13299
13325
  }
13300
13326
  }
13301
13327
  if (node.type === "NewExpression" && isAstRecord(node.callee)) {
13302
13328
  const calleeNames = readMiniGameMemberNames(node.callee, ancestors, scopeAnalysis);
13303
13329
  if (isDynamicCodeConstructorSource(node.callee, ancestors, scopeAnalysis)) {
13304
- throw new Error(`Mini-game ${path10} contains forbidden dynamic-code constructor.`);
13330
+ throw new Error(`Mini-game ${path12} contains forbidden dynamic-code constructor.`);
13305
13331
  }
13306
13332
  if (isUnknownComputedGlobalMember(node.callee, ancestors, scopeAnalysis)) {
13307
- throw new Error(`Mini-game ${path10} contains forbidden computed global construction.`);
13333
+ throw new Error(`Mini-game ${path12} contains forbidden computed global construction.`);
13308
13334
  }
13309
13335
  const forbiddenWorker = [...calleeNames].find((calleeName) => {
13310
13336
  return calleeName === "Worker" || calleeName === "SharedWorker";
13311
13337
  });
13312
13338
  if (forbiddenWorker !== void 0) {
13313
- throw new Error(`Mini-game ${path10} contains forbidden ${forbiddenWorker} construction.`);
13339
+ throw new Error(`Mini-game ${path12} contains forbidden ${forbiddenWorker} construction.`);
13314
13340
  }
13315
13341
  }
13316
13342
  if (node.type === "TaggedTemplateExpression" && isDynamicCodeConstructorSource(node.tag, ancestors, scopeAnalysis)) {
13317
- throw new Error(`Mini-game ${path10} contains forbidden dynamic-code constructor.`);
13343
+ throw new Error(`Mini-game ${path12} contains forbidden dynamic-code constructor.`);
13318
13344
  }
13319
13345
  }
13320
13346
  function isInvocationArgument(node, ancestors) {
@@ -15165,10 +15191,10 @@ function createEvidence(input, files, effectiveTargetConfig, iconManifest, packa
15165
15191
  files
15166
15192
  };
15167
15193
  }
15168
- function requireEvidenceFile(artifactRoot, files, path10) {
15169
- const file = files.find((candidate) => candidate.path === path10);
15170
- if (file === void 0 || !existsSync12(join11(artifactRoot, path10))) {
15171
- throw new Error(`Mini-game artifact is missing required evidence file: ${path10}`);
15194
+ function requireEvidenceFile(artifactRoot, files, path12) {
15195
+ const file = files.find((candidate) => candidate.path === path12);
15196
+ if (file === void 0 || !existsSync12(join11(artifactRoot, path12))) {
15197
+ throw new Error(`Mini-game artifact is missing required evidence file: ${path12}`);
15172
15198
  }
15173
15199
  return file;
15174
15200
  }
@@ -15242,9 +15268,9 @@ function verifyNamedDigest(artifactRoot, reference, expectedPath, label) {
15242
15268
  throw new Error(`Mini-game artifact ${label} digest mismatch.`);
15243
15269
  }
15244
15270
  }
15245
- function readJson(path10, label) {
15271
+ function readJson(path12, label) {
15246
15272
  try {
15247
- return JSON.parse(readFileSync16(path10, "utf8"));
15273
+ return JSON.parse(readFileSync16(path12, "utf8"));
15248
15274
  } catch (error) {
15249
15275
  throw new Error(`${label} is unreadable: ${formatError6(error)}`);
15250
15276
  }
@@ -15311,8 +15337,8 @@ function assertWechatAppId(appId, production) {
15311
15337
  throw new Error("WeChat Mini Game app id must be a valid AppID or the staging placeholder.");
15312
15338
  }
15313
15339
  }
15314
- function writeJson(path10, input) {
15315
- writeFileSync11(path10, `${JSON.stringify(input, null, 2)}
15340
+ function writeJson(path12, input) {
15341
+ writeFileSync11(path12, `${JSON.stringify(input, null, 2)}
15316
15342
  `);
15317
15343
  }
15318
15344
 
@@ -15444,7 +15470,7 @@ function isProcessAlive(pid) {
15444
15470
  }
15445
15471
 
15446
15472
  // node_modules/.cache/mpgd-native-build/tools/target/native-build-execution.js
15447
- import { existsSync as existsSync24, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync4, readdirSync as readdirSync7, rmSync as rmSync11, statSync as statSync6 } from "node:fs";
15473
+ import { existsSync as existsSync26, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync4, readdirSync as readdirSync8, rmSync as rmSync11, statSync as statSync6 } from "node:fs";
15448
15474
  import { tmpdir as tmpdir4 } from "node:os";
15449
15475
  import { dirname as dirname11, isAbsolute as isAbsolute12, join as join15 } from "node:path";
15450
15476
 
@@ -16248,9 +16274,14 @@ function applyCapacitorShellCsp(webDir) {
16248
16274
  }
16249
16275
 
16250
16276
  // node_modules/.cache/mpgd-native-build/tools/target/native-store-policy.js
16251
- import { existsSync as existsSync19, readFileSync as readFileSync20 } from "node:fs";
16277
+ import { existsSync as existsSync21, readFileSync as readFileSync22 } from "node:fs";
16252
16278
  import { dirname as dirname9, join as join13 } from "node:path";
16253
16279
 
16280
+ // node_modules/.cache/mpgd-native-build/packages/cli/src/crash-reporting-starter.js
16281
+ import { existsSync as existsSync19, lstatSync as lstatSync12, readFileSync as readFileSync20, writeFileSync as writeFileSync15 } from "node:fs";
16282
+ import path7 from "node:path";
16283
+ import { satisfies, valid, validRange } from "semver";
16284
+
16254
16285
  // node_modules/.cache/mpgd-native-build/packages/cli/src/native-shell-identity.js
16255
16286
  function assertAndroidSettingsAppProject(source) {
16256
16287
  const clean = stripGradleComments(source);
@@ -16620,238 +16651,615 @@ function readBracedText(source, opening) {
16620
16651
  throw new Error("Existing native project block is incomplete.");
16621
16652
  }
16622
16653
 
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;
16669
- }
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) };
16674
- }
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.`);
16692
- }
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
- });
16748
- }
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
- });
16757
- }
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"]
16654
+ // node_modules/.cache/mpgd-native-build/packages/cli/src/notifications-starter.js
16655
+ var notificationsMessagingPlugin = "@capacitor-firebase/messaging";
16656
+ var notificationsGameDependencies = Object.freeze({
16657
+ [notificationsMessagingPlugin]: "8.5.2",
16658
+ "@capacitor/local-notifications": "8.3.1",
16659
+ firebase: "12.19.0"
16660
+ });
16661
+ var notificationsShellPlugins = Object.freeze([
16662
+ notificationsMessagingPlugin,
16663
+ "@capacitor/local-notifications"
16664
+ ]);
16665
+ var gatewayImport = {
16666
+ anchor: "import type { RuntimeConfig } from '../runtimeDetector';\n",
16667
+ replacement: [
16668
+ "import { createStarterCapacitorNotifications } from '../capacitorNotifications';",
16669
+ "import type { RuntimeConfig } from '../runtimeDetector';",
16670
+ ""
16671
+ ].join("\n")
16793
16672
  };
16794
- function inspectAndroidManifestOrientation(manifest, orientation) {
16795
- if (orientation === void 0) {
16796
- return [];
16797
- }
16798
- const activity = findAndroidLauncherActivity(manifest);
16799
- if (activity === void 0) {
16800
- return [{ level: "error", message: "Android manifest has no launcher activity." }];
16801
- }
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;
16818
- }
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) });
16849
- }
16850
- return found;
16851
- }
16852
- function maskXmlComments(xml) {
16853
- return xml.replace(/<!--[\s\S]*?-->/gu, (comment) => comment.replace(/[^\n]/gu, " "));
16854
- }
16673
+ var gatewayProviders = (platform) => ({
16674
+ anchor: " const providers = createStoreProviders(runtime, () => gateway);\n",
16675
+ replacement: [
16676
+ " const providers = [",
16677
+ " ...createStoreProviders(runtime, () => gateway),",
16678
+ " // Notifications v2 (FCM and local reminders); `notifications/topics.json`",
16679
+ " // maps the topics, and the shell needs the two plugins",
16680
+ " // (`mpgd target init capacitor --providers notifications`).",
16681
+ ` ...(await createStarterCapacitorNotifications('${platform}', runtime.configTarget)),`,
16682
+ " ];",
16683
+ ""
16684
+ ].join("\n")
16685
+ });
16686
+ var commonEdits = {
16687
+ "src/platform/buildGateways/capacitorAndroid.ts": [gatewayImport, gatewayProviders("android")],
16688
+ "src/platform/buildGateways/capacitorIos.ts": [gatewayImport, gatewayProviders("ios")],
16689
+ "src/platform/buildGateways/reddit.ts": [
16690
+ {
16691
+ anchor: " createDevvitCommerceProducts,\n createDevvitPlatformGateway,\n",
16692
+ replacement: [
16693
+ " createDevvitCommerceProducts,",
16694
+ " createDevvitNotificationsBackend,",
16695
+ " createDevvitPlatformGateway,",
16696
+ ""
16697
+ ].join("\n")
16698
+ },
16699
+ {
16700
+ anchor: "import { createDevvitWebPaymentsClient } from '@mpgd/adapter-devvit/web';\n",
16701
+ replacement: [
16702
+ "import {",
16703
+ " createDevvitWebLoginPrompt,",
16704
+ " createDevvitWebPaymentsClient,",
16705
+ "} from '@mpgd/adapter-devvit/web';",
16706
+ ""
16707
+ ].join("\n")
16708
+ },
16709
+ {
16710
+ anchor: "import type { RuntimeConfig } from '../runtimeDetector';\n",
16711
+ replacement: [
16712
+ "import { setStarterHostedNotificationBackend } from '../notificationBackend';",
16713
+ "import { starterNotificationTopics } from '../notifications';",
16714
+ "import type { RuntimeConfig } from '../runtimeDetector';",
16715
+ ""
16716
+ ].join("\n")
16717
+ },
16718
+ {
16719
+ anchor: " ...(commerce === undefined ? {} : { commerce }),\n",
16720
+ replacement: [
16721
+ " ...(commerce === undefined ? {} : { commerce }),",
16722
+ " // Notifications v2: the game-rendered opt-in the server bridge serves",
16723
+ " // (a gated beta: `integrations.notifications` stays approval-required",
16724
+ " // until Reddit approves the app), and Reddit login for a logged-out",
16725
+ " // player, who must log in before opting in.",
16726
+ " ...notificationOptions(runtime),",
16727
+ " loginPrompt: createDevvitWebLoginPrompt(),",
16728
+ ""
16729
+ ].join("\n")
16730
+ },
16731
+ {
16732
+ anchor: "function createCommerceOptions(): DevvitGatewayCommerceOptions | undefined {\n",
16733
+ replacement: [
16734
+ "function notificationOptions(",
16735
+ " runtime: RuntimeConfig,",
16736
+ "): Pick<Parameters<typeof createDevvitPlatformGateway>[0], 'notifications'> {",
16737
+ " const topics = starterNotificationTopics(runtime.configTarget).map((topic) => topic.id);",
16738
+ " if (topics.length === 0) {",
16739
+ " return {};",
16740
+ " }",
16741
+ " // The runtime's backend is the Devvit server (consent records, reminders, notices for the",
16742
+ " // logged-in Reddit user), over the same server bridge as the gateway; created on first use.",
16743
+ " setStarterHostedNotificationBackend(runtime.configTarget, () =>",
16744
+ " createDevvitNotificationsBackend({ appVersion: runtime.appVersion, buildId: runtime.buildId }));",
16745
+ " return { notifications: { topics } };",
16746
+ "}",
16747
+ "",
16748
+ "function createCommerceOptions(): DevvitGatewayCommerceOptions | undefined {",
16749
+ ""
16750
+ ].join("\n")
16751
+ }
16752
+ ],
16753
+ "apps/target-devvit/src/server/index.ts": [
16754
+ {
16755
+ anchor: "import { context, createServer, getServerPort, payments, reddit, redis } from '@devvit/web/server';\n",
16756
+ replacement: [
16757
+ "import {",
16758
+ " context,",
16759
+ " createServer,",
16760
+ " getServerPort,",
16761
+ " notifications,",
16762
+ " payments,",
16763
+ " reddit,",
16764
+ " redis,",
16765
+ "} from '@devvit/web/server';",
16766
+ ""
16767
+ ].join("\n")
16768
+ },
16769
+ {
16770
+ anchor: [
16771
+ "import {",
16772
+ " createDevvitCommerceBridgeProducts,",
16773
+ " createDevvitCommerceV2BridgeHandler,",
16774
+ "} from '@mpgd/adapter-devvit/server';",
16775
+ ""
16776
+ ].join("\n"),
16777
+ replacement: [
16778
+ "import {",
16779
+ " createDevvitCommerceBridgeProducts,",
16780
+ " createDevvitCommerceV2BridgeHandler,",
16781
+ " createDevvitNotificationService,",
16782
+ " createDevvitNotificationsBackendBridgeHandler,",
16783
+ " createDevvitNotificationsV2BridgeHandler,",
16784
+ "} from '@mpgd/adapter-devvit/server';",
16785
+ ""
16786
+ ].join("\n")
16787
+ },
16788
+ {
16789
+ anchor: "import type { ProductCatalog } from '@mpgd/catalog';\n",
16790
+ replacement: [
16791
+ "import { resolveNotificationTopicsForTarget, type NotificationTopicCatalog, type ProductCatalog } from '@mpgd/catalog';",
16792
+ "import { assertNotificationDeployment } from '@mpgd/platform/notifications';",
16793
+ ""
16794
+ ].join("\n")
16795
+ },
16796
+ {
16797
+ anchor: "import devvitManifest from '../../devvit.json';\n",
16798
+ replacement: [
16799
+ "import notificationDeploymentJson from '../../../../notifications/deployment.json';",
16800
+ "import notificationTopicsJson from '../../../../notifications/topics.json';",
16801
+ "import devvitManifest from '../../devvit.json';",
16802
+ ""
16803
+ ].join("\n")
16804
+ },
16805
+ {
16806
+ anchor: [
16807
+ "const handleBridgeRequest = createDevvitBridgeHandler({",
16808
+ " redis,",
16809
+ " currentPlayerId,",
16810
+ " currentDisplayName,",
16811
+ " storageKeyNamespace: gameName,",
16812
+ " ...(commerce === undefined ? {} : { commerce }),",
16813
+ "});",
16814
+ ""
16815
+ ].join("\n"),
16816
+ replacement: [
16817
+ "/**",
16818
+ " * Notifications v2 (`notifications/topics.json`, mapped for the `reddit`",
16819
+ " * target): the game-rendered opt-in for the logged-in requester, and the",
16820
+ " * service that records consent and sends server reminders over the Devvit",
16821
+ " * notifications plugin. Devvit notifications are a gated beta: the game",
16822
+ " * shows them only once `integrations.notifications` is `available`.",
16823
+ " */",
16824
+ "const notificationTopicCatalog = notificationTopicsJson as NotificationTopicCatalog;",
16825
+ "const redditNotificationTopics = readRedditNotificationTopics();",
16826
+ "const notificationService: ReturnType<typeof createDevvitNotificationService>['service'] | undefined =",
16827
+ " redditNotificationTopics.length === 0 ? undefined : createNotificationService();",
16828
+ "// The opt-in is served only with the service that sends; a refused catalog turns both off.",
16829
+ "const notificationsBridge = notificationService === undefined",
16830
+ " ? undefined",
16831
+ " : createDevvitNotificationsV2BridgeHandler({",
16832
+ " notifications,",
16833
+ " currentUserId: currentPlayerId,",
16834
+ " topics: redditNotificationTopics,",
16835
+ " });",
16836
+ "// The webview runtime's backend: consent records, reminders, and notices for the request's user.",
16837
+ "const notificationsBackend = notificationService === undefined",
16838
+ " ? undefined",
16839
+ " : createDevvitNotificationsBackendBridgeHandler({ service: notificationService, context: () => context });",
16840
+ "const handleBridgeRequest = createDevvitBridgeHandler({",
16841
+ " redis,",
16842
+ " currentPlayerId,",
16843
+ " currentDisplayName,",
16844
+ " storageKeyNamespace: gameName,",
16845
+ " ...(commerce === undefined ? {} : { commerce }),",
16846
+ " ...(notificationsBridge === undefined ? {} : { notifications: notificationsBridge }),",
16847
+ " ...(notificationsBackend === undefined ? {} : { notificationsBackend }),",
16848
+ "});",
16849
+ ""
16850
+ ].join("\n")
16851
+ },
16852
+ {
16853
+ anchor: [
16854
+ "/**",
16855
+ " * The notifications v2 service (`createDevvitNotificationService` from",
16856
+ " * `@mpgd/adapter-devvit/server`). None until the game declares a topic",
16857
+ " * catalog, so the scheduler tasks below do no work.",
16858
+ " */",
16859
+ "const notificationService: Parameters<typeof handleNotificationTask>[0]['notifications'] = undefined;",
16860
+ ""
16861
+ ].join("\n"),
16862
+ replacement: [
16863
+ "/** The topic ids the catalog maps for Reddit; none when the catalog cannot be read. */",
16864
+ "function readRedditNotificationTopics(): readonly string[] {",
16865
+ " try {",
16866
+ " return resolveNotificationTopicsForTarget(notificationTopicCatalog, 'reddit').map((topic) => topic.id);",
16867
+ " } catch (error) {",
16868
+ " console.error(`devvit notifications disabled: ${errorMessage(error)}`);",
16869
+ " return [];",
16870
+ " }",
16871
+ "}",
16872
+ "",
16873
+ "/**",
16874
+ " * The notifications v2 service over Redis and the Devvit plugin; the",
16875
+ " * scheduler tasks below run its reminders and purges. A catalog or",
16876
+ " * deployment the service refuses turns notifications off, never the game",
16877
+ " * server.",
16878
+ " */",
16879
+ "function createNotificationService(): ReturnType<typeof createDevvitNotificationService>['service'] | undefined {",
16880
+ " try {",
16881
+ " return createDevvitNotificationService({",
16882
+ " redis,",
16883
+ " notifications,",
16884
+ " catalog: notificationTopicCatalog,",
16885
+ " deployment: assertNotificationDeployment(notificationDeploymentJson),",
16886
+ " }).service;",
16887
+ " } catch (error) {",
16888
+ " console.error(`devvit notifications disabled: ${errorMessage(error)}`);",
16889
+ " return undefined;",
16890
+ " }",
16891
+ "}",
16892
+ ""
16893
+ ].join("\n")
16894
+ }
16895
+ ]
16896
+ };
16897
+ var phaserEdits = {
16898
+ "src/runtime/createGame.ts": [
16899
+ {
16900
+ anchor: "import { LobbyScene } from '../scenes/LobbyScene';\n",
16901
+ replacement: [
16902
+ "import { LobbyScene } from '../scenes/LobbyScene';",
16903
+ "import {",
16904
+ " NotificationSettingsScene,",
16905
+ " routeNotificationSettingsOpens,",
16906
+ "} from '../scenes/NotificationSettingsScene';",
16907
+ ""
16908
+ ].join("\n")
16909
+ },
16910
+ {
16911
+ anchor: " scene: [BootScene, LobbyScene, PlayScene],\n",
16912
+ replacement: " scene: [BootScene, LobbyScene, PlayScene, NotificationSettingsScene],\n"
16913
+ },
16914
+ {
16915
+ anchor: " ownGameAudio(game, input.context);\n",
16916
+ replacement: [
16917
+ " ownGameAudio(game, input.context);",
16918
+ " // The advertising opt-out action and consent notices open the settings screen.",
16919
+ " routeNotificationSettingsOpens(game, input.context);",
16920
+ ""
16921
+ ].join("\n")
16922
+ }
16923
+ ],
16924
+ "src/scenes/LobbyScene.ts": [
16925
+ {
16926
+ anchor: "import { starterContextKey, type StarterContext } from '../runtime/gameContext';\n",
16927
+ replacement: [
16928
+ "import { starterContextKey, type StarterContext } from '../runtime/gameContext';",
16929
+ "import { notificationSettingsSceneKey } from './NotificationSettingsScene';",
16930
+ ""
16931
+ ].join("\n")
16932
+ },
16933
+ {
16934
+ anchor: " this.input.once('pointerdown', () => this.scene.start('PlayScene'));\n",
16935
+ replacement: [
16936
+ " // The settings button shows only where this target has notifications.",
16937
+ " const notificationsButton = context.platform.notificationsV2 === undefined",
16938
+ " ? undefined",
16939
+ " : this.add",
16940
+ " .text(940, 16, locale === 'ko' ? '\uC54C\uB9BC \uC124\uC815' : 'Notifications', {",
16941
+ " color: '#5eead4',",
16942
+ " fontFamily: 'Arial, sans-serif',",
16943
+ " fontSize: '22px',",
16944
+ " })",
16945
+ " .setOrigin(1, 0)",
16946
+ " .setPadding(16, 12, 16, 12)",
16947
+ " .setInteractive({ useHandCursor: true });",
16948
+ " notificationsButton?.on('pointerup', () => this.scene.start(notificationSettingsSceneKey));",
16949
+ " // A tap anywhere else starts the game; the scene drops this listener on shutdown.",
16950
+ " this.input.on('pointerdown', (_pointer: Phaser.Input.Pointer, over: Phaser.GameObjects.GameObject[]) => {",
16951
+ " if (notificationsButton === undefined || !over.includes(notificationsButton)) {",
16952
+ " this.scene.start('PlayScene');",
16953
+ " }",
16954
+ " });",
16955
+ ""
16956
+ ].join("\n")
16957
+ }
16958
+ ]
16959
+ };
16960
+ var liteEdits = {
16961
+ "src/runtime/createGame.ts": [
16962
+ {
16963
+ anchor: "import { createTapGame } from '../lite/tapGame';\n",
16964
+ replacement: [
16965
+ "import { mountNotificationSettingsPanel } from '../lite/notificationSettingsPanel';",
16966
+ "import { createTapGame } from '../lite/tapGame';",
16967
+ ""
16968
+ ].join("\n")
16969
+ },
16970
+ {
16971
+ anchor: " const shell = createTapGame({ mount, title: '__GAME_TITLE__' });\n",
16972
+ replacement: [
16973
+ " const shell = createTapGame({ mount, title: '__GAME_TITLE__' });",
16974
+ " const disposeNotificationSettings = mountNotificationSettingsPanel(mount, input.context);",
16975
+ ""
16976
+ ].join("\n")
16977
+ },
16978
+ {
16979
+ anchor: " audio.dispose();\n",
16980
+ replacement: [" audio.dispose();", " disposeNotificationSettings();", ""].join("\n")
16981
+ }
16982
+ ]
16983
+ };
16984
+ var readmeSection = [
16985
+ "",
16986
+ "## Notifications",
16987
+ "",
16988
+ "This game was created with `--providers notifications`. Its topics live in",
16989
+ "`notifications/topics.json` and the backend settings (`advertisingRules`,",
16990
+ "the advertising sender) in `notifications/deployment.json`; run",
16991
+ "`pnpm validate:notifications` after every change and before every release.",
16992
+ "Replace the sender contact placeholder in `notifications/deployment.json`",
16993
+ "before any advertising is sent. The notification settings screen asks for the",
16994
+ "OS or host permission and records reminder, advertising (\uAD11\uACE0\uC131 \uC815\uBCF4",
16995
+ "\uC218\uC2E0\uB3D9\uC758), and night consent through the notifications runtime. See",
16996
+ "`docs/NOTIFICATIONS.md` and, for Android and iOS, the mpgd-kit Firebase setup",
16997
+ "guide; `mpgd target init capacitor --providers notifications` installs the two",
16998
+ "Capacitor plugins into a game-owned shell.",
16999
+ ""
17000
+ ].join("\n");
17001
+ var notificationsCapacitorGateways = Object.freeze({
17002
+ android: "src/platform/buildGateways/capacitorAndroid.ts",
17003
+ ios: "src/platform/buildGateways/capacitorIos.ts"
17004
+ });
17005
+ var notificationsCapacitorGatewayModules = Object.freeze([
17006
+ "src/platform/capacitorNotifications.ts",
17007
+ "src/platform/notifications.ts",
17008
+ "src/platform/notificationBackend.ts",
17009
+ "src/platform/notificationSettings.ts",
17010
+ "src/platform/notificationOpens.ts"
17011
+ ]);
17012
+ var notificationsCapacitorSupportFiles = Object.freeze([
17013
+ ...notificationsCapacitorGatewayModules,
17014
+ "notifications/topics.json",
17015
+ "notifications/deployment.json",
17016
+ "tools/validate-notifications.ts",
17017
+ "docs/NOTIFICATIONS.md"
17018
+ ]);
17019
+ var notificationsCapacitorTemplateSupportFiles = Object.freeze([
17020
+ "src/platform/serverSession.ts",
17021
+ "src/platform/developmentServerSession.ts"
17022
+ ]);
17023
+ var notificationsCapacitorTemplateSupportImporters = Object.freeze({
17024
+ "src/platform/developmentServerSession.ts": {
17025
+ importer: "src/platform/serverSession.ts",
17026
+ requires: { packageName: "@mpgd/game-services", exportName: "createDevelopmentServerSession" },
17027
+ 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."
17028
+ }
17029
+ });
17030
+
17031
+ // node_modules/.cache/mpgd-native-build/packages/cli/src/native-shell-store-policy.js
17032
+ function inspectIosInfoPlistStorePolicy(plist) {
17033
+ const issues = [];
17034
+ if (lacksExportComplianceKey(plist)) {
17035
+ issues.push({
17036
+ level: "warning",
17037
+ 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)."
17038
+ });
17039
+ }
17040
+ const capabilities = readPlistTopLevelValue(plist, "UIRequiredDeviceCapabilities");
17041
+ if (capabilities !== void 0 && requires32BitArm(capabilities.xml)) {
17042
+ issues.push({
17043
+ level: "warning",
17044
+ message: "iOS Info.plist UIRequiredDeviceCapabilities lists the 32-bit armv7; use arm64."
17045
+ });
17046
+ }
17047
+ return issues;
17048
+ }
17049
+ function lacksExportComplianceKey(plist) {
17050
+ return readPlistTopLevelValue(plist, "ITSAppUsesNonExemptEncryption") === void 0;
17051
+ }
17052
+ function requires32BitArm(capabilitiesXml) {
17053
+ return /<string>\s*armv7\s*<\/string>/u.test(capabilitiesXml);
17054
+ }
17055
+ function readPlistTopLevelValue(plist, key) {
17056
+ const masked = plist.replace(/<!--[\s\S]*?-->/gu, (comment) => comment.replace(/[^\n]/gu, " "));
17057
+ const marker = new RegExp(`<key>\\s*${escapeRegExp(key)}\\s*</key>\\s*`, "u").exec(masked);
17058
+ if (marker === null) {
17059
+ return void 0;
17060
+ }
17061
+ const start = marker.index + marker[0].length;
17062
+ const open2 = /^<([A-Za-z]+)\s*(\/?)>/u.exec(masked.slice(start));
17063
+ if (open2 === null) {
17064
+ throw new Error(`Info.plist key ${key} has no readable value.`);
17065
+ }
17066
+ const tag = open2[1] ?? "";
17067
+ if (open2[2] === "/") {
17068
+ const end = start + open2[0].length;
17069
+ return { start, end, xml: plist.slice(start, end) };
17070
+ }
17071
+ let depth = 0;
17072
+ const tags = new RegExp(`<(/?)${tag}\\b[^>]*?(/?)>`, "gu");
17073
+ tags.lastIndex = start;
17074
+ for (let match = tags.exec(masked); match !== null; match = tags.exec(masked)) {
17075
+ if (match[2] === "/") {
17076
+ continue;
17077
+ }
17078
+ depth += match[1] === "/" ? -1 : 1;
17079
+ if (depth === 0) {
17080
+ const end = match.index + match[0].length;
17081
+ return { start, end, xml: plist.slice(start, end) };
17082
+ }
17083
+ }
17084
+ throw new Error(`Info.plist key ${key} has an unclosed value.`);
17085
+ }
17086
+ function escapeRegExp(value) {
17087
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
17088
+ }
17089
+ var nativeShellOrientations = ["portrait", "landscape", "any"];
17090
+ function readGameNativeOrientation(gameConfig) {
17091
+ if (!isRecord5(gameConfig) || gameConfig.native === void 0) {
17092
+ return void 0;
17093
+ }
17094
+ if (!isRecord5(gameConfig.native)) {
17095
+ throw new Error("mpgd.game.json native must be an object when present.");
17096
+ }
17097
+ for (const key of Object.keys(gameConfig.native)) {
17098
+ if (key !== "orientation") {
17099
+ throw new Error(`mpgd.game.json native.${key} is not supported.`);
17100
+ }
17101
+ }
17102
+ const orientation = gameConfig.native.orientation;
17103
+ if (orientation === void 0) {
17104
+ return void 0;
17105
+ }
17106
+ if (!nativeShellOrientations.includes(orientation)) {
17107
+ throw new Error("mpgd.game.json native.orientation must be 'portrait', 'landscape' or 'any'.");
17108
+ }
17109
+ return orientation;
17110
+ }
17111
+ function readIosDeviceFamily(metadata) {
17112
+ if (!isRecord5(metadata) || metadata.iosDeviceFamily === void 0) {
17113
+ return void 0;
17114
+ }
17115
+ if (metadata.iosDeviceFamily !== "universal" && metadata.iosDeviceFamily !== "iphone") {
17116
+ throw new Error("ios metadata.iosDeviceFamily must be 'universal' or 'iphone'.");
17117
+ }
17118
+ return metadata.iosDeviceFamily;
17119
+ }
17120
+ var iosPortrait = "UIInterfaceOrientationPortrait";
17121
+ var iosPortraitUpsideDown = "UIInterfaceOrientationPortraitUpsideDown";
17122
+ var iosLandscape = [
17123
+ "UIInterfaceOrientationLandscapeLeft",
17124
+ "UIInterfaceOrientationLandscapeRight"
17125
+ ];
17126
+ var iosIpadOrientations = [
17127
+ iosPortrait,
17128
+ iosPortraitUpsideDown,
17129
+ ...iosLandscape
17130
+ ];
17131
+ function readPlistStrings(plist, key) {
17132
+ const value = readPlistTopLevelValue(plist, key);
17133
+ if (value === void 0) {
17134
+ return void 0;
17135
+ }
17136
+ return [...value.xml.matchAll(/<string>\s*([^<]*?)\s*<\/string>/gu)].map((match) => match[1] ?? "");
17137
+ }
17138
+ function inspectIosInfoPlistOrientation(plist, orientation, deviceFamily) {
17139
+ const issues = [];
17140
+ if (orientation !== void 0) {
17141
+ const listed = readPlistStrings(plist, "UISupportedInterfaceOrientations");
17142
+ const iphone = listed ?? iosIpadOrientations;
17143
+ const portrait = iphone.some((value) => value === iosPortrait || value === iosPortraitUpsideDown);
17144
+ const landscape = iphone.some((value) => iosLandscape.includes(value));
17145
+ const matchesByOrientation = {
17146
+ portrait: portrait && !landscape,
17147
+ landscape: landscape && !portrait,
17148
+ any: portrait && landscape
17149
+ };
17150
+ const matches = matchesByOrientation[orientation];
17151
+ if (!matches) {
17152
+ issues.push({
17153
+ level: "error",
17154
+ 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}".`
17155
+ });
17156
+ }
17157
+ }
17158
+ if (deviceFamily === "universal") {
17159
+ const ipad = readPlistStrings(plist, "UISupportedInterfaceOrientations~ipad") ?? readPlistStrings(plist, "UISupportedInterfaceOrientations") ?? iosIpadOrientations;
17160
+ if (iosIpadOrientations.some((value) => !ipad.includes(value))) {
17161
+ issues.push({
17162
+ level: "warning",
17163
+ 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."
17164
+ });
17165
+ }
17166
+ }
17167
+ return issues;
17168
+ }
17169
+ function iosTargetedDeviceFamily(family) {
17170
+ return family === "iphone" ? "1" : "1,2";
17171
+ }
17172
+ function readIosAppReleaseDeviceFamily(project) {
17173
+ for (const id of readAppTargetConfigurationIds(project)) {
17174
+ const settings = locatePbxBuildSettings(project, id);
17175
+ if (settings.name !== "Release") {
17176
+ continue;
17177
+ }
17178
+ const value = /\bTARGETED_DEVICE_FAMILY\s*=\s*([^;]+);/u.exec(project.slice(settings.start, settings.end))?.[1];
17179
+ return value?.trim().replace(/^"(.*)"$/u, "$1").replace(/\s+/gu, "");
17180
+ }
17181
+ return void 0;
17182
+ }
17183
+ function inspectIosProjectDeviceFamily(project, family) {
17184
+ if (family === void 0) {
17185
+ return [];
17186
+ }
17187
+ const actual = readIosAppReleaseDeviceFamily(project);
17188
+ const expected = iosTargetedDeviceFamily(family);
17189
+ if (actual === expected) {
17190
+ return [];
17191
+ }
17192
+ return [{
17193
+ level: "error",
17194
+ message: `iOS App target Release TARGETED_DEVICE_FAMILY is ${actual ?? "unset"}, but mpgd.targets.json ios metadata.iosDeviceFamily "${family}" needs "${expected}".`
17195
+ }];
17196
+ }
17197
+ var androidOrientationValues = {
17198
+ portrait: ["portrait", "sensorPortrait", "userPortrait", "reversePortrait"],
17199
+ landscape: ["landscape", "sensorLandscape", "userLandscape", "reverseLandscape"],
17200
+ any: ["unspecified", "user", "sensor", "fullSensor", "fullUser"]
17201
+ };
17202
+ function inspectAndroidManifestOrientation(manifest, orientation) {
17203
+ if (orientation === void 0) {
17204
+ return [];
17205
+ }
17206
+ const activity = findAndroidLauncherActivity(manifest);
17207
+ if (activity === void 0) {
17208
+ return [{ level: "error", message: "Android manifest has no launcher activity." }];
17209
+ }
17210
+ const value = readXmlAttribute(activity.tag, "android:screenOrientation") ?? "unspecified";
17211
+ const issues = [];
17212
+ if (!androidOrientationValues[orientation].includes(value)) {
17213
+ issues.push({
17214
+ level: "error",
17215
+ message: `Android launcher activity android:screenOrientation="${value}" does not match mpgd.game.json native.orientation "${orientation}".`
17216
+ });
17217
+ }
17218
+ const application = /<application\b[^>]*?>/u.exec(maskXmlComments(manifest))?.[0] ?? "";
17219
+ if (orientation !== "any" && readXmlAttribute(application, "android:appCategory") !== "game") {
17220
+ issues.push({
17221
+ level: "warning",
17222
+ 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.'
17223
+ });
17224
+ }
17225
+ return issues;
17226
+ }
17227
+ function findAndroidLauncherActivity(manifest) {
17228
+ const elements2 = readAndroidActivityElements(manifest, "activity");
17229
+ const direct = elements2.find((element) => element.launcher);
17230
+ if (direct !== void 0) {
17231
+ return { start: direct.start, tag: direct.tag };
17232
+ }
17233
+ const alias = readAndroidActivityElements(manifest, "activity-alias").find((element) => element.launcher);
17234
+ const target2 = alias === void 0 ? void 0 : readXmlAttribute(alias.tag, "android:targetActivity");
17235
+ if (target2 === void 0) {
17236
+ return void 0;
17237
+ }
17238
+ 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}`);
17239
+ const match = elements2.filter((element) => sameClass(readXmlAttribute(element.tag, "android:name") ?? ""));
17240
+ return match.length === 1 && match[0] !== void 0 ? { start: match[0].start, tag: match[0].tag } : void 0;
17241
+ }
17242
+ function readAndroidActivityElements(manifest, element) {
17243
+ const masked = maskXmlComments(manifest);
17244
+ const openings = new RegExp(`<${element}(?=[\\s/>])(?:"[^"]*"|'[^']*'|[^>"'])*?(\\/?)>`, "gu");
17245
+ const found = [];
17246
+ for (const opening of masked.matchAll(openings)) {
17247
+ const start = opening.index ?? 0;
17248
+ const tagEnd = start + opening[0].length;
17249
+ const tag = manifest.slice(start, tagEnd);
17250
+ if (opening[1] === "/") {
17251
+ found.push({ start, tag, launcher: false });
17252
+ continue;
17253
+ }
17254
+ const close = masked.indexOf(`</${element}>`, tagEnd);
17255
+ const body = close === -1 ? "" : masked.slice(tagEnd, close);
17256
+ found.push({ start, tag, launcher: /android\.intent\.category\.LAUNCHER/u.test(body) });
17257
+ }
17258
+ return found;
17259
+ }
17260
+ function maskXmlComments(xml) {
17261
+ return xml.replace(/<!--[\s\S]*?-->/gu, (comment) => comment.replace(/[^\n]/gu, " "));
17262
+ }
16855
17263
  function xmlAttribute(name) {
16856
17264
  return new RegExp(`(\\s+)${escapeRegExp(name)}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`, "u");
16857
17265
  }
@@ -16976,164 +17384,525 @@ function findPbxObjectById(project, id) {
16976
17384
  if (marker === null) {
16977
17385
  return void 0;
16978
17386
  }
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) {
17387
+ const body = readBraced(project, project.indexOf("{", marker.index));
17388
+ return { text: body.text, start: body.start };
17389
+ }
17390
+ function readPbxList(project, object, key) {
17391
+ const marker = new RegExp(`\\b${key}\\s*=\\s*\\(`, "u").exec(object.text);
17392
+ if (marker === null) {
17393
+ return void 0;
17394
+ }
17395
+ const start = object.start + marker.index + marker[0].length;
17396
+ let quote = false;
17397
+ for (let index = start; index < project.length; index += 1) {
17398
+ const character = project[index];
17399
+ if (quote) {
17400
+ if (character === "\\") {
17401
+ index += 1;
17402
+ } else if (character === '"') {
17403
+ quote = false;
17404
+ }
17405
+ continue;
17406
+ }
17407
+ if (character === "/" && project[index + 1] === "*") {
17408
+ const close = project.indexOf("*/", index + 2);
17409
+ index = close === -1 ? project.length : close + 1;
17410
+ continue;
17411
+ }
17412
+ if (character === '"') {
17413
+ quote = true;
17414
+ } else if (character === ")") {
17415
+ const text = project.slice(start, index);
17416
+ const ids = text.replace(/\/\*[^]*?\*\//gu, " ").split(",").map((entry) => /^\s*([A-F0-9]+)\b/u.exec(entry)?.[1]).filter((id) => id !== void 0);
17417
+ return { text, ids, end: index };
17418
+ }
17419
+ }
17420
+ return void 0;
17421
+ }
17422
+ var admobCapacitorPlugin = "@capacitor-community/admob";
17423
+ var androidAdIdPermission = "com.google.android.gms.permission.AD_ID";
17424
+ function shellUsesAdMob(shellPackage) {
17425
+ if (!isRecord5(shellPackage)) {
17426
+ return false;
17427
+ }
17428
+ return [shellPackage.dependencies, shellPackage.devDependencies].some((dependencies) => isRecord5(dependencies) && dependencies[admobCapacitorPlugin] !== void 0);
17429
+ }
17430
+ function adIdPermissions(manifest) {
17431
+ const masked = maskXmlComments(manifest);
17432
+ return [...masked.matchAll(/<uses-permission(?=[\s/>])[^>]*?\/?>(?:\s*<\/uses-permission>)?/gu)].filter((match) => readXmlAttribute(match[0], "android:name") === androidAdIdPermission);
17433
+ }
17434
+ function readAndroidAdIdDeclaration(manifest) {
17435
+ const permissions = adIdPermissions(manifest);
17436
+ if (permissions.length === 0) {
17437
+ return "absent";
17438
+ }
17439
+ return permissions.some((match) => readXmlAttribute(match[0], "tools:node") === "remove") ? "removed" : "declared";
17440
+ }
17441
+ function readGameAdvertisingIdPolicy(gameConfig) {
17442
+ const ads = isRecord5(gameConfig) ? gameConfig.ads : void 0;
17443
+ const admob = isRecord5(ads) ? ads.admob : void 0;
17444
+ const value = isRecord5(admob) ? admob.advertisingId : void 0;
17445
+ if (value === void 0 || value === "declared") {
17446
+ return "declared";
17447
+ }
17448
+ if (value === "removed") {
17449
+ return "removed";
17450
+ }
17451
+ throw new Error("mpgd.game.json ads.admob.advertisingId must be 'declared' or 'removed'.");
17452
+ }
17453
+ function inspectAndroidManifestAds(manifest, usesAds, advertisingIdPolicy = "declared") {
17454
+ const issues = [];
17455
+ const declaration = readAndroidAdIdDeclaration(manifest);
17456
+ if (usesAds) {
17457
+ const hasAppId = [...maskXmlComments(manifest).matchAll(/<meta-data\b[^>]*?\/?>/gu)].some((match) => readXmlAttribute(match[0], "android:name") === "com.google.android.gms.ads.APPLICATION_ID");
17458
+ if (!hasAppId) {
17459
+ issues.push({
17460
+ level: "error",
17461
+ 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."
17462
+ });
17463
+ }
17464
+ if (declaration === "removed" && advertisingIdPolicy !== "removed") {
17465
+ issues.push({
17466
+ level: "error",
17467
+ 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.`
17468
+ });
17469
+ }
17470
+ if (declaration !== "removed" && advertisingIdPolicy === "removed") {
17471
+ issues.push({
17472
+ level: "error",
17473
+ 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" />.`
17474
+ });
17475
+ }
17476
+ } else if (declaration !== "removed") {
17477
+ issues.push({
17478
+ level: "warning",
17479
+ 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" />.`
17480
+ });
17481
+ }
17482
+ return issues;
17483
+ }
17484
+ function inspectIosInfoPlistAds(plist, usesAds) {
17485
+ if (!usesAds) {
17486
+ return [];
17487
+ }
17488
+ const issues = [];
17489
+ if (readPlistTopLevelValue(plist, "GADApplicationIdentifier") === void 0) {
17490
+ issues.push({
17491
+ level: "error",
17492
+ message: "The shell installs AdMob but Info.plist has no GADApplicationIdentifier; the Google Mobile Ads SDK stops the app at launch."
17493
+ });
17494
+ }
17495
+ const networks = readPlistTopLevelValue(plist, "SKAdNetworkItems");
17496
+ if (networks === void 0 || !/cstr6suwn9\.skadnetwork/u.test(networks.xml)) {
17497
+ issues.push({
17498
+ level: "warning",
17499
+ message: "Info.plist SKAdNetworkItems does not list Google's cstr6suwn9.skadnetwork; copy the current list from https://developers.google.com/admob/ios/3p-skadnetworks."
17500
+ });
17501
+ }
17502
+ return issues;
17503
+ }
17504
+ var iosPushEntitlementsFile = "App.entitlements";
17505
+ var iosPushEntitlementsSetting = `App/${iosPushEntitlementsFile}`;
17506
+ var iosPushEntitlementsXml = [
17507
+ '<?xml version="1.0" encoding="UTF-8"?>',
17508
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
17509
+ '<plist version="1.0">',
17510
+ "<dict>",
17511
+ " <key>aps-environment</key>",
17512
+ " <string>development</string>",
17513
+ "</dict>",
17514
+ "</plist>",
17515
+ ""
17516
+ ].join("\n");
17517
+ function shellUsesFirebaseMessaging(shellPackage) {
17518
+ if (!isRecord5(shellPackage)) {
17519
+ return false;
17520
+ }
17521
+ return [shellPackage.dependencies, shellPackage.devDependencies].some((dependencies) => isRecord5(dependencies) && dependencies[notificationsMessagingPlugin] !== void 0);
17522
+ }
17523
+ function stripSwiftCommentsAndStrings(source) {
17524
+ let output = "";
17525
+ let commentDepth = 0;
17526
+ let lineComment = false;
17527
+ let closing;
17528
+ for (let index = 0; index < source.length; index += 1) {
17529
+ const character = source[index] ?? "";
17530
+ const next = source[index + 1] ?? "";
17531
+ const blank = character === "\n" ? "\n" : " ";
17532
+ if (lineComment) {
17533
+ lineComment = character !== "\n";
17534
+ output += blank;
17535
+ } else if (commentDepth > 0) {
17536
+ if (character === "/" && next === "*") {
17537
+ commentDepth += 1;
17538
+ output += " ";
17539
+ index += 1;
17540
+ } else if (character === "*" && next === "/") {
17541
+ commentDepth -= 1;
17542
+ output += " ";
17543
+ index += 1;
17544
+ } else {
17545
+ output += blank;
17546
+ }
17547
+ } else if (closing !== void 0) {
17548
+ if (!closing.raw && character === "\\" && next !== "" && next !== "\n") {
17549
+ output += " ";
17550
+ index += 1;
17551
+ } else if (source.startsWith(closing.text, index)) {
17552
+ output += " ".repeat(closing.text.length);
17553
+ index += closing.text.length - 1;
17554
+ closing = void 0;
17555
+ } else {
17556
+ if (character === "\n" && !closing.multiline) {
17557
+ closing = void 0;
17558
+ }
17559
+ output += blank;
17560
+ }
17561
+ } else if (character === "/" && next === "/") {
17562
+ lineComment = true;
17563
+ output += " ";
17564
+ index += 1;
17565
+ } else if (character === "/" && next === "*") {
17566
+ commentDepth = 1;
17567
+ output += " ";
17568
+ index += 1;
17569
+ } else {
17570
+ const opener = /^(#*)("""|")/u.exec(source.slice(index, index + 64));
17571
+ if (opener === null || opener[1] === "" && character !== '"') {
17572
+ output += character;
17573
+ continue;
17574
+ }
17575
+ const hashes = opener[1] ?? "";
17576
+ const quote = opener[2] ?? '"';
17577
+ closing = { text: `${quote}${hashes}`, multiline: quote === '"""', raw: hashes !== "" };
17578
+ output += " ".repeat(opener[0].length);
17579
+ index += opener[0].length - 1;
17580
+ }
17581
+ }
17582
+ return output;
17583
+ }
17584
+ function swiftFunctionBodies(code, label) {
17585
+ const bodies = [];
17586
+ for (const match of code.matchAll(new RegExp(`\\bfunc\\b[^{}]*\\b${label}\\b[^{}]*\\{`, "gu"))) {
17587
+ const open2 = (match.index ?? 0) + match[0].length - 1;
17588
+ let depth = 0;
17589
+ for (let index = open2; index < code.length; index += 1) {
17590
+ if (code[index] === "{") {
17591
+ depth += 1;
17592
+ } else if (code[index] === "}") {
17593
+ depth -= 1;
17594
+ if (depth === 0) {
17595
+ bodies.push(code.slice(open2 + 1, index));
17596
+ break;
17597
+ }
17598
+ }
17599
+ }
17600
+ }
17601
+ return bodies;
17602
+ }
17603
+ function swiftForwardsPushToken(source) {
17604
+ return swiftFunctionBodies(stripSwiftCommentsAndStrings(source), "didRegisterForRemoteNotificationsWithDeviceToken").some((body) => /\bcapacitorDidRegisterForRemoteNotifications\b|\.\s*apnsToken\s*=/u.test(body));
17605
+ }
17606
+ function swiftDeclaresSceneDelegate(source) {
17607
+ return /\bclass\s+\w+\s*:[^{]*\bUIWindowSceneDelegate\b/u.test(stripSwiftCommentsAndStrings(source));
17608
+ }
17609
+ function swiftForwardsColdStartNotification(source) {
17610
+ return /\.\s*notificationResponse\b/u.test(stripSwiftCommentsAndStrings(source));
17611
+ }
17612
+ function readPbxSetting(settings, name) {
17613
+ const value = new RegExp(`(?:^|[\\s;{])${name}\\s*=\\s*([^;]+);`, "u").exec(settings)?.[1];
17614
+ return value?.trim().replace(/^"(.*)"$/u, "$1");
17615
+ }
17616
+ function resolveIosEntitlementsSetting(value) {
17617
+ const relative14 = value.replace(/^\$[({](?:SRCROOT|PROJECT_DIR)[)}]\//u, "");
17618
+ return relative14.includes("$") || relative14 === "" ? void 0 : relative14;
17619
+ }
17620
+ var iosPushEntitlementStep = `add the Push Notifications capability to the App target in Xcode, or create ios/App/App/${iosPushEntitlementsFile} with <key>aps-environment</key><string>development</string> and set CODE_SIGN_ENTITLEMENTS = ${iosPushEntitlementsSetting} in the App target's Debug and Release build settings (Xcode signs the value from the provisioning profile, production for the App Store)`;
17621
+ var iosPushTokenForwardingStep = "add to the AppDelegate class: func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: deviceToken) } and func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error) }";
17622
+ var iosPushColdStartStep = "copy forwardColdStartNotification and deliverColdStartNotification from the mpgd-kit reference shell (apps/mobile-capacitor/ios/App/App/SceneDelegate.swift) into SceneDelegate, import UserNotifications, and call forwardColdStartNotification(connectionOptions.notificationResponse) right after SceneDelegateProxy.shared.scene(scene, willConnectTo: session, options: connectionOptions)";
17623
+ function inspectIosPushWiring(input) {
17624
+ const issues = [];
17625
+ const guide = " (see the mpgd-kit Firebase guide, docs/FIREBASE_NOTIFICATIONS.md#4-ios).";
17626
+ const entitlement = inspectIosPushEntitlement(input, guide);
17627
+ if (entitlement !== void 0) {
17628
+ issues.push(entitlement);
17629
+ }
17630
+ if (!input.swiftSources.some(swiftForwardsPushToken)) {
17631
+ issues.push({
17632
+ level: "error",
17633
+ message: `The shell installs @capacitor-firebase/messaging, but no App Swift source forwards the APNs device token to Capacitor, so the plugin never gets an FCM token: ${iosPushTokenForwardingStep}${guide}`
17634
+ });
17635
+ }
17636
+ if (input.swiftSources.some(swiftDeclaresSceneDelegate) && !input.swiftSources.some(swiftForwardsColdStartNotification)) {
17637
+ issues.push({
17638
+ level: "error",
17639
+ message: `The shell installs @capacitor-firebase/messaging, but its iOS SceneDelegate does not forward connectionOptions.notificationResponse, so a tap on a notification while the app is not running opens the game without the notification's route: ${iosPushColdStartStep}${guide}`
17640
+ });
17641
+ }
17642
+ return issues;
17643
+ }
17644
+ function inspectIosPushEntitlement(input, guide) {
17645
+ const unverifiable = (reason) => ({
17646
+ level: "warning",
17647
+ message: `The kit cannot verify the iOS aps-environment entitlement: ${reason}; make sure the App target's Release signing includes it${guide}`
17648
+ });
17649
+ let release;
17650
+ try {
17651
+ release = readIosAppReleaseSigning(input.project);
17652
+ } catch {
17653
+ return unverifiable("the Xcode project could not be read");
17654
+ }
17655
+ if (release.conditional) {
17656
+ return unverifiable("CODE_SIGN_ENTITLEMENTS is set per SDK");
17657
+ }
17658
+ if (release.entitlements === void 0 && release.baseConfiguration) {
17659
+ return unverifiable("the Release configuration has a base xcconfig that may set CODE_SIGN_ENTITLEMENTS");
17660
+ }
17661
+ const relative14 = release.entitlements === void 0 ? void 0 : resolveIosEntitlementsSetting(release.entitlements);
17662
+ if (release.entitlements !== void 0 && relative14 === void 0) {
17663
+ return unverifiable(`CODE_SIGN_ENTITLEMENTS is ${release.entitlements}`);
17664
+ }
17665
+ const entitlements = relative14 === void 0 ? void 0 : input.readProjectFile(relative14);
17666
+ let value;
17667
+ try {
17668
+ value = entitlements === void 0 ? void 0 : readPlistTopLevelValue(entitlements, "aps-environment")?.xml;
17669
+ } catch {
17670
+ value = void 0;
17671
+ }
17672
+ if (value !== void 0 && /^<string>\s*(?:development|production)\s*<\/string>$/u.test(value)) {
16985
17673
  return void 0;
16986
17674
  }
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 === "\\") {
16993
- index += 1;
16994
- } else if (character === '"') {
16995
- quote = false;
17675
+ if (value !== void 0 && /^<string>[^<]*\$[({]/u.test(value)) {
17676
+ return unverifiable(`aps-environment is ${value}`);
17677
+ }
17678
+ return {
17679
+ level: "error",
17680
+ message: `The shell installs @capacitor-firebase/messaging, but the iOS App target has no aps-environment entitlement in its Release configuration${release.entitlements === void 0 ? "" : ` (${release.entitlements})`}, so APNs issues no device token and no push reaches the app: ${iosPushEntitlementStep}${guide}`
17681
+ };
17682
+ }
17683
+ function readIosAppReleaseSigning(project) {
17684
+ const projectObject = findPbxObject(project, (body) => /\bisa\s*=\s*PBXProject\s*;/u.test(body));
17685
+ const projectListId = /\bbuildConfigurationList\s*=\s*([A-F0-9]+)\b/u.exec(projectObject?.text ?? "")?.[1];
17686
+ const projectList = projectListId === void 0 ? void 0 : findPbxObjectById(project, projectListId);
17687
+ const levels = [
17688
+ readAppTargetConfigurationIds(project),
17689
+ projectList === void 0 ? [] : readPbxList(project, projectList, "buildConfigurations")?.ids ?? []
17690
+ ];
17691
+ let baseConfiguration = false;
17692
+ for (const ids of levels) {
17693
+ for (const id of ids) {
17694
+ const settings = locatePbxBuildSettings(project, id);
17695
+ if (settings.name !== "Release") {
17696
+ continue;
16996
17697
  }
16997
- continue;
16998
- }
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 };
17698
+ const body = project.slice(settings.start, settings.end);
17699
+ const entitlements = readPbxSetting(body, "CODE_SIGN_ENTITLEMENTS");
17700
+ const conditional = /\bCODE_SIGN_ENTITLEMENTS\s*\[/u.test(body);
17701
+ if (entitlements !== void 0 || conditional) {
17702
+ return baseConfiguration ? { entitlements: void 0, conditional: false, baseConfiguration } : { entitlements, conditional, baseConfiguration };
17703
+ }
17704
+ baseConfiguration ||= /\bbaseConfigurationReference\s*=/u.test(findPbxObjectById(project, id)?.text.replace(body, "") ?? "");
17010
17705
  }
17011
17706
  }
17012
- return void 0;
17707
+ return { entitlements: void 0, conditional: false, baseConfiguration };
17013
17708
  }
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;
17709
+ var iosFirebaseConfigFile = "GoogleService-Info.plist";
17710
+ function inspectIosFirebaseConfig(project) {
17711
+ if (iosAppBundlesResource(project, iosFirebaseConfigFile)) {
17712
+ return [];
17019
17713
  }
17020
- return [shellPackage.dependencies, shellPackage.devDependencies].some((dependencies) => isRecord5(dependencies) && dependencies[admobCapacitorPlugin] !== void 0);
17714
+ return [{
17715
+ level: "warning",
17716
+ message: `The shell installs @capacitor-firebase/messaging, but the iOS App target does not bundle ${iosFirebaseConfigFile}; the plugin then leaves Firebase unconfigured and push fails closed. Download it from the Firebase console and add it to the App target in Xcode.`
17717
+ }];
17021
17718
  }
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);
17719
+
17720
+ // node_modules/.cache/mpgd-native-build/packages/cli/src/crash-reporting-starter.js
17721
+ var crashReportingPlugin = "@capacitor-firebase/crashlytics";
17722
+ var crashlyticsGradlePluginVersion = "3.0.8";
17723
+ var crashlyticsGradleClasspath = `com.google.firebase:firebase-crashlytics-gradle:${crashlyticsGradlePluginVersion}`;
17724
+ var crashlyticsGradlePluginId = "com.google.firebase.crashlytics";
17725
+ var androidRootGradle = "android/build.gradle";
17726
+ var androidAppGradle = "android/app/build.gradle";
17727
+ var crashReportingAndroidSteps = Object.freeze([
17728
+ `In android/build.gradle, add classpath '${crashlyticsGradleClasspath}' to buildscript.dependencies, next to the google-services classpath (4.4.1 or later).`,
17729
+ `In android/app/build.gradle, add apply plugin: '${crashlyticsGradlePluginId}' right after apply plugin: 'com.google.gms.google-services', inside the same google-services.json check.`
17730
+ ]);
17731
+ var crashReportingIosSteps = Object.freeze([
17732
+ 'Upload dSYMs for every App Store build: add the Crashlytics run script build phase ("${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run") as the last phase of the App target, or run upload-symbols after each archive; see docs/CRASH_REPORTING.md in mpgd-kit.'
17733
+ ]);
17734
+ function androidShellHasCrashlytics(shellRoot) {
17735
+ const read = (relative14) => {
17736
+ const file = path7.join(shellRoot, relative14);
17737
+ return existsSync19(file) && lstatSync12(file).isFile() ? readFileSync20(file, "utf8") : void 0;
17738
+ };
17739
+ const root = read(androidRootGradle);
17740
+ const app = read(androidAppGradle);
17741
+ return root !== void 0 && app !== void 0 && androidRootGradleHasCrashlytics(root) && androidAppGradleHasCrashlytics(app);
17025
17742
  }
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";
17743
+ function androidRootGradleHasCrashlytics(source) {
17744
+ return /['"]com\.google\.firebase:firebase-crashlytics-gradle:[^'"]+['"]|\bid\s*\(?\s*['"]com\.google\.firebase\.crashlytics['"]/u.test(stripGradleComments(source));
17032
17745
  }
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";
17746
+ function androidAppGradleHasCrashlytics(source) {
17747
+ const clean = stripGradleComments(source);
17748
+ if (/\bapply\s+plugin\s*:\s*['"]com\.google\.firebase\.crashlytics['"]/u.test(clean)) {
17749
+ return true;
17039
17750
  }
17040
- if (value === "removed") {
17041
- return "removed";
17751
+ return clean.split("\n").some((line) => /\bid\s*\(?\s*['"]com\.google\.firebase\.crashlytics['"]/u.test(line) && !/\bapply\s*\(?\s*false\b/u.test(line));
17752
+ }
17753
+ function shellUsesCrashlytics(shellPackage) {
17754
+ if (typeof shellPackage !== "object" || shellPackage === null) {
17755
+ return false;
17042
17756
  }
17043
- throw new Error("mpgd.game.json ads.admob.advertisingId must be 'declared' or 'removed'.");
17757
+ const { dependencies, devDependencies } = shellPackage;
17758
+ return [dependencies, devDependencies].some((entries) => typeof entries === "object" && entries !== null && entries[crashReportingPlugin] !== void 0);
17044
17759
  }
17045
- function inspectAndroidManifestAds(manifest, usesAds, advertisingIdPolicy = "declared") {
17760
+ function inspectCrashReportingShell(input) {
17761
+ if (!shellUsesCrashlytics(input.shellPackage)) {
17762
+ return [];
17763
+ }
17046
17764
  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
- });
17055
- }
17056
- if (declaration === "removed" && advertisingIdPolicy !== "removed") {
17765
+ if (input.platform === "android") {
17766
+ const config2 = path7.join(input.shellApp, "android/app/google-services.json");
17767
+ if (!existsSync19(config2) || !lstatSync12(config2).isFile() || readFileSync20(config2, "utf8").trim() === "") {
17057
17768
  issues.push({
17058
17769
  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.`
17770
+ message: `The shell installs ${crashReportingPlugin}, but android/app/google-services.json is missing; Firebase does not start and the release reports no crashes. Download it from the Firebase console (the same file notifications use).`
17060
17771
  });
17061
17772
  }
17062
- if (declaration !== "removed" && advertisingIdPolicy === "removed") {
17773
+ if (!androidShellHasCrashlytics(input.shellApp)) {
17063
17774
  issues.push({
17064
17775
  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" />.`
17776
+ message: `The shell installs ${crashReportingPlugin}, but the Android project does not apply the Crashlytics Gradle plugin; the Crashlytics SDK stops the app at startup without the build ID it writes. ${crashReportingAndroidSteps.join(" ")}`
17066
17777
  });
17067
17778
  }
17068
- } else if (declaration !== "removed") {
17779
+ return issues;
17780
+ }
17781
+ if (input.iosProject !== void 0 && !iosAppBundlesResource(input.iosProject, "GoogleService-Info.plist")) {
17069
17782
  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" />.`
17783
+ level: "error",
17784
+ message: `The shell installs ${crashReportingPlugin}, but the iOS App target does not bundle GoogleService-Info.plist; Firebase does not start and the plugin still calls Crashlytics. Download it from the Firebase console and add it to the App target in Xcode.`
17072
17785
  });
17073
17786
  }
17074
17787
  return issues;
17075
17788
  }
17076
- function inspectIosInfoPlistAds(plist, usesAds) {
17077
- if (!usesAds) {
17078
- return [];
17789
+
17790
+ // node_modules/.cache/mpgd-native-build/packages/cli/src/native-shell-ios-push-files.js
17791
+ import { existsSync as existsSync20, lstatSync as lstatSync13, readdirSync as readdirSync6, readFileSync as readFileSync21 } from "node:fs";
17792
+ import path8 from "node:path";
17793
+ var maxSwiftSources = 2e3;
17794
+ function readIosPushWiringInput(iosProjectDirectory) {
17795
+ const project = readContainedFile(iosProjectDirectory, "App.xcodeproj/project.pbxproj");
17796
+ if (project === void 0) {
17797
+ return void 0;
17079
17798
  }
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
- });
17799
+ return {
17800
+ project,
17801
+ readProjectFile: (relative14) => readContainedFile(iosProjectDirectory, relative14),
17802
+ swiftSources: listContainedSwiftFiles(iosProjectDirectory, "App").map((relative14) => readContainedFile(iosProjectDirectory, relative14) ?? "")
17803
+ };
17804
+ }
17805
+ function readContainedFile(root, relative14) {
17806
+ const file = path8.resolve(root, relative14);
17807
+ const within = path8.relative(root, file);
17808
+ if (within === "" || within.startsWith("..") || path8.isAbsolute(within)) {
17809
+ return void 0;
17086
17810
  }
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
- });
17811
+ let current = root;
17812
+ for (const segment of within.split(path8.sep)) {
17813
+ current = path8.join(current, segment);
17814
+ if (!existsSync20(current) || lstatSync13(current).isSymbolicLink()) {
17815
+ return void 0;
17816
+ }
17093
17817
  }
17094
- return issues;
17818
+ return lstatSync13(file).isFile() ? readFileSync21(file, "utf8") : void 0;
17819
+ }
17820
+ function listContainedSwiftFiles(root, relativeRoot) {
17821
+ const pending = [relativeRoot];
17822
+ const files = [];
17823
+ while (pending.length > 0) {
17824
+ const relative14 = pending.pop() ?? "";
17825
+ const full = path8.join(root, relative14);
17826
+ if (!existsSync20(full) || lstatSync13(full).isSymbolicLink() || !lstatSync13(full).isDirectory()) {
17827
+ continue;
17828
+ }
17829
+ for (const entry of readdirSync6(full, { withFileTypes: true })) {
17830
+ const child = path8.join(relative14, entry.name);
17831
+ if (entry.isDirectory() && child !== path8.join(relativeRoot, "public")) {
17832
+ pending.push(child);
17833
+ } else if (entry.isFile() && entry.name.endsWith(".swift")) {
17834
+ files.push(child);
17835
+ if (files.length >= maxSwiftSources) {
17836
+ return files.sort();
17837
+ }
17838
+ }
17839
+ }
17840
+ }
17841
+ return files.sort();
17095
17842
  }
17096
17843
 
17097
17844
  // node_modules/.cache/mpgd-native-build/tools/target/native-store-policy.js
17098
17845
  function inspectNativeStorePolicy(input) {
17099
17846
  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;
17847
+ const gameConfigJson = gameConfig !== void 0 && existsSync21(gameConfig) ? readJson2(gameConfig) : void 0;
17101
17848
  const orientation = readGameNativeOrientation(gameConfigJson);
17102
17849
  const shellPackage = join13(input.shellApp, "package.json");
17103
- const usesAds = existsSync19(shellPackage) && shellUsesAdMob(readJson2(shellPackage));
17850
+ const shellPackageJson = existsSync21(shellPackage) ? readJson2(shellPackage) : void 0;
17851
+ const usesAds = shellUsesAdMob(shellPackageJson);
17852
+ const usesPush = shellUsesFirebaseMessaging(shellPackageJson);
17104
17853
  if (input.platform === "ios") {
17105
17854
  const project = join13(input.shellApp, "ios/App/App.xcodeproj/project.pbxproj");
17106
- if (!existsSync19(project)) {
17855
+ if (!existsSync21(project)) {
17107
17856
  return [];
17108
17857
  }
17109
- const projectSource = readFileSync20(project, "utf8");
17858
+ const projectSource = readFileSync22(project, "utf8");
17110
17859
  const deviceFamily = readIosDeviceFamily(input.metadata);
17111
17860
  const issues = [
17112
17861
  ...inspectIosProjectDeviceFamily(projectSource, deviceFamily),
17113
17862
  ...inspectIosAppPrivacyManifest(projectSource)
17114
17863
  ];
17864
+ const pushInput = usesPush ? readIosPushWiringInput(dirname9(dirname9(project))) : void 0;
17865
+ if (pushInput !== void 0) {
17866
+ issues.push(...inspectIosPushWiring(pushInput), ...inspectIosFirebaseConfig(projectSource));
17867
+ }
17868
+ issues.push(...inspectCrashReportingShell({
17869
+ platform: "ios",
17870
+ shellApp: input.shellApp,
17871
+ shellPackage: shellPackageJson,
17872
+ iosProject: projectSource
17873
+ }));
17115
17874
  const plist = join13(dirname9(dirname9(project)), readIosReleaseInfoPlist(projectSource));
17116
- if (existsSync19(plist)) {
17117
- const plistSource = readFileSync20(plist, "utf8");
17875
+ if (existsSync21(plist)) {
17876
+ const plistSource = readFileSync22(plist, "utf8");
17118
17877
  issues.push(...inspectIosInfoPlistStorePolicy(plistSource), ...inspectIosInfoPlistOrientation(plistSource, orientation, deviceFamily ?? "universal"), ...inspectIosInfoPlistAds(plistSource, usesAds));
17119
17878
  }
17120
17879
  return issues;
17121
17880
  }
17122
17881
  const manifest = join13(input.shellApp, "android/app/src/main/AndroidManifest.xml");
17123
- if (!existsSync19(manifest)) {
17882
+ if (!existsSync21(manifest)) {
17124
17883
  return [];
17125
17884
  }
17126
- const manifestSource = readFileSync20(manifest, "utf8");
17885
+ const manifestSource = readFileSync22(manifest, "utf8");
17127
17886
  const releaseManifest = join13(input.shellApp, "android/app/src/release/AndroidManifest.xml");
17128
- const releaseSource = existsSync19(releaseManifest) ? readFileSync20(releaseManifest, "utf8") : "";
17887
+ const releaseSource = existsSync21(releaseManifest) ? readFileSync22(releaseManifest, "utf8") : "";
17129
17888
  return [
17130
17889
  ...inspectAndroidManifestOrientation(manifestSource, orientation),
17131
17890
  ...inspectAndroidManifestAds(`${manifestSource}
17132
- ${releaseSource}`, usesAds, usesAds ? readGameAdvertisingIdPolicy(gameConfigJson) : "declared")
17891
+ ${releaseSource}`, usesAds, usesAds ? readGameAdvertisingIdPolicy(gameConfigJson) : "declared"),
17892
+ // `--providers crash-reporting`: the Firebase config file and the Gradle plugin.
17893
+ ...inspectCrashReportingShell({
17894
+ platform: "android",
17895
+ shellApp: input.shellApp,
17896
+ shellPackage: shellPackageJson
17897
+ })
17133
17898
  ];
17134
17899
  }
17900
+ function shellInstallsFirebaseMessaging(shellApp) {
17901
+ const shellPackage = join13(shellApp, "package.json");
17902
+ return existsSync21(shellPackage) && shellUsesFirebaseMessaging(readJson2(shellPackage));
17903
+ }
17135
17904
  function readJson2(file) {
17136
- const source = readFileSync20(file, "utf8");
17905
+ const source = readFileSync22(file, "utf8");
17137
17906
  try {
17138
17907
  return JSON.parse(source);
17139
17908
  } catch (error) {
@@ -17153,14 +17922,14 @@ ${errors.map((issue) => `- ${issue.message}`).join("\n")}`);
17153
17922
 
17154
17923
  // node_modules/.cache/mpgd-native-build/tools/target/native-ios-inspection.js
17155
17924
  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";
17925
+ import { existsSync as existsSync22, mkdtempSync as mkdtempSync3, readFileSync as readFileSync23, rmSync as rmSync10, statSync as statSync4 } from "node:fs";
17157
17926
  import { tmpdir as tmpdir3 } from "node:os";
17158
- import path7 from "node:path";
17927
+ import path9 from "node:path";
17159
17928
  function inspectSignedIosArchive(archive, input) {
17160
- return inspectSignedIosApplication(path7.join(archive, "Products/Applications/App.app"), input);
17929
+ return inspectSignedIosApplication(path9.join(archive, "Products/Applications/App.app"), input);
17161
17930
  }
17162
17931
  function inspectSignedIosIpa(ipa, input) {
17163
- if (!existsSync20(ipa) || !statSync4(ipa).isFile()) {
17932
+ if (!existsSync22(ipa) || !statSync4(ipa).isFile()) {
17164
17933
  throw new Error("iOS store IPA is missing.");
17165
17934
  }
17166
17935
  const runner = input.runner ?? { run: runCommand2 };
@@ -17176,530 +17945,190 @@ function inspectSignedIosIpa(ipa, input) {
17176
17945
  if (appManifests.length !== 1) {
17177
17946
  throw new Error("iOS store IPA must contain exactly one app manifest.");
17178
17947
  }
17179
- const appRelative = path7.dirname(appManifests[0] ?? "");
17180
- const extracted = mkdtempSync3(path7.join(tmpdir3(), "mpgd-ios-inspection-"));
17948
+ const appRelative = path9.dirname(appManifests[0] ?? "");
17949
+ const extracted = mkdtempSync3(path9.join(tmpdir3(), "mpgd-ios-inspection-"));
17181
17950
  try {
17182
17951
  const result = runner.run("unzip", ["-q", ipa, "-d", extracted]);
17183
17952
  if (result.status !== 0) {
17184
17953
  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 });
17189
- }
17190
- }
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.");
17195
- }
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.");
17198
- }
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.");
17203
- }
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.");
17210
- }
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 };
17236
- }
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.");
17247
- }
17248
- }
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}.`);
17257
- }
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.");
17954
+ }
17955
+ return inspectSignedIosApplication(path9.join(extracted, appRelative), input);
17956
+ } finally {
17957
+ rmSync10(extracted, { recursive: true, force: true });
17311
17958
  }
17312
- return { teamId, archiveBuildSettings, exportOptionsPlist };
17313
17959
  }
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")
17960
+ function inspectSignedIosApplication(appPath, input) {
17961
+ const plist = path9.join(appPath, "Info.plist");
17962
+ if (!existsSync22(plist) || !statSync4(plist).isFile()) {
17963
+ throw new Error("Signed iOS app manifest is missing.");
17964
+ }
17965
+ if (!existsSync22(path9.join(appPath, "Assets.car")) || existsSync22(path9.join(appPath, "Info-Smoke.plist"))) {
17966
+ throw new Error("Signed iOS app contains smoke assets or omits release icons.");
17967
+ }
17968
+ const runner = input.runner ?? { run: runCommand2 };
17969
+ const signature = runner.run("codesign", ["--verify", "--deep", "--strict", appPath]);
17970
+ if (signature.status !== 0) {
17971
+ throw new Error("iOS app code signature could not be verified.");
17972
+ }
17973
+ const signingInfo = runner.run("codesign", ["-dv", "--verbose=4", appPath]);
17974
+ const signingText = `${signingInfo.stdout}
17975
+ ${signingInfo.stderr}`;
17976
+ const expectedTeamLine = `TeamIdentifier=${input.expectedTeamId}`;
17977
+ if (signingInfo.status !== 0 || /Signature=adhoc/iu.test(signingText) || !signingText.split(/\r?\n/u).some((line) => line.trim() === expectedTeamLine)) {
17978
+ throw new Error("iOS app is not signed for the expected development team.");
17979
+ }
17980
+ if (input.expectedApsEnvironment !== void 0) {
17981
+ const entitlements = runner.run("codesign", ["-d", "--entitlements", "-", "--xml", appPath]);
17982
+ if (entitlements.status !== 0) {
17983
+ throw new Error("iOS app entitlements could not be read.");
17565
17984
  }
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")
17985
+ const problem = iosStorePushEntitlementError(readSignedApsEnvironment(entitlements.stdout));
17986
+ if (problem !== void 0) {
17987
+ throw new Error(problem);
17593
17988
  }
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")
17989
+ }
17990
+ const fields = [
17991
+ ["bundleId", "CFBundleIdentifier", input.expectedBundleId],
17992
+ ["marketingVersion", "CFBundleShortVersionString", input.expectedMarketingVersion],
17993
+ ["buildNumber", "CFBundleVersion", input.expectedBuildNumber]
17994
+ ];
17995
+ const observed = Object.fromEntries(fields.map(([name, key, expected]) => {
17996
+ const result = runner.run("/usr/libexec/PlistBuddy", ["-c", `Print :${key}`, plist]);
17997
+ if (result.status !== 0 || result.stdout.trim() !== expected) {
17998
+ throw new Error(`iOS app ${name} does not match release identity.`);
17628
17999
  }
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")
18000
+ return [name, expected];
18001
+ }));
18002
+ const configFiles = [
18003
+ path9.join(appPath, "capacitor.config.json"),
18004
+ path9.join(appPath, "public/capacitor.config.json")
18005
+ ];
18006
+ if (!configFiles.some((file) => existsSync22(file))) {
18007
+ throw new Error("Signed iOS app Capacitor configuration is missing.");
18008
+ }
18009
+ for (const configFile of configFiles) {
18010
+ if (existsSync22(configFile)) {
18011
+ assertNoDebugServer(configFile);
17652
18012
  }
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."
17699
18013
  }
17700
- });
18014
+ return { ...observed, teamId: input.expectedTeamId, signed: true };
18015
+ }
18016
+ function readSignedApsEnvironment(codesignOutput) {
18017
+ const start = codesignOutput.search(/<\?xml|<plist\b/u);
18018
+ if (start === -1) {
18019
+ return codesignOutput.trim() === "" ? void 0 : null;
18020
+ }
18021
+ let value;
18022
+ try {
18023
+ value = readPlistTopLevelValue(codesignOutput.slice(start), "aps-environment")?.xml;
18024
+ } catch {
18025
+ return null;
18026
+ }
18027
+ if (value === void 0) {
18028
+ return void 0;
18029
+ }
18030
+ return /^<string>\s*([^<]*?)\s*<\/string>$/u.exec(value)?.[1] ?? null;
18031
+ }
18032
+ function iosStorePushEntitlementError(apsEnvironment) {
18033
+ if (apsEnvironment === "production") {
18034
+ return void 0;
18035
+ }
18036
+ const fix = "Enable Push Notifications on the App ID in the Apple Developer account, regenerate the App Store provisioning profile (MPGD_IOS_PROVISIONING_PROFILE_FILE), and rebuild (docs/FIREBASE_NOTIFICATIONS.md#4-ios).";
18037
+ if (apsEnvironment === void 0) {
18038
+ return `The store IPA has no aps-environment entitlement although the shell installs @capacitor-firebase/messaging: the provisioning profile that signed it lacks the Push Notifications capability (the App ID has no push, or the profile was generated before it was enabled), so the app never gets an APNs token. ${fix}`;
18039
+ }
18040
+ if (apsEnvironment === "development") {
18041
+ return `The store IPA is signed with aps-environment = development although it is an App Store build: the export used a development provisioning profile, so its APNs token belongs to the sandbox and production pushes never arrive. Export with the App Store distribution profile. ${fix}`;
18042
+ }
18043
+ return `The store IPA's aps-environment entitlement is ${apsEnvironment === null ? "unreadable" : apsEnvironment}, not production, so production pushes never arrive. ${fix}`;
18044
+ }
18045
+ function assertNoDebugServer(file) {
18046
+ const value = JSON.parse(readFileSync23(file, "utf8"));
18047
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
18048
+ throw new Error("Signed iOS app has malformed Capacitor configuration.");
18049
+ }
18050
+ const config2 = value;
18051
+ const server = config2.server;
18052
+ const ios = config2.ios;
18053
+ if (typeof server === "object" && server !== null && "url" in server || typeof ios === "object" && ios !== null && "webContentsDebuggingEnabled" in ios && ios.webContentsDebuggingEnabled === true) {
18054
+ throw new Error("Signed iOS app retains a live-reload or debug bridge configuration.");
18055
+ }
18056
+ }
18057
+ function runCommand2(command, args) {
18058
+ const result = spawnSync3(command, [...args], {
18059
+ encoding: "utf8",
18060
+ env: nativeInspectionEnvironment(),
18061
+ maxBuffer: 4 * 1024 * 1024
18062
+ });
18063
+ if (result.error !== void 0) {
18064
+ throw new Error(`Required iOS inspection command is unavailable: ${command}.`);
18065
+ }
18066
+ return {
18067
+ status: result.status,
18068
+ stdout: result.stdout,
18069
+ stderr: result.stderr
18070
+ };
18071
+ }
18072
+
18073
+ // node_modules/.cache/mpgd-native-build/tools/target/native-ios-signing.js
18074
+ import { spawnSync as spawnSync4 } from "node:child_process";
18075
+ import { existsSync as existsSync23, statSync as statSync5 } from "node:fs";
18076
+ import path10 from "node:path";
18077
+ function resolveIosSigningPlan(environment, mode) {
18078
+ const teamId = environment.MPGD_IOS_TEAM_ID?.trim();
18079
+ if (teamId === void 0 || !/^[A-Z0-9]{10}$/u.test(teamId)) {
18080
+ throw new Error("Signed iOS builds require a 10-character MPGD_IOS_TEAM_ID.");
18081
+ }
18082
+ const style = environment.MPGD_IOS_SIGNING_STYLE ?? "Automatic";
18083
+ if (style !== "Automatic" && style !== "Manual") {
18084
+ throw new Error("MPGD_IOS_SIGNING_STYLE must be Automatic or Manual.");
18085
+ }
18086
+ const identity = environment.MPGD_IOS_SIGNING_IDENTITY?.trim();
18087
+ const profile2 = environment.MPGD_IOS_PROVISIONING_PROFILE_SPECIFIER?.trim();
18088
+ if (style === "Manual" && (identity === void 0 || identity === "" || profile2 === void 0 || profile2 === "")) {
18089
+ throw new Error("Manual iOS signing requires host identity and provisioning profile names.");
18090
+ }
18091
+ if (style === "Automatic" && profile2 !== void 0 && profile2 !== "") {
18092
+ throw new Error("Automatic iOS signing must not set a manual provisioning profile.");
18093
+ }
18094
+ const archiveBuildSettings = [
18095
+ "CODE_SIGNING_ALLOWED=YES",
18096
+ `CODE_SIGN_STYLE=${style}`,
18097
+ `DEVELOPMENT_TEAM=${teamId}`,
18098
+ ...identity === void 0 || identity === "" ? [] : [`CODE_SIGN_IDENTITY=${identity}`],
18099
+ ...profile2 === void 0 || profile2 === "" ? [] : [`PROVISIONING_PROFILE_SPECIFIER=${profile2}`],
18100
+ ...environment.MPGD_IOS_SIGNING_KEYCHAIN === void 0 ? [] : [`OTHER_CODE_SIGN_FLAGS=--keychain ${JSON.stringify(environment.MPGD_IOS_SIGNING_KEYCHAIN)}`]
18101
+ ];
18102
+ if (mode !== "store-export") {
18103
+ return { teamId, archiveBuildSettings };
18104
+ }
18105
+ const configuredExportFile = environment.MPGD_IOS_EXPORT_OPTIONS_PLIST;
18106
+ if (configuredExportFile === void 0 || configuredExportFile.trim() === "") {
18107
+ throw new Error("iOS store export requires MPGD_IOS_EXPORT_OPTIONS_PLIST.");
18108
+ }
18109
+ const exportOptionsPlist = path10.resolve(configuredExportFile);
18110
+ if (!existsSync23(exportOptionsPlist) || !statSync5(exportOptionsPlist).isFile()) {
18111
+ throw new Error("iOS export options plist must be an existing host file.");
18112
+ }
18113
+ const method = spawnSync4("/usr/libexec/PlistBuddy", ["-c", "Print :method", exportOptionsPlist], {
18114
+ encoding: "utf8",
18115
+ maxBuffer: 1024 * 1024
18116
+ });
18117
+ if (method.error !== void 0 || method.status !== 0 || !["app-store", "app-store-connect"].includes(method.stdout.trim())) {
18118
+ throw new Error("iOS export options must select an App Store distribution method.");
18119
+ }
18120
+ return { teamId, archiveBuildSettings, exportOptionsPlist };
18121
+ }
18122
+
18123
+ // node_modules/.cache/mpgd-native-build/tools/target/native-release-identity.js
18124
+ import { existsSync as existsSync25, readFileSync as readFileSync25, writeFileSync as writeFileSync17 } from "node:fs";
18125
+ import { dirname as dirname10, isAbsolute as isAbsolute11, join as join14, relative as relative12, resolve as resolve17 } from "node:path";
17701
18126
 
17702
18127
  // node_modules/.cache/mpgd-native-build/packages/cli/src/capacitor-shell-starter.js
18128
+ import { chmodSync as chmodSync2, existsSync as existsSync24, lstatSync as lstatSync14, mkdirSync as mkdirSync11, readdirSync as readdirSync7, readFileSync as readFileSync24, realpathSync as realpathSync9, renameSync as renameSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync16 } from "node:fs";
18129
+ import path11 from "node:path";
18130
+ import { DOMParser as DOMParser2 } from "@xmldom/xmldom";
18131
+ import { satisfies as satisfies2, valid as valid2, validRange as validRange2 } from "semver";
17703
18132
  var shellPath = "apps/mobile-capacitor";
17704
18133
  var webPath = `${shellPath}/www`;
17705
18134
  var javaKeywords = new Set(`
@@ -17780,7 +18209,7 @@ var androidDataExtractionRulesXml = [
17780
18209
  ""
17781
18210
  ].join("\n");
17782
18211
  function assertAndroidLauncherClasses(nativeDirectory, gradle) {
17783
- const application = readAndroidApplication(path9.join(nativeDirectory, "app/src/main/AndroidManifest.xml"));
18212
+ const application = readAndroidApplication(path11.join(nativeDirectory, "app/src/main/AndroidManifest.xml"));
17784
18213
  if (application === void 0) {
17785
18214
  throw new Error("Existing android project launcher activity is missing.");
17786
18215
  }
@@ -17788,8 +18217,8 @@ function assertAndroidLauncherClasses(nativeDirectory, gradle) {
17788
18217
  const manifestRoot = application.parentNode;
17789
18218
  const manifestPackage = manifestRoot?.getAttribute("package");
17790
18219
  const namespace = /\bnamespace\s*(?:=\s*)?["']([A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)+)["']/u.exec(stripGradleComments(gradle))?.[1] ?? (manifestPackage === null || manifestPackage === "" ? void 0 : manifestPackage);
17791
- const releaseFile = path9.join(nativeDirectory, "app/src/release/AndroidManifest.xml");
17792
- const release = existsSync22(releaseFile) ? readAndroidApplication(releaseFile, false) : void 0;
18220
+ const releaseFile = path11.join(nativeDirectory, "app/src/release/AndroidManifest.xml");
18221
+ const release = existsSync24(releaseFile) ? readAndroidApplication(releaseFile, false) : void 0;
17793
18222
  if (release !== void 0) {
17794
18223
  assertAndroidLauncherEnabled(release, "Release application");
17795
18224
  }
@@ -17899,7 +18328,7 @@ function resolvesAndroidClass(nativeDirectory, qualified, sourceFiles, visited,
17899
18328
  if (!isNativeFile(nativeDirectory, relative14)) {
17900
18329
  continue;
17901
18330
  }
17902
- const source = stripSourceCommentsAndStrings(readFileSync22(path9.join(nativeDirectory, relative14), "utf8"));
18331
+ const source = stripSourceCommentsAndStrings(readFileSync24(path11.join(nativeDirectory, relative14), "utf8"));
17903
18332
  const declaredPackage = /\bpackage\s+([A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)+)\s*;?/u.exec(source)?.[1];
17904
18333
  if (declaredPackage !== packageName) {
17905
18334
  continue;
@@ -17955,15 +18384,15 @@ function listNativeFiles(nativeDirectory, relativeRoot, extension) {
17955
18384
  const files = [];
17956
18385
  while (pending.length > 0) {
17957
18386
  const relative14 = pending.pop() ?? "";
17958
- const full = path9.join(nativeDirectory, relative14);
17959
- if (!existsSync22(full) || lstatSync12(full).isSymbolicLink()) {
18387
+ const full = path11.join(nativeDirectory, relative14);
18388
+ if (!existsSync24(full) || lstatSync14(full).isSymbolicLink()) {
17960
18389
  continue;
17961
18390
  }
17962
- for (const entry of readdirSync6(full, { withFileTypes: true })) {
18391
+ for (const entry of readdirSync7(full, { withFileTypes: true })) {
17963
18392
  if (entry.isSymbolicLink()) {
17964
18393
  continue;
17965
18394
  }
17966
- const child = path9.join(relative14, entry.name);
18395
+ const child = path11.join(relative14, entry.name);
17967
18396
  if (entry.isDirectory()) {
17968
18397
  pending.push(child);
17969
18398
  } else if (entry.isFile() && entry.name.endsWith(extension)) {
@@ -18169,7 +18598,7 @@ function assertReferencedIosFiles(nativeDirectory, project, infoRelative) {
18169
18598
  throw new Error(`Existing ios project is incomplete; referenced app file ${relative14} is missing.`);
18170
18599
  }
18171
18600
  }
18172
- const info = readFileSync22(path9.join(nativeDirectory, infoRelative), "utf8");
18601
+ const info = readFileSync24(path11.join(nativeDirectory, infoRelative), "utf8");
18173
18602
  assertSceneDelegateFiles(nativeDirectory, info, "Release", compiledSources);
18174
18603
  return compiledSources;
18175
18604
  }
@@ -18300,7 +18729,7 @@ function readPbxGroupPath(project, parents, referenceId) {
18300
18729
  const groupPath = /\bpath\s*=\s*(?:"([^"]+)"|([^;]+))\s*;/u.exec(group);
18301
18730
  const value = (groupPath?.[1] ?? groupPath?.[2])?.trim();
18302
18731
  if (value !== void 0) {
18303
- if (value.includes("$") || path9.isAbsolute(value)) {
18732
+ if (value.includes("$") || path11.isAbsolute(value)) {
18304
18733
  throw new Error("Existing ios project build input group path is unsupported.");
18305
18734
  }
18306
18735
  parts.push(value);
@@ -18322,7 +18751,7 @@ function assertPbxFileReference(nativeDirectory, project, referenceId, parentGro
18322
18751
  }
18323
18752
  const relative14 = /\bpath\s*=\s*(?:"([^"]+)"|([^;]+))\s*;/u.exec(reference);
18324
18753
  const filePath = (relative14?.[1] ?? relative14?.[2])?.trim();
18325
- if (filePath === void 0 || filePath.includes("$") || path9.isAbsolute(filePath)) {
18754
+ if (filePath === void 0 || filePath.includes("$") || path11.isAbsolute(filePath)) {
18326
18755
  throw new Error("Existing ios project App build input path is unsupported.");
18327
18756
  }
18328
18757
  if (filePath === "public" || filePath === "capacitor.config.json") {
@@ -18349,19 +18778,19 @@ function assertPbxFileReference(nativeDirectory, project, referenceId, parentGro
18349
18778
  return [resolved];
18350
18779
  }
18351
18780
  function isNativeEntry(directory, relative14) {
18352
- const file = path9.resolve(directory, relative14);
18353
- const within = path9.relative(directory, file);
18354
- if (within === "" || within.startsWith("..") || path9.isAbsolute(within)) {
18781
+ const file = path11.resolve(directory, relative14);
18782
+ const within = path11.relative(directory, file);
18783
+ if (within === "" || within.startsWith("..") || path11.isAbsolute(within)) {
18355
18784
  return false;
18356
18785
  }
18357
18786
  let current = directory;
18358
- for (const segment of within.split(path9.sep)) {
18359
- current = path9.join(current, segment);
18360
- if (!existsSync22(current) || lstatSync12(current).isSymbolicLink()) {
18787
+ for (const segment of within.split(path11.sep)) {
18788
+ current = path11.join(current, segment);
18789
+ if (!existsSync24(current) || lstatSync14(current).isSymbolicLink()) {
18361
18790
  return false;
18362
18791
  }
18363
18792
  }
18364
- return lstatSync12(file).isFile() || lstatSync12(file).isDirectory();
18793
+ return lstatSync14(file).isFile() || lstatSync14(file).isDirectory();
18365
18794
  }
18366
18795
  function assertSceneDelegateFiles(nativeDirectory, source, label, compiledSources) {
18367
18796
  const values = parsePlistDictionary(source, label);
@@ -18390,7 +18819,7 @@ function assertSceneDelegateFiles(nativeDirectory, source, label, compiledSource
18390
18819
  if (!compiledSources.has(relative14)) {
18391
18820
  return false;
18392
18821
  }
18393
- const text = stripSourceCommentsAndStrings(readFileSync22(path9.join(nativeDirectory, relative14), "utf8"));
18822
+ const text = stripSourceCommentsAndStrings(readFileSync24(path11.join(nativeDirectory, relative14), "utf8"));
18394
18823
  return hasUsableTopLevelSwiftSceneDelegate(text, className);
18395
18824
  })) {
18396
18825
  throw new Error(`Existing ios project ${label} scene delegate ${delegate} is missing from App Sources.`);
@@ -18415,29 +18844,29 @@ function hasUsableTopLevelSwiftSceneDelegate(source, className) {
18415
18844
  return false;
18416
18845
  }
18417
18846
  function assertAndroidManifestResources(nativeDirectory) {
18418
- const manifestFiles = ["main", "release"].map((sourceSet) => path9.join(nativeDirectory, `app/src/${sourceSet}/AndroidManifest.xml`)).filter((file) => existsSync22(file));
18419
- const resourceRoots = ["main", "release"].map((sourceSet) => path9.join(nativeDirectory, `app/src/${sourceSet}/res`));
18847
+ const manifestFiles = ["main", "release"].map((sourceSet) => path11.join(nativeDirectory, `app/src/${sourceSet}/AndroidManifest.xml`)).filter((file) => existsSync24(file));
18848
+ const resourceRoots = ["main", "release"].map((sourceSet) => path11.join(nativeDirectory, `app/src/${sourceSet}/res`));
18420
18849
  const references = manifestFiles.flatMap((file) => {
18421
- const manifest = readFileSync22(file, "utf8").replace(/<!--[\s\S]*?-->/gu, "");
18850
+ const manifest = readFileSync24(file, "utf8").replace(/<!--[\s\S]*?-->/gu, "");
18422
18851
  return [...manifest.matchAll(/(?<![\w+])@([a-z]+)\/([A-Za-z_][A-Za-z0-9_.]*)/gu)];
18423
18852
  });
18424
18853
  for (const reference of references) {
18425
18854
  const type = reference[1] ?? "";
18426
18855
  const name = reference[2] ?? "";
18427
18856
  const found = resourceRoots.some((resourceRoot) => {
18428
- if (!existsSync22(resourceRoot)) {
18857
+ if (!existsSync24(resourceRoot)) {
18429
18858
  return false;
18430
18859
  }
18431
- const folders = readdirSync6(resourceRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
18860
+ const folders = readdirSync7(resourceRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
18432
18861
  return folders.some((folder) => {
18433
18862
  const densityFallback = (type === "mipmap" || type === "drawable") && new RegExp(`^${type}-(?:ldpi|mdpi|hdpi|xhdpi|xxhdpi|xxxhdpi|nodpi|anydpi)$`, "u").test(folder);
18434
18863
  if (folder === type || densityFallback) {
18435
- return readdirSync6(path9.join(resourceRoot, folder)).some((file) => file === `${name}.xml` || file.startsWith(`${name}.`));
18864
+ return readdirSync7(path11.join(resourceRoot, folder)).some((file) => file === `${name}.xml` || file.startsWith(`${name}.`));
18436
18865
  }
18437
18866
  if (folder !== "values") {
18438
18867
  return false;
18439
18868
  }
18440
- return readdirSync6(path9.join(resourceRoot, folder)).some((file) => file.endsWith(".xml") && readAndroidValueResource(path9.join(resourceRoot, folder, file), type, name).length > 0);
18869
+ return readdirSync7(path11.join(resourceRoot, folder)).some((file) => file.endsWith(".xml") && readAndroidValueResource(path11.join(resourceRoot, folder, file), type, name).length > 0);
18441
18870
  });
18442
18871
  });
18443
18872
  if (!found) {
@@ -18450,13 +18879,13 @@ function assertNativeDisplayName(nativeDirectory, platform, expectedDisplayName,
18450
18879
  if (platform === "ios" && !isNativeFile(nativeDirectory, relative14)) {
18451
18880
  throw new Error(`Existing ${platform} project is incomplete; native display name resource is missing.`);
18452
18881
  }
18453
- const source = platform === "ios" ? readFileSync22(path9.join(nativeDirectory, relative14), "utf8") : "";
18882
+ const source = platform === "ios" ? readFileSync24(path11.join(nativeDirectory, relative14), "utf8") : "";
18454
18883
  const labels = platform === "ios" ? [parsePlistDictionary(source, "Release").get("CFBundleDisplayName")].filter((value) => value?.tagName === "string").map((value) => value?.textContent ?? "") : [];
18455
18884
  if (platform === "ios" && (labels.length !== 1 || labels[0] !== expectedDisplayName)) {
18456
18885
  throw new Error(`Existing ${platform} project display name differs or cannot be read safely.`);
18457
18886
  }
18458
18887
  if (platform === "android") {
18459
- const main = readAndroidApplication(path9.join(nativeDirectory, "app/src/main/AndroidManifest.xml"));
18888
+ const main = readAndroidApplication(path11.join(nativeDirectory, "app/src/main/AndroidManifest.xml"));
18460
18889
  if (main === void 0) {
18461
18890
  throw new Error("Existing android project application label is missing.");
18462
18891
  }
@@ -18475,8 +18904,8 @@ function assertNativeDisplayName(nativeDirectory, platform, expectedDisplayName,
18475
18904
  throw new Error("Existing android project launcher label differs from the requested name.");
18476
18905
  }
18477
18906
  }
18478
- const overlayFile = path9.join(nativeDirectory, "app/src/release/AndroidManifest.xml");
18479
- if (existsSync22(overlayFile)) {
18907
+ const overlayFile = path11.join(nativeDirectory, "app/src/release/AndroidManifest.xml");
18908
+ if (existsSync24(overlayFile)) {
18480
18909
  const release = readAndroidApplication(overlayFile, false);
18481
18910
  if (release !== void 0) {
18482
18911
  if (hasAndroidMergerDirective(release)) {
@@ -18486,8 +18915,8 @@ function assertNativeDisplayName(nativeDirectory, platform, expectedDisplayName,
18486
18915
  if (releaseLabel !== null && resolveAndroidLabel(nativeDirectory, releaseLabel) !== expectedDisplayName) {
18487
18916
  throw new Error("Existing android Release application label differs from the requested name.");
18488
18917
  }
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");
18918
+ const appBuildFile = ["app/build.gradle", "app/build.gradle.kts"].map((relative15) => path11.join(nativeDirectory, relative15)).find((file) => existsSync24(file));
18919
+ const appGradle = appBuildFile === void 0 ? "" : readFileSync24(appBuildFile, "utf8");
18491
18920
  const manifestPackage = main.parentNode?.getAttribute("package");
18492
18921
  const namespace = /\bnamespace\s*(?:=\s*)?["']([A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)+)["']/u.exec(stripGradleComments(appGradle))?.[1] ?? (manifestPackage === null || manifestPackage === "" ? void 0 : manifestPackage);
18493
18922
  const launcherNames = /* @__PURE__ */ new Set();
@@ -18523,21 +18952,21 @@ function assertNativeDisplayName(nativeDirectory, platform, expectedDisplayName,
18523
18952
  }
18524
18953
  }
18525
18954
  function assertAndroidReleaseDisplayName(shellApp, expectedDisplayName) {
18526
- const nativeDirectory = path9.join(shellApp, "android");
18955
+ const nativeDirectory = path11.join(shellApp, "android");
18527
18956
  assertAndroidManifestResources(nativeDirectory);
18528
18957
  assertNativeDisplayName(nativeDirectory, "android", expectedDisplayName);
18529
18958
  }
18530
18959
  function assertAndroidReleaseLauncherIntegrity(shellApp) {
18531
- const nativeDirectory = path9.join(shellApp, "android");
18960
+ const nativeDirectory = path11.join(shellApp, "android");
18532
18961
  const gradleRelative = requireOneNativeFile(nativeDirectory, ["app/build.gradle", "app/build.gradle.kts"], "android");
18533
- const gradle = readFileSync22(path9.join(nativeDirectory, gradleRelative), "utf8");
18962
+ const gradle = readFileSync24(path11.join(nativeDirectory, gradleRelative), "utf8");
18534
18963
  assertAndroidLauncherClasses(nativeDirectory, gradle);
18535
18964
  }
18536
18965
  function assertIosReleaseDisplayName(shellApp, expectedDisplayName) {
18537
- const nativeDirectory = path9.join(shellApp, "ios");
18538
- const projectFile = path9.join(nativeDirectory, "App/App.xcodeproj/project.pbxproj");
18539
- const project = readFileSync22(projectFile, "utf8");
18540
- const infoRelative = path9.join("App", readIosReleaseInfoPlist(project));
18966
+ const nativeDirectory = path11.join(shellApp, "ios");
18967
+ const projectFile = path11.join(nativeDirectory, "App/App.xcodeproj/project.pbxproj");
18968
+ const project = readFileSync24(projectFile, "utf8");
18969
+ const infoRelative = path11.join("App", readIosReleaseInfoPlist(project));
18541
18970
  if (!isNativeFile(nativeDirectory, infoRelative)) {
18542
18971
  throw new Error("Existing ios project Release Info.plist is missing or unsafe.");
18543
18972
  }
@@ -18547,20 +18976,20 @@ function assertIosReleaseDisplayName(shellApp, expectedDisplayName) {
18547
18976
  }
18548
18977
  function assertNoIosLocalizedDisplayNameOverride(nativeDirectory, project) {
18549
18978
  const files = listNativeFiles(nativeDirectory, "App/App", ".strings");
18550
- if (/\bInfoPlist\.strings\b/u.test(project) || isNativeFile(nativeDirectory, "App/InfoPlist.strings") || files.some((relative14) => path9.basename(relative14) === "InfoPlist.strings")) {
18979
+ if (/\bInfoPlist\.strings\b/u.test(project) || isNativeFile(nativeDirectory, "App/InfoPlist.strings") || files.some((relative14) => path11.basename(relative14) === "InfoPlist.strings")) {
18551
18980
  throw new Error("Existing ios project localized InfoPlist.strings overrides are unsupported.");
18552
18981
  }
18553
18982
  }
18554
18983
  function assertIosArchiveScheme(shellApp) {
18555
- const nativeDirectory = path9.join(shellApp, "ios");
18984
+ const nativeDirectory = path11.join(shellApp, "ios");
18556
18985
  const projectRelative = "App/App.xcodeproj/project.pbxproj";
18557
18986
  const schemeRelative = "App/App.xcodeproj/xcshareddata/xcschemes/App.xcscheme";
18558
18987
  if (!isNativeFile(nativeDirectory, projectRelative) || !isNativeFile(nativeDirectory, schemeRelative)) {
18559
18988
  throw new Error("Existing ios project shared App archive scheme is missing or unsafe.");
18560
18989
  }
18561
- const project = readFileSync22(path9.join(nativeDirectory, projectRelative), "utf8");
18990
+ const project = readFileSync24(path11.join(nativeDirectory, projectRelative), "utf8");
18562
18991
  const appTargetId = readIosAppTargetId(project);
18563
- const schemeSource = readFileSync22(path9.join(nativeDirectory, schemeRelative), "utf8");
18992
+ const schemeSource = readFileSync24(path11.join(nativeDirectory, schemeRelative), "utf8");
18564
18993
  const scheme = parseXml(schemeSource, "ios project App archive scheme").documentElement;
18565
18994
  if (scheme?.tagName !== "Scheme") {
18566
18995
  throw new Error("Existing ios project App archive scheme is malformed.");
@@ -18587,7 +19016,7 @@ function hasAndroidMergerDirectiveDeep(element) {
18587
19016
  return hasAndroidMergerDirective(element) || childElements(element).some(hasAndroidMergerDirectiveDeep);
18588
19017
  }
18589
19018
  function readAndroidApplication(file, required = true) {
18590
- const root = parseXml(readFileSync22(file, "utf8"), "android manifest").documentElement;
19019
+ const root = parseXml(readFileSync24(file, "utf8"), "android manifest").documentElement;
18591
19020
  const application = root === null ? void 0 : childElements(root).find((element) => element.tagName === "application");
18592
19021
  if (root?.tagName !== "manifest" || required && application === void 0) {
18593
19022
  throw new Error("Existing android project manifest application is missing.");
@@ -18615,22 +19044,22 @@ function readAndroidString(nativeDirectory, key) {
18615
19044
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(key)) {
18616
19045
  throw new Error("Existing android project application label is unsupported.");
18617
19046
  }
18618
- const overlay = path9.join(nativeDirectory, "app/src/release/res/values");
18619
- const overlayValues = existsSync22(overlay) && lstatSync12(overlay).isDirectory() ? readdirSync6(overlay).filter((file) => file.endsWith(".xml")).flatMap((file) => readAndroidValueResource(path9.join(overlay, file), "string", key)) : [];
18620
- const main = path9.join(nativeDirectory, "app/src/main/res/values");
18621
- const mainValues = existsSync22(main) && lstatSync12(main).isDirectory() ? readdirSync6(main).filter((file) => file.endsWith(".xml")).flatMap((file) => readAndroidValueResource(path9.join(main, file), "string", key)) : [];
19047
+ const overlay = path11.join(nativeDirectory, "app/src/release/res/values");
19048
+ const overlayValues = existsSync24(overlay) && lstatSync14(overlay).isDirectory() ? readdirSync7(overlay).filter((file) => file.endsWith(".xml")).flatMap((file) => readAndroidValueResource(path11.join(overlay, file), "string", key)) : [];
19049
+ const main = path11.join(nativeDirectory, "app/src/main/res/values");
19050
+ const mainValues = existsSync24(main) && lstatSync14(main).isDirectory() ? readdirSync7(main).filter((file) => file.endsWith(".xml")).flatMap((file) => readAndroidValueResource(path11.join(main, file), "string", key)) : [];
18622
19051
  const values = overlayValues.length > 0 ? overlayValues : mainValues;
18623
19052
  if (values.length !== 1) {
18624
19053
  throw new Error("Existing android project application label resource is missing or ambiguous.");
18625
19054
  }
18626
- const qualified = ["main", "release"].flatMap((sourceSet) => listNativeFiles(nativeDirectory, `app/src/${sourceSet}/res`, ".xml").filter((relative14) => /^values-[^/]+$/u.test(path9.basename(path9.dirname(relative14)))).flatMap((relative14) => readAndroidValueResource(path9.join(nativeDirectory, relative14), "string", key)));
19055
+ const qualified = ["main", "release"].flatMap((sourceSet) => listNativeFiles(nativeDirectory, `app/src/${sourceSet}/res`, ".xml").filter((relative14) => /^values-[^/]+$/u.test(path11.basename(path11.dirname(relative14)))).flatMap((relative14) => readAndroidValueResource(path11.join(nativeDirectory, relative14), "string", key)));
18627
19056
  if (qualified.some((value) => value !== values[0])) {
18628
19057
  throw new Error("Existing android project configuration-qualified application label differs.");
18629
19058
  }
18630
19059
  return values[0] ?? "";
18631
19060
  }
18632
19061
  function readAndroidValueResource(file, type, name) {
18633
- const root = parseXml(readFileSync22(file, "utf8"), "android values resource").documentElement;
19062
+ const root = parseXml(readFileSync24(file, "utf8"), "android values resource").documentElement;
18634
19063
  if (root?.tagName !== "resources") {
18635
19064
  throw new Error("Existing android values resource is malformed.");
18636
19065
  }
@@ -18690,19 +19119,19 @@ function requireOneNativeFile(directory, candidates, platform) {
18690
19119
  return present[0] ?? "";
18691
19120
  }
18692
19121
  function isNativeFile(directory, relative14) {
18693
- const file = path9.resolve(directory, relative14);
18694
- const within = path9.relative(directory, file);
18695
- if (within === "" || within.startsWith("..") || path9.isAbsolute(within)) {
19122
+ const file = path11.resolve(directory, relative14);
19123
+ const within = path11.relative(directory, file);
19124
+ if (within === "" || within.startsWith("..") || path11.isAbsolute(within)) {
18696
19125
  return false;
18697
19126
  }
18698
19127
  let current = directory;
18699
- for (const segment of within.split(path9.sep)) {
18700
- current = path9.join(current, segment);
18701
- if (!existsSync22(current) || lstatSync12(current).isSymbolicLink()) {
19128
+ for (const segment of within.split(path11.sep)) {
19129
+ current = path11.join(current, segment);
19130
+ if (!existsSync24(current) || lstatSync14(current).isSymbolicLink()) {
18702
19131
  return false;
18703
19132
  }
18704
19133
  }
18705
- return lstatSync12(file).isFile();
19134
+ return lstatSync14(file).isFile();
18706
19135
  }
18707
19136
  var androidAdvertisingRendererService = "dev.mpgd.capacitor.AdvertisingMessagingService";
18708
19137
  var androidMessagingPluginService = "io.capawesome.capacitorjs.plugins.firebase.messaging.MessagingService";
@@ -18747,7 +19176,7 @@ function assertNativeReleaseIdentity(input) {
18747
19176
  const candidates = [
18748
19177
  join14(input.shellApp, "android/app/build.gradle"),
18749
19178
  join14(input.shellApp, "android/app/build.gradle.kts")
18750
- ].filter((file) => existsSync23(file));
19179
+ ].filter((file) => existsSync25(file));
18751
19180
  if (candidates.length !== 1) {
18752
19181
  throw new Error("Android native release requires exactly one Gradle app build file.");
18753
19182
  }
@@ -18778,12 +19207,12 @@ function stampNativeReleaseVersions(input) {
18778
19207
  const candidates = [
18779
19208
  join14(input.stagedShellApp, "android/app/build.gradle"),
18780
19209
  join14(input.stagedShellApp, "android/app/build.gradle.kts")
18781
- ].filter((file3) => existsSync23(file3));
19210
+ ].filter((file3) => existsSync25(file3));
18782
19211
  if (candidates.length !== 1) {
18783
19212
  throw new Error("Android native release requires exactly one Gradle app build file.");
18784
19213
  }
18785
19214
  const file2 = candidates[0] ?? "";
18786
- writeFileSync16(file2, stampAndroidGradleVersions(readFileSync23(file2, "utf8"), {
19215
+ writeFileSync17(file2, stampAndroidGradleVersions(readFileSync25(file2, "utf8"), {
18787
19216
  versionCode,
18788
19217
  versionName
18789
19218
  }, file2));
@@ -18792,7 +19221,7 @@ function stampNativeReleaseVersions(input) {
18792
19221
  const marketingVersion = requireFinalSemVer2(optional(input.environment.MPGD_TARGET_MARKETING_VERSION), "MPGD_TARGET_MARKETING_VERSION");
18793
19222
  const buildNumber = requirePositiveInteger2(optional(input.environment.MPGD_TARGET_BUILD_NUMBER), "MPGD_TARGET_BUILD_NUMBER");
18794
19223
  const file = join14(input.stagedShellApp, "ios/App/App.xcodeproj/project.pbxproj");
18795
- writeFileSync16(file, stampIosProjectVersions(readRequiredFile(file, "iOS Xcode project configuration"), { marketingVersion, buildNumber }, file));
19224
+ writeFileSync17(file, stampIosProjectVersions(readRequiredFile(file, "iOS Xcode project configuration"), { marketingVersion, buildNumber }, file));
18796
19225
  }
18797
19226
  function stampAndroidGradleVersions(source, versions, file) {
18798
19227
  const end = "(?=\\s*(?:;|\\r?\\n|\\}|$))";
@@ -19054,7 +19483,7 @@ function assertAndroidAppliedScripts(appBuild, androidRoot) {
19054
19483
  throw new Error("Native release preflight cannot resolve an applied Gradle script.");
19055
19484
  }
19056
19485
  };
19057
- const rootFiles = ["build.gradle", "build.gradle.kts"].map((name) => join14(androidRoot, name)).filter((file) => existsSync23(file));
19486
+ const rootFiles = ["build.gradle", "build.gradle.kts"].map((name) => join14(androidRoot, name)).filter((file) => existsSync25(file));
19058
19487
  if (rootFiles.length !== 1) {
19059
19488
  throw new Error("Native release preflight requires one Android root Gradle build file.");
19060
19489
  }
@@ -19064,7 +19493,7 @@ function assertAndroidAppliedScripts(appBuild, androidRoot) {
19064
19493
  if (/\b(?:afterEvaluate|projectsEvaluated)\b/u.test(rootCode) && /\b(?:project|subprojects|allprojects|android)\b/u.test(rootCode)) {
19065
19494
  throw new Error("Native release preflight cannot resolve root Gradle app callbacks.");
19066
19495
  }
19067
- const settingsFiles = ["settings.gradle", "settings.gradle.kts"].map((name) => join14(androidRoot, name)).filter((file) => existsSync23(file));
19496
+ const settingsFiles = ["settings.gradle", "settings.gradle.kts"].map((name) => join14(androidRoot, name)).filter((file) => existsSync25(file));
19068
19497
  if (settingsFiles.length !== 1) {
19069
19498
  throw new Error("Native release preflight requires one Android settings Gradle file.");
19070
19499
  }
@@ -19400,10 +19829,10 @@ function stripOptionalQuotes(value) {
19400
19829
  return value;
19401
19830
  }
19402
19831
  function readRequiredFile(file, label) {
19403
- if (!existsSync23(file)) {
19832
+ if (!existsSync25(file)) {
19404
19833
  throw new Error(`Missing ${label}: ${file}`);
19405
19834
  }
19406
- return readFileSync23(file, "utf8");
19835
+ return readFileSync25(file, "utf8");
19407
19836
  }
19408
19837
  function requireFinalSemVer2(value, label) {
19409
19838
  const normalized = requireValue(value, label);
@@ -19514,7 +19943,7 @@ function androidSignedReleaseGradleArgs(environment) {
19514
19943
  if (initScript === void 0 || initScript === "") {
19515
19944
  return ["bundleRelease", "--no-daemon"];
19516
19945
  }
19517
- if (!isAbsolute12(initScript) || !existsSync24(initScript) || !statSync6(initScript).isFile()) {
19946
+ if (!isAbsolute12(initScript) || !existsSync26(initScript) || !statSync6(initScript).isFile()) {
19518
19947
  throw new Error("Android upload signing init script must be an existing absolute file.");
19519
19948
  }
19520
19949
  return ["bundleRelease", "--no-daemon", "--init-script", initScript];
@@ -19624,13 +20053,16 @@ function executeIosBuildWithEnvironment(input, stagedShell, environment) {
19624
20053
  "-exportOptionsPlist",
19625
20054
  requireString(signing.exportOptionsPlist, "iOS export options plist")
19626
20055
  ], environment, `${stagedShell}/ios`);
19627
- const exportedIpas = readdirSync7(exportRoot).filter((name) => name.endsWith(".ipa"));
20056
+ const exportedIpas = readdirSync8(exportRoot).filter((name) => name.endsWith(".ipa"));
19628
20057
  if (exportedIpas.length !== 1) {
19629
20058
  throw new Error("iOS store export must produce exactly one IPA.");
19630
20059
  }
19631
20060
  releaseArtifact = `${artifactRoot}/App.ipa`;
19632
20061
  input.copyFile(join15(exportRoot, exportedIpas[0] ?? ""), input.targetPath(releaseArtifact));
19633
- inspectSignedIosIpa(input.targetPath(releaseArtifact), expectedIosIdentity);
20062
+ inspectSignedIosIpa(input.targetPath(releaseArtifact), {
20063
+ ...expectedIosIdentity,
20064
+ ...shellInstallsFirebaseMessaging(stagedShell) ? { expectedApsEnvironment: "production" } : {}
20065
+ });
19634
20066
  } finally {
19635
20067
  rmSync11(exportRoot, { recursive: true, force: true });
19636
20068
  }
@@ -19742,7 +20174,7 @@ var releaseManifestEnvKeys = [
19742
20174
  var platformTargets = loadPlatformTargetsConfig();
19743
20175
  var configBaseDir = platformTargets.baseDir;
19744
20176
  var kitLinkCheck = resolve18("tools/workspace/relink-kit-workspace.mjs");
19745
- if (existsSync25(kitLinkCheck)) {
20177
+ if (existsSync27(kitLinkCheck)) {
19746
20178
  spawnSync5(process.execPath, [kitLinkCheck, "--warn-unless-owner", configBaseDir], {
19747
20179
  stdio: "inherit"
19748
20180
  });
@@ -20072,7 +20504,7 @@ try {
20072
20504
  function createRuntimeTargetConfigMatrixFile(matrix) {
20073
20505
  const directory = mkdtempSync5(join16(tmpdir5(), "mpgd-target-config-matrix-"));
20074
20506
  const file = join16(directory, "matrix.json");
20075
- writeFileSync17(file, `${JSON.stringify(matrix)}
20507
+ writeFileSync18(file, `${JSON.stringify(matrix)}
20076
20508
  `);
20077
20509
  return file;
20078
20510
  }
@@ -20092,7 +20524,7 @@ function replaceDirectory(source, destination) {
20092
20524
  function mirrorAitRuntimeAssets(gameApp2, wrapperApp) {
20093
20525
  const sourceAssets = `${gameApp2}/dist/assets`;
20094
20526
  const destinationAssets = `${wrapperApp}/public/assets`;
20095
- if (existsSync25(sourceAssets)) {
20527
+ if (existsSync27(sourceAssets)) {
20096
20528
  replaceDirectory(sourceAssets, destinationAssets);
20097
20529
  } else {
20098
20530
  rmSync12(destinationAssets, { recursive: true, force: true });
@@ -20103,7 +20535,7 @@ async function stageTargetAssetPacks(webOutput) {
20103
20535
  return;
20104
20536
  }
20105
20537
  const destination = join16(webOutput, assetPackTargetNamespace);
20106
- if (existsSync25(destination)) {
20538
+ if (existsSync27(destination)) {
20107
20539
  throw new Error("mpgd-asset-packs is owned by the target policy; remove source/public copies from the game");
20108
20540
  }
20109
20541
  cpSync4(assetTargetReport.packagedDir, destination, {
@@ -20126,7 +20558,7 @@ async function writeRemoteAssetPacks() {
20126
20558
  });
20127
20559
  replaceDirectory(assetTargetReport.remoteDir, `${remoteOutput}/remote`);
20128
20560
  mkdirSync13(remoteOutput, { recursive: true });
20129
- writeFileSync17(`${remoteOutput}/report.json`, JSON.stringify({
20561
+ writeFileSync18(`${remoteOutput}/report.json`, JSON.stringify({
20130
20562
  ...assetTargetReport,
20131
20563
  outDir: remoteOutput,
20132
20564
  packagedDir: assetPackTargetNamespace,
@@ -20209,14 +20641,14 @@ function copyIosSyncSwiftPackage(shellApp, releaseArtifact, packageName, swiftPa
20209
20641
  }
20210
20642
  function rewriteIosSyncSwiftPackagePath(releaseArtifact, packageName, swiftPackageName) {
20211
20643
  const packageFile = targetPath(`${releaseArtifact}/App/CapApp-SPM/Package.swift`);
20212
- const contents = readFileSync24(packageFile, "utf8");
20644
+ const contents = readFileSync26(packageFile, "utf8");
20213
20645
  const originalPath = readSwiftPackagePath(packageFile, swiftPackageName);
20214
20646
  const rewrittenPath = `../../node_modules/${packageName}`;
20215
20647
  const marker = `.package(name: "${swiftPackageName}", path: "`;
20216
- writeFileSync17(packageFile, contents.replace(`${marker}${originalPath}"`, `${marker}${rewrittenPath}"`));
20648
+ writeFileSync18(packageFile, contents.replace(`${marker}${originalPath}"`, `${marker}${rewrittenPath}"`));
20217
20649
  }
20218
20650
  function readSwiftPackagePath(packageFile, swiftPackageName) {
20219
- const contents = readFileSync24(packageFile, "utf8");
20651
+ const contents = readFileSync26(packageFile, "utf8");
20220
20652
  const marker = `.package(name: "${swiftPackageName}", path: "`;
20221
20653
  const index = contents.indexOf(marker);
20222
20654
  if (index < 0 || contents.indexOf(marker, index + marker.length) >= 0) {
@@ -20234,10 +20666,10 @@ function copyFile2(source, destination) {
20234
20666
  cpSync4(source, destination);
20235
20667
  }
20236
20668
  function removeFilesByExtension(directory, extension) {
20237
- if (!existsSync25(directory)) {
20669
+ if (!existsSync27(directory)) {
20238
20670
  return;
20239
20671
  }
20240
- for (const file of readdirSync8(directory)) {
20672
+ for (const file of readdirSync9(directory)) {
20241
20673
  const target2 = `${directory}/${file}`;
20242
20674
  if (file.endsWith(extension) && statSync7(target2).isFile()) {
20243
20675
  rmSync12(target2, { force: true });
@@ -20249,7 +20681,7 @@ function safeArtifactFileStem(value) {
20249
20681
  return stem.length === 0 ? "mpgd-kit" : stem;
20250
20682
  }
20251
20683
  function ensureCapacitorPlatform(shellApp, platform, commandEnv) {
20252
- if (!existsSync25(`${shellApp}/${platform}`)) {
20684
+ if (!existsSync27(`${shellApp}/${platform}`)) {
20253
20685
  run("pnpm", ["--dir", shellApp, "cap", "add", platform], commandEnv);
20254
20686
  }
20255
20687
  }
@@ -20273,7 +20705,7 @@ function writeManifest(target2, releaseProfile, artifact, commandEnv) {
20273
20705
  function findEmbeddedIconManifestArtifactPath(artifact) {
20274
20706
  const artifactPath = targetPath(artifact);
20275
20707
  const fileName = "mpgd-icon-manifest.json";
20276
- const matches = statSync7(artifactPath).isDirectory() ? findNamedArtifactFiles(artifactPath, fileName).map((path10) => relative13(artifactPath, path10).replaceAll("\\", "/")) : listZipEntries(artifactPath).filter((entry) => basename5(entry) === fileName);
20708
+ const matches = statSync7(artifactPath).isDirectory() ? findNamedArtifactFiles(artifactPath, fileName).map((path12) => relative13(artifactPath, path12).replaceAll("\\", "/")) : listZipEntries(artifactPath).filter((entry) => basename5(entry) === fileName);
20277
20709
  if (matches.length !== 1) {
20278
20710
  throw new Error(`Expected exactly one ${fileName} in release artifact ${artifactPath}; found ${matches.length}.`);
20279
20711
  }
@@ -20285,22 +20717,22 @@ function findEmbeddedIconManifestArtifactPath(artifact) {
20285
20717
  }
20286
20718
  function findNamedArtifactFiles(root, fileName) {
20287
20719
  const matches = [];
20288
- for (const entry of readdirSync8(root)) {
20289
- const path10 = join16(root, entry);
20290
- const stat2 = lstatSync13(path10);
20720
+ for (const entry of readdirSync9(root)) {
20721
+ const path12 = join16(root, entry);
20722
+ const stat2 = lstatSync15(path12);
20291
20723
  if (stat2.isSymbolicLink()) {
20292
20724
  continue;
20293
20725
  }
20294
20726
  if (stat2.isDirectory()) {
20295
- matches.push(...findNamedArtifactFiles(path10, fileName));
20727
+ matches.push(...findNamedArtifactFiles(path12, fileName));
20296
20728
  } else if (stat2.isFile() && entry === fileName) {
20297
- matches.push(path10);
20729
+ matches.push(path12);
20298
20730
  }
20299
20731
  }
20300
20732
  return matches;
20301
20733
  }
20302
- function listZipEntries(path10) {
20303
- const result = spawnSync5("unzip", ["-Z1", path10], {
20734
+ function listZipEntries(path12) {
20735
+ const result = spawnSync5("unzip", ["-Z1", path12], {
20304
20736
  cwd: process.cwd(),
20305
20737
  env: nativeInspectionEnvironment(),
20306
20738
  encoding: "utf8"
@@ -20310,7 +20742,7 @@ function listZipEntries(path10) {
20310
20742
  }
20311
20743
  const toleratedPrefixWarning = result.status === 1 && result.stdout.trim().length > 0 && result.stderr.includes("extra bytes at beginning or within zipfile") && result.stderr.includes("(attempting to process anyway)");
20312
20744
  if (result.status !== 0 && !toleratedPrefixWarning) {
20313
- throw new Error(`Failed to list release artifact ${path10}: ${result.stderr.trim() || `exit ${String(result.status)}`}`);
20745
+ throw new Error(`Failed to list release artifact ${path12}: ${result.stderr.trim() || `exit ${String(result.status)}`}`);
20314
20746
  }
20315
20747
  return result.stdout.split("\n").filter((entry) => entry.length > 0);
20316
20748
  }
@@ -20337,7 +20769,7 @@ function isMiniGameTarget(target2) {
20337
20769
  }
20338
20770
  function buildMiniGameBundle(bundleKind, outputDir, stagingRoot, gameApp2, profile2, commandEnv) {
20339
20771
  const viteConfig = join16(gameApp2, "vite.minigame.config.ts");
20340
- if (!existsSync25(viteConfig)) {
20772
+ if (!existsSync27(viteConfig)) {
20341
20773
  throw new Error(`Mini-game target requires a game-owned vite.minigame.config.ts: ${viteConfig}`);
20342
20774
  }
20343
20775
  run("pnpm", ["exec", "vite", "build", "--config", "vite.minigame.config.ts", "--mode", profile2], {
@@ -20369,11 +20801,11 @@ function withProcessEnv(env2, keys, callback) {
20369
20801
  }
20370
20802
  }
20371
20803
  }
20372
- function targetPath(path10) {
20373
- return resolveFromPlatformTargetsBase(configBaseDir, path10);
20804
+ function targetPath(path12) {
20805
+ return resolveFromPlatformTargetsBase(configBaseDir, path12);
20374
20806
  }
20375
20807
  function findFileByExtension(directory, extension) {
20376
- const files = readdirSync8(directory).filter((file) => file.endsWith(extension)).sort();
20808
+ const files = readdirSync9(directory).filter((file) => file.endsWith(extension)).sort();
20377
20809
  if (files.length === 0) {
20378
20810
  throw new Error(`Missing ${extension} artifact in ${directory}.`);
20379
20811
  }