@absolutejs/absolute 0.20.0-beta.18 → 0.20.0-beta.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -6763,7 +6763,7 @@ var init_syncSchema = __esm(() => {
6763
6763
  import { readFileSync as readFileSync12 } from "fs";
6764
6764
  import { extname as extname5, join as join20, relative as relative11, resolve as resolve16 } from "path";
6765
6765
  import ts4 from "typescript";
6766
- var DEVICES_PACKAGE = "@absolutejs/devices", CAPACITOR_ADAPTER = "@absolutejs/devices-capacitor", SOURCE_GLOB, IGNORED_DIRECTORIES, IDENTIFIER_PATTERN, CAPACITOR_MODULE_PATTERN, CAPACITOR_PACKAGE_PATTERN, object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), readJson = (path) => {
6766
+ var DEVICES_PACKAGE = "@absolutejs/devices", CAPACITOR_ADAPTER = "@absolutejs/devices-capacitor", SOURCE_GLOB, IGNORED_DIRECTORIES, IDENTIFIER_PATTERN, CAPACITOR_MODULE_PATTERN, CAPACITOR_PACKAGE_PATTERN, ANDROID_PERMISSION_PATTERN, IOS_USAGE_DESCRIPTIONS, object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), readJson = (path) => {
6767
6767
  const value = JSON.parse(readFileSync12(path, "utf8"));
6768
6768
  if (!object2(value))
6769
6769
  throw new TypeError(`${path} must contain an object.`);
@@ -6772,6 +6772,24 @@ var DEVICES_PACKAGE = "@absolutejs/devices", CAPACITOR_ADAPTER = "@absolutejs/de
6772
6772
  if (typeof value !== "string" || value.length === 0)
6773
6773
  throw new TypeError(`${field} must be a non-empty string.`);
6774
6774
  return value;
6775
+ }, androidPermissions = (value, field) => {
6776
+ if (value === undefined)
6777
+ return;
6778
+ if (!object2(value))
6779
+ throw new TypeError(`${field} must be an object.`);
6780
+ const { permissions } = value;
6781
+ if (!Array.isArray(permissions) || !permissions.every((permission) => typeof permission === "string" && ANDROID_PERMISSION_PATTERN.test(permission)))
6782
+ throw new TypeError(`${field}.permissions must contain Android permission names.`);
6783
+ return [...permissions];
6784
+ }, iosUsageDescriptions = (value, field) => {
6785
+ if (value === undefined)
6786
+ return;
6787
+ if (!object2(value))
6788
+ throw new TypeError(`${field} must be an object.`);
6789
+ const { usageDescriptions } = value;
6790
+ if (!Array.isArray(usageDescriptions) || !usageDescriptions.every((purpose) => typeof purpose === "string" && IOS_USAGE_DESCRIPTIONS.has(purpose)))
6791
+ throw new TypeError(`${field}.usageDescriptions contains an unsupported purpose.`);
6792
+ return [...usageDescriptions];
6775
6793
  }, parseProvider = (name, value) => {
6776
6794
  if (!IDENTIFIER_PATTERN.test(name))
6777
6795
  throw new TypeError("Device capability names must be identifiers.");
@@ -6785,8 +6803,33 @@ var DEVICES_PACKAGE = "@absolutejs/devices", CAPACITOR_ADAPTER = "@absolutejs/de
6785
6803
  throw new TypeError(`${name}.module must be an official devices-capacitor subpath.`);
6786
6804
  if (!Array.isArray(value.packages) || !value.packages.every((spec) => typeof spec === "string" && CAPACITOR_PACKAGE_PATTERN.test(spec)))
6787
6805
  throw new TypeError(`${name}.packages must contain exact official Capacitor package versions.`);
6788
- return { factory, module, packages: [...value.packages] };
6789
- }, loadAbsoluteDeviceCapabilityProviders = (projectRoot) => {
6806
+ let native;
6807
+ const { native: nativeMetadata } = value;
6808
+ if (nativeMetadata !== undefined) {
6809
+ if (!object2(nativeMetadata))
6810
+ throw new TypeError(`${name}.native must be an object.`);
6811
+ const { android, ios } = nativeMetadata;
6812
+ const permissions = androidPermissions(android, `${name}.native.android`);
6813
+ const usageDescriptions = iosUsageDescriptions(ios, `${name}.native.ios`);
6814
+ native = {
6815
+ ...permissions === undefined ? {} : { android: { permissions } },
6816
+ ...usageDescriptions === undefined ? {} : { ios: { usageDescriptions } }
6817
+ };
6818
+ }
6819
+ return {
6820
+ factory,
6821
+ module,
6822
+ ...native === undefined ? {} : { native },
6823
+ packages: [...value.packages]
6824
+ };
6825
+ }, absoluteDeviceNativeRequirements = (plan) => ({
6826
+ androidPermissions: [
6827
+ ...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.android?.permissions ?? []))
6828
+ ].sort(),
6829
+ iosUsageDescriptions: [
6830
+ ...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.ios?.usageDescriptions ?? []))
6831
+ ].sort()
6832
+ }), loadAbsoluteDeviceCapabilityProviders = (projectRoot) => {
6790
6833
  const path = join20(resolve16(projectRoot), "node_modules", CAPACITOR_ADAPTER, "package.json");
6791
6834
  const manifest = readJson(path);
6792
6835
  const { absolutejs } = manifest;
@@ -6902,6 +6945,12 @@ var init_deviceCapabilities = __esm(() => {
6902
6945
  IDENTIFIER_PATTERN = /^[A-Za-z_$][\w$]*$/u;
6903
6946
  CAPACITOR_MODULE_PATTERN = /^@absolutejs\/devices-capacitor\/[a-z][a-z0-9-]*$/u;
6904
6947
  CAPACITOR_PACKAGE_PATTERN = /^@capacitor\/[a-z][a-z0-9-]*@\d+\.\d+\.\d+$/u;
6948
+ ANDROID_PERMISSION_PATTERN = /^android\.permission\.[A-Z][A-Z0-9_]*$/u;
6949
+ IOS_USAGE_DESCRIPTIONS = new Set([
6950
+ "camera",
6951
+ "photo-library",
6952
+ "photo-library-add"
6953
+ ]);
6905
6954
  });
6906
6955
 
6907
6956
  // src/mobile/buildPipeline.ts
@@ -16415,10 +16464,10 @@ ${domains}
16415
16464
  };
16416
16465
  var init_nativeDeepLinks = () => {};
16417
16466
 
16418
- // src/mobile/nativeBackgroundSync.ts
16467
+ // src/mobile/nativeDeviceCapabilities.ts
16419
16468
  import { readFile as readFile12, rename as rename9, writeFile as writeFile10 } from "fs/promises";
16420
16469
  import { join as join47 } from "path";
16421
- var writeChanged = async (path, source) => {
16470
+ var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->", END_MARKER2 = "<!-- absolutejs:device-capabilities:end -->", NOT_FOUND2 = -1, escapeXml2 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;"), writeChangedFile2 = async (path, source) => {
16422
16471
  const current = await readFile12(path, "utf8");
16423
16472
  if (current === source)
16424
16473
  return false;
@@ -16426,6 +16475,85 @@ var writeChanged = async (path, source) => {
16426
16475
  await writeFile10(temporary, source, { flag: "wx" });
16427
16476
  await rename9(temporary, path);
16428
16477
  return true;
16478
+ }, managed = (source, region, insertion) => {
16479
+ const start2 = source.indexOf(START_MARKER2);
16480
+ const end = source.indexOf(END_MARKER2);
16481
+ if (start2 === NOT_FOUND2 !== (end === NOT_FOUND2) || start2 !== NOT_FOUND2 && end < start2)
16482
+ throw new TypeError("AbsoluteJS device-capability ownership markers are malformed.");
16483
+ if (start2 !== NOT_FOUND2) {
16484
+ const lineStart = source.lastIndexOf(`
16485
+ `, start2) + 1;
16486
+ const nextLine = source.indexOf(`
16487
+ `, end + END_MARKER2.length);
16488
+ const lineEnd = nextLine === NOT_FOUND2 ? source.length : nextLine + 1;
16489
+ return `${source.slice(0, lineStart)}${region}${source.slice(lineEnd)}`;
16490
+ }
16491
+ if (region.length === 0)
16492
+ return source;
16493
+ if (insertion === NOT_FOUND2)
16494
+ throw new TypeError("Could not find a safe native project location for device permissions.");
16495
+ return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
16496
+ }, IOS_KEYS, iosDescription = (appName, purpose) => {
16497
+ if (purpose === "camera")
16498
+ return `${appName} uses your camera when you choose to take a photo.`;
16499
+ if (purpose === "photo-library")
16500
+ return `${appName} accesses your photo library only for photo actions you choose.`;
16501
+ return `${appName} adds to your photo library only for photo actions you choose.`;
16502
+ }, configureIos2 = async (config, plan) => {
16503
+ const path = join47(config.nativeProjectDirectory, "ios/App/App/Info.plist");
16504
+ const source = await readFile12(path, "utf8");
16505
+ const requirements = absoluteDeviceNativeRequirements(plan);
16506
+ const content = requirements.iosUsageDescriptions.map((purpose) => ` <key>${IOS_KEYS[purpose]}</key>
16507
+ <string>${escapeXml2(iosDescription(config.appName, purpose))}</string>`).join(`
16508
+ `);
16509
+ const region = content ? ` ${START_MARKER2}
16510
+ ${content}
16511
+ ${END_MARKER2}
16512
+ ` : "";
16513
+ return writeChangedFile2(path, managed(source, region, source.lastIndexOf("</dict>")));
16514
+ }, configureAndroid2 = async (config, plan) => {
16515
+ const path = join47(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
16516
+ const source = await readFile12(path, "utf8");
16517
+ const permissions = absoluteDeviceNativeRequirements(plan).androidPermissions;
16518
+ const content = permissions.map((permission) => ` <uses-permission android:name="${escapeXml2(permission)}" />`).join(`
16519
+ `);
16520
+ const region = content ? ` ${START_MARKER2}
16521
+ ${content}
16522
+ ${END_MARKER2}
16523
+ ` : "";
16524
+ const application = source.indexOf("<application");
16525
+ const insertion = application === NOT_FOUND2 ? NOT_FOUND2 : source.lastIndexOf(`
16526
+ `, application) + 1;
16527
+ return writeChangedFile2(path, managed(source, region, insertion));
16528
+ }, applyAbsoluteNativeDeviceCapabilities = async (projectRoot, config, platforms = config.platforms, plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot)) => {
16529
+ const results = await Promise.all(platforms.map(async (platform6) => ({
16530
+ didChange: platform6 === "ios" ? await configureIos2(config, plan) : await configureAndroid2(config, plan),
16531
+ platform: platform6
16532
+ })));
16533
+ return {
16534
+ changed: results.filter(({ didChange }) => didChange).map(({ platform: platform6 }) => platform6)
16535
+ };
16536
+ };
16537
+ var init_nativeDeviceCapabilities = __esm(() => {
16538
+ init_deviceCapabilities();
16539
+ IOS_KEYS = {
16540
+ camera: "NSCameraUsageDescription",
16541
+ "photo-library": "NSPhotoLibraryUsageDescription",
16542
+ "photo-library-add": "NSPhotoLibraryAddUsageDescription"
16543
+ };
16544
+ });
16545
+
16546
+ // src/mobile/nativeBackgroundSync.ts
16547
+ import { readFile as readFile13, rename as rename10, writeFile as writeFile11 } from "fs/promises";
16548
+ import { join as join48 } from "path";
16549
+ var writeChanged = async (path, source) => {
16550
+ const current = await readFile13(path, "utf8");
16551
+ if (current === source)
16552
+ return false;
16553
+ const temporary = `${path}.${crypto.randomUUID()}.tmp`;
16554
+ await writeFile11(temporary, source, { flag: "wx" });
16555
+ await rename10(temporary, path);
16556
+ return true;
16429
16557
  }, replaceRegion = (source, start2, end, region, insert) => {
16430
16558
  const existingStart = source.indexOf(start2);
16431
16559
  const existingEnd = source.indexOf(end);
@@ -16500,11 +16628,11 @@ ${makeRegion(values)} </array>
16500
16628
  if (!platforms.includes("ios") || !projectUsesAbsoluteAuth(projectRoot) || !projectUsesAbsoluteSync(projectRoot))
16501
16629
  return { changed: false };
16502
16630
  const identifier = `${config.appId}.absolutejs.background-sync`;
16503
- const infoPath = join47(config.nativeProjectDirectory, "ios/App/App/Info.plist");
16504
- const info2 = await readFile12(infoPath, "utf8");
16631
+ const infoPath = join48(config.nativeProjectDirectory, "ios/App/App/Info.plist");
16632
+ const info2 = await readFile13(infoPath, "utf8");
16505
16633
  const nextInfo = ensurePlistArrayValues(ensurePlistArrayValues(info2, "BGTaskSchedulerPermittedIdentifiers", [identifier], "background-sync-identifiers"), "UIBackgroundModes", ["fetch", "processing"], "background-sync-modes");
16506
- const delegatePath = join47(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
16507
- let delegate = await readFile12(delegatePath, "utf8");
16634
+ const delegatePath = join48(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
16635
+ let delegate = await readFile13(delegatePath, "utf8");
16508
16636
  if (!delegate.includes("import AbsoluteSyncCapacitor")) {
16509
16637
  const importIndex = delegate.lastIndexOf("import Capacitor");
16510
16638
  if (importIndex < 0)
@@ -16538,10 +16666,10 @@ var init_nativeBackgroundSync = __esm(() => {
16538
16666
  import {
16539
16667
  access as access7,
16540
16668
  mkdir as mkdir10,
16541
- readFile as readFile13,
16542
- rename as rename10,
16669
+ readFile as readFile14,
16670
+ rename as rename11,
16543
16671
  rm as rm7,
16544
- writeFile as writeFile11
16672
+ writeFile as writeFile12
16545
16673
  } from "fs/promises";
16546
16674
  import { resolve as resolve37 } from "path";
16547
16675
  import { Elysia } from "elysia";
@@ -16596,7 +16724,7 @@ var OWNERSHIP_FILE = ".absolutejs-mobile-associations.json", HTTP_OK = 200, VERI
16596
16724
  }, writeAtomic = async (path, source) => {
16597
16725
  let current;
16598
16726
  try {
16599
- current = await readFile13(path, "utf8");
16727
+ current = await readFile14(path, "utf8");
16600
16728
  } catch (error) {
16601
16729
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
16602
16730
  throw error;
@@ -16605,8 +16733,8 @@ var OWNERSHIP_FILE = ".absolutejs-mobile-associations.json", HTTP_OK = 200, VERI
16605
16733
  if (current === source)
16606
16734
  return false;
16607
16735
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
16608
- await writeFile11(temporary, source, { flag: "wx" });
16609
- await rename10(temporary, path);
16736
+ await writeFile12(temporary, source, { flag: "wx" });
16737
+ await rename11(temporary, path);
16610
16738
  return true;
16611
16739
  }, exists2 = async (path) => {
16612
16740
  try {
@@ -16619,7 +16747,7 @@ var OWNERSHIP_FILE = ".absolutejs-mobile-associations.json", HTTP_OK = 200, VERI
16619
16747
  const path = resolve37(root, OWNERSHIP_FILE);
16620
16748
  let ownership;
16621
16749
  try {
16622
- ownership = JSON.parse(await readFile13(path, "utf8"));
16750
+ ownership = JSON.parse(await readFile14(path, "utf8"));
16623
16751
  } catch {
16624
16752
  throw new TypeError(`Association output ${root} already exists and is not owned by AbsoluteJS.`);
16625
16753
  }
@@ -16632,12 +16760,12 @@ var OWNERSHIP_FILE = ".absolutejs-mobile-associations.json", HTTP_OK = 200, VERI
16632
16760
  await assertOwnedOutput(root);
16633
16761
  const backup = `${root}.${crypto.randomUUID()}.previous`;
16634
16762
  if (hasCurrent)
16635
- await rename10(root, backup);
16763
+ await rename11(root, backup);
16636
16764
  try {
16637
- await rename10(temporary, root);
16765
+ await rename11(temporary, root);
16638
16766
  } catch (error) {
16639
16767
  if (hasCurrent)
16640
- await rename10(backup, root);
16768
+ await rename11(backup, root);
16641
16769
  throw error;
16642
16770
  }
16643
16771
  if (hasCurrent)
@@ -16727,7 +16855,7 @@ var init_associationFiles = __esm(() => {
16727
16855
  });
16728
16856
 
16729
16857
  // src/mobile/androidWebView.ts
16730
- import { mkdir as mkdir11, writeFile as writeFile12 } from "fs/promises";
16858
+ import { mkdir as mkdir11, writeFile as writeFile13 } from "fs/promises";
16731
16859
  import { dirname as dirname28, resolve as resolve38 } from "path";
16732
16860
 
16733
16861
  class CdpConnection {
@@ -17001,7 +17129,7 @@ var CDP_COMMAND_TIMEOUT_MS = 1e4, WEBVIEW_ATTACH_TIMEOUT_MS = 30000, WEBVIEW_POL
17001
17129
  }
17002
17130
  const absolutePath = resolve38(path);
17003
17131
  await mkdir11(dirname28(absolutePath), { recursive: true });
17004
- await writeFile12(absolutePath, Buffer.from(data, "base64"));
17132
+ await writeFile13(absolutePath, Buffer.from(data, "base64"));
17005
17133
  return absolutePath;
17006
17134
  }
17007
17135
  };
@@ -17119,8 +17247,8 @@ var DEFAULT_ROUTE_TIMEOUT_MS = 30000, DEFAULT_HMR_TIMEOUT_MS = 30000, routeExpre
17119
17247
  };
17120
17248
 
17121
17249
  // src/mobile/releaseDoctor.ts
17122
- import { access as access8, readFile as readFile14, readdir as readdir4 } from "fs/promises";
17123
- import { extname as extname8, join as join48, relative as relative24 } from "path";
17250
+ import { access as access8, readFile as readFile15, readdir as readdir4 } from "fs/promises";
17251
+ import { extname as extname8, join as join49, relative as relative24 } from "path";
17124
17252
  var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17125
17253
  try {
17126
17254
  await access8(path);
@@ -17133,13 +17261,13 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17133
17261
  return findHmrAsset(path);
17134
17262
  if (!isFile2 || !RELEASE_ASSET_EXTENSIONS.has(extname8(path)))
17135
17263
  return;
17136
- const source = await readFile14(path, "utf8");
17264
+ const source = await readFile15(path, "utf8");
17137
17265
  return HMR_ASSET_PATTERN.test(source) ? path : undefined;
17138
17266
  }, findHmrAsset = async (root) => {
17139
17267
  if (!await pathExists5(root))
17140
17268
  return;
17141
17269
  const entries = await readdir4(root, { withFileTypes: true });
17142
- const matches = await Promise.all(entries.map((entry) => inspectReleaseAsset(join48(root, entry.name), entry.isDirectory(), entry.isFile())));
17270
+ const matches = await Promise.all(entries.map((entry) => inspectReleaseAsset(join49(root, entry.name), entry.isDirectory(), entry.isFile())));
17143
17271
  return matches.find((match) => match !== undefined);
17144
17272
  }, pass = (id, detail, path) => ({ detail, id, path, status: "pass" }), fail5 = (id, detail, path, remediation) => ({
17145
17273
  detail,
@@ -17172,13 +17300,13 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17172
17300
  if (!await pathExists5(nativeConfigPath)) {
17173
17301
  return fail5("android.capacitor-config", "The generated Android Capacitor config is missing.", nativeConfigPath, "Run `absolute mobile sync android` before release validation.");
17174
17302
  }
17175
- const unsafe = isUnsafeCapacitorConfig(await readFile14(nativeConfigPath, "utf8"));
17303
+ const unsafe = isUnsafeCapacitorConfig(await readFile15(nativeConfigPath, "utf8"));
17176
17304
  return unsafe ? fail5("android.capacitor-config", "Android Capacitor config contains a development server URL, cleartext transport, navigation allowlist, or invalid JSON.", nativeConfigPath, "Run `absolute mobile sync android`; do not ship development transport overrides.") : pass("android.capacitor-config", "Android Capacitor config contains no development transport overrides.", nativeConfigPath);
17177
17305
  }, manifestReleaseCheck = async (manifestPath) => {
17178
17306
  if (!await pathExists5(manifestPath)) {
17179
17307
  return fail5("android.cleartext", "The Android manifest is missing.", manifestPath, "Run `absolute mobile sync android` before release validation.");
17180
17308
  }
17181
- const source = await readFile14(manifestPath, "utf8");
17309
+ const source = await readFile15(manifestPath, "utf8");
17182
17310
  return /android:usesCleartextTraffic=["']true["']/u.test(source) ? fail5("android.cleartext", "Android explicitly permits cleartext traffic.", manifestPath, 'Remove usesCleartextTraffic="true" from the release manifest.') : pass("android.cleartext", "Android does not explicitly permit cleartext traffic.", manifestPath);
17183
17311
  }, hmrAssetsReleaseCheck = async (publicRoot) => {
17184
17312
  const hmrAsset = await findHmrAsset(publicRoot);
@@ -17186,7 +17314,7 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17186
17314
  }, syncSchemaReleaseCheck = (projectRoot) => {
17187
17315
  if (!projectUsesAbsoluteSync(projectRoot))
17188
17316
  return;
17189
- const manifestPath = join48(projectRoot, "package.json");
17317
+ const manifestPath = join49(projectRoot, "package.json");
17190
17318
  try {
17191
17319
  const schema = discoverAbsoluteSyncSchema(projectRoot);
17192
17320
  const versions = schema.components.map((component2) => `${component2.id}@${component2.version}`).join(", ");
@@ -17205,21 +17333,46 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17205
17333
  } catch (error) {
17206
17334
  return fail5("sync.storage-schema", error instanceof Error ? error.message : "Generated offline schema metadata is invalid.", manifestPath, "Fix absolutejs.sync.localSchema metadata in the named app or package before releasing.");
17207
17335
  }
17208
- }, deviceCapabilityReleaseCheck = (projectRoot) => {
17209
- const manifestPath = join48(projectRoot, "package.json");
17336
+ }, IOS_USAGE_KEYS, androidDevicePermissionCheck = async (config, permissions) => {
17337
+ if (!config.platforms.includes("android") || permissions.length === 0)
17338
+ return;
17339
+ const path = join49(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
17340
+ const source = await readFile15(path, "utf8");
17341
+ const missing = permissions.filter((permission) => !source.includes(`android:name="${permission}"`) && !source.includes(`android:name='${permission}'`));
17342
+ if (missing.length === 0)
17343
+ return;
17344
+ return fail5("mobile.device-capabilities", `Android is missing native declarations for: ${missing.join(", ")}.`, path, "Run `absolute mobile sync android` to regenerate detected device permissions.");
17345
+ }, iosDevicePermissionCheck = async (config, purposes) => {
17346
+ if (!config.platforms.includes("ios") || purposes.length === 0)
17347
+ return;
17348
+ const path = join49(config.nativeProjectDirectory, "ios/App/App/Info.plist");
17349
+ const source = await readFile15(path, "utf8");
17350
+ const missing = purposes.filter((purpose) => !source.includes(`<key>${IOS_USAGE_KEYS[purpose]}</key>`));
17351
+ if (missing.length === 0)
17352
+ return;
17353
+ return fail5("mobile.device-capabilities", `iOS is missing usage descriptions for: ${missing.join(", ")}.`, path, "Run `absolute mobile sync ios` to regenerate detected device usage descriptions.");
17354
+ }, deviceCapabilityReleaseCheck = async (config, projectRoot) => {
17355
+ const manifestPath = join49(projectRoot, "package.json");
17210
17356
  try {
17211
17357
  const plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot);
17212
17358
  assertAbsoluteDeviceCapabilityPackages(projectRoot, plan);
17213
- return pass("mobile.device-capabilities", plan.capabilities.length > 0 ? `Native provider packages match detected capabilities: ${plan.capabilities.join(", ")}.` : "No optional native device capabilities are used.", manifestPath);
17359
+ const requirements = absoluteDeviceNativeRequirements(plan);
17360
+ const androidCheck = await androidDevicePermissionCheck(config, requirements.androidPermissions);
17361
+ if (androidCheck)
17362
+ return androidCheck;
17363
+ const iosCheck = await iosDevicePermissionCheck(config, requirements.iosUsageDescriptions);
17364
+ if (iosCheck)
17365
+ return iosCheck;
17366
+ return pass("mobile.device-capabilities", plan.capabilities.length > 0 ? `Native provider packages and permission declarations match detected capabilities: ${plan.capabilities.join(", ")}.` : "No optional native device capabilities are used.", manifestPath);
17214
17367
  } catch (error) {
17215
17368
  return fail5("mobile.device-capabilities", error instanceof Error ? error.message : "Native device capability provisioning is invalid.", manifestPath, "Run `absolute mobile sync` and approve the exact capability plugins before releasing.");
17216
17369
  }
17217
17370
  }, inspectAndroidRelease = async (config, projectRoot) => {
17218
- const androidRoot = join48(config.nativeProjectDirectory, "android");
17219
- const nativeConfigPath = join48(androidRoot, "app", "src", "main", "assets", "capacitor.config.json");
17220
- const manifestPath = join48(androidRoot, "app", "src", "main", "AndroidManifest.xml");
17221
- const publicRoot = join48(androidRoot, "app", "src", "main", "assets", "public");
17222
- const journalPath = join48(projectRoot, ".absolutejs", "mobile", "dev-session", "journal.json");
17371
+ const androidRoot = join49(config.nativeProjectDirectory, "android");
17372
+ const nativeConfigPath = join49(androidRoot, "app", "src", "main", "assets", "capacitor.config.json");
17373
+ const manifestPath = join49(androidRoot, "app", "src", "main", "AndroidManifest.xml");
17374
+ const publicRoot = join49(androidRoot, "app", "src", "main", "assets", "public");
17375
+ const journalPath = join49(projectRoot, ".absolutejs", "mobile", "dev-session", "journal.json");
17223
17376
  const checks = await Promise.all([
17224
17377
  journalReleaseCheck(journalPath, "android"),
17225
17378
  capacitorConfigReleaseCheck(nativeConfigPath),
@@ -17231,11 +17384,11 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17231
17384
  path: check2.path ? relative24(projectRoot, check2.path).replaceAll("\\", "/") || "." : undefined
17232
17385
  }));
17233
17386
  }, inspectIosRelease = async (config, projectRoot) => {
17234
- const iosAppRoot = join48(config.nativeProjectDirectory, "ios", "App", "App");
17235
- const nativeConfigPath = join48(iosAppRoot, "capacitor.config.json");
17236
- const infoPath = join48(iosAppRoot, "Info.plist");
17237
- const publicRoot = join48(iosAppRoot, "public");
17238
- const journalPath = join48(projectRoot, ".absolutejs", "mobile", "ios-dev-session", "journal.json");
17387
+ const iosAppRoot = join49(config.nativeProjectDirectory, "ios", "App", "App");
17388
+ const nativeConfigPath = join49(iosAppRoot, "capacitor.config.json");
17389
+ const infoPath = join49(iosAppRoot, "Info.plist");
17390
+ const publicRoot = join49(iosAppRoot, "public");
17391
+ const journalPath = join49(projectRoot, ".absolutejs", "mobile", "ios-dev-session", "journal.json");
17239
17392
  const checks = [
17240
17393
  await journalReleaseCheck(journalPath, "ios")
17241
17394
  ];
@@ -17246,7 +17399,7 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17246
17399
  }
17247
17400
  if (!await pathExists5(nativeConfigPath)) {
17248
17401
  checks.push(fail5("ios.capacitor-config", "The generated iOS Capacitor config is missing.", nativeConfigPath, "Run `absolute mobile sync ios` before release validation."));
17249
- } else if (isUnsafeCapacitorConfig(await readFile14(nativeConfigPath, "utf8"))) {
17402
+ } else if (isUnsafeCapacitorConfig(await readFile15(nativeConfigPath, "utf8"))) {
17250
17403
  checks.push(fail5("ios.capacitor-config", "iOS Capacitor config contains a development server URL, cleartext transport, navigation allowlist, or invalid JSON.", nativeConfigPath, "Run `absolute mobile sync ios`; do not ship development transport overrides."));
17251
17404
  } else {
17252
17405
  checks.push(pass("ios.capacitor-config", "iOS Capacitor config contains no development transport overrides.", nativeConfigPath));
@@ -17254,7 +17407,7 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17254
17407
  if (!await pathExists5(infoPath)) {
17255
17408
  checks.push(fail5("ios.transport-security", "The iOS Info.plist is missing.", infoPath, "Run `absolute mobile sync ios` before release validation."));
17256
17409
  } else {
17257
- const info2 = await readFile14(infoPath, "utf8");
17410
+ const info2 = await readFile15(infoPath, "utf8");
17258
17411
  checks.push(/<key>NSAllowsArbitraryLoads<\/key>\s*<true\s*\/>/u.test(info2) ? fail5("ios.transport-security", "iOS App Transport Security permits arbitrary network loads.", infoPath, "Remove NSAllowsArbitraryLoads from the release Info.plist.") : pass("ios.transport-security", "iOS App Transport Security does not permit arbitrary loads.", infoPath));
17259
17412
  }
17260
17413
  const hmrAsset = await findHmrAsset(publicRoot);
@@ -17275,7 +17428,7 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
17275
17428
  path: syncSchema.path ? relative24(projectRoot, syncSchema.path).replaceAll("\\", "/") || "." : undefined
17276
17429
  });
17277
17430
  }
17278
- const deviceCapabilities = deviceCapabilityReleaseCheck(projectRoot);
17431
+ const deviceCapabilities = await deviceCapabilityReleaseCheck(config, projectRoot);
17279
17432
  checks.push({
17280
17433
  ...deviceCapabilities,
17281
17434
  path: deviceCapabilities.path ? relative24(projectRoot, deviceCapabilities.path).replaceAll("\\", "/") || "." : undefined
@@ -17291,6 +17444,11 @@ var init_releaseDoctor = __esm(() => {
17291
17444
  init_deviceCapabilities();
17292
17445
  HMR_ASSET_PATTERN = /(?:__HMR_WS__|hmr-timing|__absolute_target|absolutejs-error-overlay)/u;
17293
17446
  RELEASE_ASSET_EXTENSIONS = new Set([".html", ".js", ".mjs"]);
17447
+ IOS_USAGE_KEYS = {
17448
+ camera: "NSCameraUsageDescription",
17449
+ "photo-library": "NSPhotoLibraryUsageDescription",
17450
+ "photo-library-add": "NSPhotoLibraryAddUsageDescription"
17451
+ };
17294
17452
  });
17295
17453
 
17296
17454
  // src/mobile/androidRelease.ts
@@ -17300,13 +17458,13 @@ import {
17300
17458
  copyFile as copyFile5,
17301
17459
  mkdir as mkdir12,
17302
17460
  mkdtemp as mkdtemp5,
17303
- readFile as readFile15,
17304
- rename as rename11,
17461
+ readFile as readFile16,
17462
+ rename as rename12,
17305
17463
  rm as rm8,
17306
17464
  stat as stat2,
17307
- writeFile as writeFile13
17465
+ writeFile as writeFile14
17308
17466
  } from "fs/promises";
17309
- import { dirname as dirname29, isAbsolute as isAbsolute7, join as join49, relative as relative25, resolve as resolve39, sep as sep6 } from "path";
17467
+ import { dirname as dirname29, isAbsolute as isAbsolute7, join as join50, relative as relative25, resolve as resolve39, sep as sep6 } from "path";
17310
17468
  var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), requireManifest2 = (value) => {
17311
17469
  if (!isRecord13(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
17312
17470
  throw new TypeError("Invalid embedded AbsoluteJS mobile manifest.");
@@ -17356,7 +17514,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
17356
17514
  artifactPath
17357
17515
  ]);
17358
17516
  return result.exitCode === 0 && /jar verified/iu.test(result.stdout);
17359
- }, sha256File2 = async (path) => createHash12("sha256").update(await readFile15(path)).digest("hex"), safeOutputDirectory2 = (projectRoot, requested) => {
17517
+ }, sha256File2 = async (path) => createHash12("sha256").update(await readFile16(path)).digest("hex"), safeOutputDirectory2 = (projectRoot, requested) => {
17360
17518
  const root = resolve39(projectRoot);
17361
17519
  const output = resolve39(root, requested ?? ".absolutejs/mobile/releases/android");
17362
17520
  const projectRelative = relative25(root, output);
@@ -17365,11 +17523,11 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
17365
17523
  }
17366
17524
  return output;
17367
17525
  }, installRelease2 = async (artifactPath, metadata, outputRoot) => {
17368
- const releaseRoot = join49(outputRoot, metadata.releaseId);
17526
+ const releaseRoot = join50(outputRoot, metadata.releaseId);
17369
17527
  const artifactName = "app-release.aab";
17370
- const destination = join49(releaseRoot, artifactName);
17528
+ const destination = join50(releaseRoot, artifactName);
17371
17529
  if (await pathExists6(releaseRoot)) {
17372
- const existing = requireManifestIdentity(JSON.parse(await readFile15(join49(releaseRoot, "release.json"), "utf8")), metadata);
17530
+ const existing = requireManifestIdentity(JSON.parse(await readFile16(join50(releaseRoot, "release.json"), "utf8")), metadata);
17373
17531
  const [installedBytes, installedSha256] = await Promise.all([
17374
17532
  stat2(destination).then(({ size }) => size),
17375
17533
  sha256File2(destination)
@@ -17380,16 +17538,16 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
17380
17538
  return { artifactPath: destination, metadata: existing, releaseRoot };
17381
17539
  }
17382
17540
  await mkdir12(dirname29(releaseRoot), { recursive: true });
17383
- const staging = await mkdtemp5(join49(dirname29(releaseRoot), ".android-stage-"));
17541
+ const staging = await mkdtemp5(join50(dirname29(releaseRoot), ".android-stage-"));
17384
17542
  try {
17385
- await copyFile5(artifactPath, join49(staging, artifactName));
17543
+ await copyFile5(artifactPath, join50(staging, artifactName));
17386
17544
  const complete = {
17387
17545
  ...metadata,
17388
17546
  artifact: artifactName
17389
17547
  };
17390
- await writeFile13(join49(staging, "release.json"), `${JSON.stringify(complete, null, "\t")}
17548
+ await writeFile14(join50(staging, "release.json"), `${JSON.stringify(complete, null, "\t")}
17391
17549
  `, { flag: "wx" });
17392
- await rename11(staging, releaseRoot);
17550
+ await rename12(staging, releaseRoot);
17393
17551
  return { artifactPath: destination, metadata: complete, releaseRoot };
17394
17552
  } finally {
17395
17553
  await rm8(staging, { force: true, recursive: true }).catch(() => {
@@ -17415,8 +17573,8 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
17415
17573
  const projectRoot = resolve39(options.projectRoot);
17416
17574
  const host2 = options.host ?? detectAbsoluteMobileHost();
17417
17575
  const androidRoot = options.androidRoot ?? process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host2);
17418
- const nativeDirectory = join49(options.config.nativeProjectDirectory, "android");
17419
- const manifest = requireManifest2(JSON.parse(await readFile15(join49(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
17576
+ const nativeDirectory = join50(options.config.nativeProjectDirectory, "android");
17577
+ const manifest = requireManifest2(JSON.parse(await readFile16(join50(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
17420
17578
  if (manifest.appId !== options.config.appId) {
17421
17579
  throw new TypeError("Embedded mobile manifest appId does not match mobile.appId.");
17422
17580
  }
@@ -17480,7 +17638,7 @@ var init_androidRelease = __esm(() => {
17480
17638
  });
17481
17639
 
17482
17640
  // src/mobile/iosConformance.ts
17483
- import { readFile as readFile16, stat as stat3 } from "fs/promises";
17641
+ import { readFile as readFile17, stat as stat3 } from "fs/promises";
17484
17642
  var HMR_LINE, parseAbsoluteIosHmrLog = (line) => {
17485
17643
  const match = HMR_LINE.exec(line);
17486
17644
  if (!match)
@@ -17515,7 +17673,7 @@ var HMR_LINE, parseAbsoluteIosHmrLog = (line) => {
17515
17673
  if (Date.now() > deadline)
17516
17674
  throw new Error(`No iOS native HMR acknowledgement was observed within ${timeoutMs}ms.`);
17517
17675
  options.signal?.throwIfAborted();
17518
- const contents = await readFile16(options.logPath).catch(() => Buffer.alloc(0));
17676
+ const contents = await readFile17(options.logPath).catch(() => Buffer.alloc(0));
17519
17677
  if (contents.byteLength < offset) {
17520
17678
  offset = 0;
17521
17679
  buffered = "";
@@ -17636,11 +17794,11 @@ var exports_mobile = {};
17636
17794
  __export(exports_mobile, {
17637
17795
  runMobile: () => runMobile
17638
17796
  });
17639
- import { access as access11, mkdir as mkdir13, readFile as readFile17, writeFile as writeFile14 } from "fs/promises";
17640
- import { join as join50, resolve as resolve41 } from "path";
17797
+ import { access as access11, mkdir as mkdir13, readFile as readFile18, writeFile as writeFile15 } from "fs/promises";
17798
+ import { join as join51, resolve as resolve41 } from "path";
17641
17799
  import { createInterface } from "readline/promises";
17642
- var NOT_FOUND2 = -1, isRecord15 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), CAPACITOR_PACKAGES, CAPACITOR_PACKAGE_SPECS, CAPACITOR_SYNC_PACKAGE_SPECS, packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@")), directProjectPackages = async (projectRoot) => {
17643
- const manifest = JSON.parse(await readFile17(join50(projectRoot, "package.json"), "utf8"));
17800
+ var NOT_FOUND3 = -1, isRecord15 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), CAPACITOR_PACKAGES, CAPACITOR_PACKAGE_SPECS, CAPACITOR_SYNC_PACKAGE_SPECS, packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@")), directProjectPackages = async (projectRoot) => {
17801
+ const manifest = JSON.parse(await readFile18(join51(projectRoot, "package.json"), "utf8"));
17644
17802
  if (!isRecord15(manifest))
17645
17803
  throw new TypeError("Application package.json must contain an object.");
17646
17804
  const names = new Set;
@@ -17653,7 +17811,7 @@ var NOT_FOUND2 = -1, isRecord15 = (value) => typeof value === "object" && value
17653
17811
  return names;
17654
17812
  }, resolvedPackageVersion = async (projectRoot, packageName) => {
17655
17813
  try {
17656
- const manifest = JSON.parse(await readFile17(join50(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
17814
+ const manifest = JSON.parse(await readFile18(join51(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
17657
17815
  return isRecord15(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
17658
17816
  } catch {
17659
17817
  return;
@@ -17684,7 +17842,7 @@ var NOT_FOUND2 = -1, isRecord15 = (value) => typeof value === "object" && value
17684
17842
  await installApprovedPackages(projectRoot, args, `AbsoluteJS detected native device capabilities (${capabilityPlan.capabilities.join(", ")}). Install only their required Capacitor plugins now?`, capabilityPackages);
17685
17843
  }, valueAfter = (args, flag) => {
17686
17844
  const index = args.indexOf(flag);
17687
- return index === NOT_FOUND2 ? undefined : args[index + 1];
17845
+ return index === NOT_FOUND3 ? undefined : args[index + 1];
17688
17846
  }, valuesAfter = (args, flag) => args.flatMap((value, index) => {
17689
17847
  const next = args[index + 1];
17690
17848
  return value === flag && next !== undefined ? [next] : [];
@@ -17694,7 +17852,7 @@ var NOT_FOUND2 = -1, isRecord15 = (value) => typeof value === "object" && value
17694
17852
  }
17695
17853
  return value;
17696
17854
  }, capacitorExecutable = async (projectRoot) => {
17697
- const executable = join50(projectRoot, "node_modules", ".bin", "cap");
17855
+ const executable = join51(projectRoot, "node_modules", ".bin", "cap");
17698
17856
  try {
17699
17857
  await access11(executable);
17700
17858
  return executable;
@@ -17765,6 +17923,7 @@ var NOT_FOUND2 = -1, isRecord15 = (value) => typeof value === "object" && value
17765
17923
  return;
17766
17924
  await runCapacitorForPlatforms(projectRoot, "add", mobile.platforms);
17767
17925
  await applyAbsoluteNativeDeepLinks(mobile);
17926
+ await applyAbsoluteNativeDeviceCapabilities(projectRoot, mobile);
17768
17927
  await applyAbsoluteNativeBackgroundSync(projectRoot, mobile);
17769
17928
  }, sync = async (args) => {
17770
17929
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
@@ -17777,6 +17936,7 @@ var NOT_FOUND2 = -1, isRecord15 = (value) => typeof value === "object" && value
17777
17936
  await repairAbsoluteIosDevSession(projectRoot);
17778
17937
  await runCapacitorForPlatforms(projectRoot, "sync", platforms);
17779
17938
  await applyAbsoluteNativeDeepLinks(mobile, platforms);
17939
+ await applyAbsoluteNativeDeviceCapabilities(projectRoot, mobile, platforms);
17780
17940
  await applyAbsoluteNativeBackgroundSync(projectRoot, mobile, platforms);
17781
17941
  }, associations = async (args) => {
17782
17942
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
@@ -17995,6 +18155,9 @@ Mobile release transport checks failed.`);
17995
18155
  await writeAbsoluteCapacitorConfig(mobile, { projectRoot });
17996
18156
  await runCapacitorForPlatforms(projectRoot, "sync", ["android"]);
17997
18157
  await applyAbsoluteNativeDeepLinks(mobile, ["android"]);
18158
+ await applyAbsoluteNativeDeviceCapabilities(projectRoot, mobile, [
18159
+ "android"
18160
+ ]);
17998
18161
  await applyAbsoluteNativeBackgroundSync(projectRoot, mobile, [
17999
18162
  "android"
18000
18163
  ]);
@@ -18010,7 +18173,7 @@ Mobile release transport checks failed.`);
18010
18173
  const durationMs = Math.round(performance.now() - startedAt);
18011
18174
  console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} Android App Bundle in ${getDurationString(durationMs)}.`);
18012
18175
  console.log(`Artifact: ${release.artifactPath}`);
18013
- console.log(`Metadata: ${join50(release.releaseRoot, "release.json")}`);
18176
+ console.log(`Metadata: ${join51(release.releaseRoot, "release.json")}`);
18014
18177
  return release;
18015
18178
  } finally {
18016
18179
  sendTelemetryEvent("mobile:android-release-build", {
@@ -18097,6 +18260,9 @@ Mobile release transport checks failed.`);
18097
18260
  await writeAbsoluteCapacitorConfig(mobile, { projectRoot });
18098
18261
  await runCapacitorForPlatforms(projectRoot, "sync", ["ios"]);
18099
18262
  await applyAbsoluteNativeDeepLinks(mobile, ["ios"]);
18263
+ await applyAbsoluteNativeDeviceCapabilities(projectRoot, mobile, [
18264
+ "ios"
18265
+ ]);
18100
18266
  await applyAbsoluteNativeBackgroundSync(projectRoot, mobile, ["ios"]);
18101
18267
  await requireIosReleaseReady(mobile, projectRoot);
18102
18268
  const release = await buildAbsoluteIosRelease({
@@ -18110,7 +18276,7 @@ Mobile release transport checks failed.`);
18110
18276
  const durationMs = Math.round(performance.now() - startedAt);
18111
18277
  console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} iOS IPA ${release.metadata.marketingVersion}${release.metadata.buildNumber ? ` (${release.metadata.buildNumber})` : ""} in ${getDurationString(durationMs)}.`);
18112
18278
  console.log(`Artifact: ${release.artifactPath}`);
18113
- console.log(`Metadata: ${join50(release.releaseRoot, "release.json")}`);
18279
+ console.log(`Metadata: ${join51(release.releaseRoot, "release.json")}`);
18114
18280
  return release;
18115
18281
  } finally {
18116
18282
  sendTelemetryEvent("mobile:ios-release-build", {
@@ -18217,7 +18383,7 @@ Mobile release transport checks failed.`);
18217
18383
  checks.push({
18218
18384
  id: "sync.storage-schema",
18219
18385
  label: `Offline schema ${schema.components.map((component2) => `${component2.id}@${component2.version}`).join(", ")}`,
18220
- path: join50(projectRoot, "package.json"),
18386
+ path: join51(projectRoot, "package.json"),
18221
18387
  platform: "host",
18222
18388
  status: "pass"
18223
18389
  });
@@ -18225,7 +18391,7 @@ Mobile release transport checks failed.`);
18225
18391
  checks.push({
18226
18392
  id: "sync.storage-schema",
18227
18393
  label: "Offline schema metadata is invalid",
18228
- path: join50(projectRoot, "package.json"),
18394
+ path: join51(projectRoot, "package.json"),
18229
18395
  platform: "host",
18230
18396
  remediation: error instanceof Error ? error.message : String(error),
18231
18397
  status: "fail"
@@ -18379,11 +18545,11 @@ Emulator setup verification:`);
18379
18545
  });
18380
18546
  }, writeAndroidFailureArtifacts = async (options) => {
18381
18547
  await mkdir13(options.artifactRoot, { recursive: true });
18382
- const screenshot = options.session ? await options.session.screenshot(join50(options.artifactRoot, "android-failure.png")).catch(() => {
18548
+ const screenshot = options.session ? await options.session.screenshot(join51(options.artifactRoot, "android-failure.png")).catch(() => {
18383
18549
  return;
18384
18550
  }) : undefined;
18385
- const diagnosticPath = join50(options.artifactRoot, "android-failure.json");
18386
- await writeFile14(diagnosticPath, `${JSON.stringify({
18551
+ const diagnosticPath = join51(options.artifactRoot, "android-failure.json");
18552
+ await writeFile15(diagnosticPath, `${JSON.stringify({
18387
18553
  diagnostics: options.session?.diagnostics ?? [],
18388
18554
  error: options.error instanceof Error ? options.error.message : String(options.error),
18389
18555
  platform: "android",
@@ -18560,7 +18726,7 @@ Emulator setup verification:`);
18560
18726
  return result;
18561
18727
  }, writeIosFailureArtifacts = async (options) => {
18562
18728
  await mkdir13(options.artifactRoot, { recursive: true });
18563
- const screenshot = join50(options.artifactRoot, "ios-failure.png");
18729
+ const screenshot = join51(options.artifactRoot, "ios-failure.png");
18564
18730
  const screenshotResult = captureCommand4([
18565
18731
  options.xcrun,
18566
18732
  "simctl",
@@ -18569,8 +18735,8 @@ Emulator setup verification:`);
18569
18735
  "screenshot",
18570
18736
  screenshot
18571
18737
  ]);
18572
- const diagnosticPath = join50(options.artifactRoot, "ios-failure.json");
18573
- await writeFile14(diagnosticPath, `${JSON.stringify({
18738
+ const diagnosticPath = join51(options.artifactRoot, "ios-failure.json");
18739
+ await writeFile15(diagnosticPath, `${JSON.stringify({
18574
18740
  appId: options.appId,
18575
18741
  error: options.error instanceof Error ? options.error.message : String(options.error),
18576
18742
  platform: "ios",
@@ -18615,7 +18781,7 @@ Emulator setup verification:`);
18615
18781
  ], "iOS app launch");
18616
18782
  await waitForIosHmrClient({ https, port, timeoutMs });
18617
18783
  await mkdir13(artifactRoot, { recursive: true });
18618
- const screenshot = join50(artifactRoot, "ios-simulator.png");
18784
+ const screenshot = join51(artifactRoot, "ios-simulator.png");
18619
18785
  requireCapturedIosCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
18620
18786
  const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
18621
18787
  const report = {
@@ -18719,6 +18885,7 @@ var init_mobile = __esm(() => {
18719
18885
  init_capacitorProject();
18720
18886
  init_config();
18721
18887
  init_nativeDeepLinks();
18888
+ init_nativeDeviceCapabilities();
18722
18889
  init_nativeBackgroundSync();
18723
18890
  init_emulatorDoctor();
18724
18891
  init_emulatorInstaller();
@@ -18762,11 +18929,11 @@ var init_mobile = __esm(() => {
18762
18929
  "@capacitor/cli@8.5.0",
18763
18930
  "@capacitor/android@8.5.0",
18764
18931
  "@capacitor/ios@8.5.0",
18765
- "@absolutejs/devices@0.1.0",
18766
- "@absolutejs/devices-capacitor@0.2.0"
18932
+ "@absolutejs/devices@0.2.0",
18933
+ "@absolutejs/devices-capacitor@0.3.1"
18767
18934
  ];
18768
18935
  CAPACITOR_SYNC_PACKAGE_SPECS = [
18769
- "@absolutejs/sync-capacitor@0.8.0",
18936
+ "@absolutejs/sync-capacitor@0.9.0",
18770
18937
  "@capacitor-community/sqlite@8.1.1"
18771
18938
  ];
18772
18939
  });
@@ -18776,9 +18943,9 @@ var exports_typecheck = {};
18776
18943
  __export(exports_typecheck, {
18777
18944
  typecheck: () => typecheck
18778
18945
  });
18779
- import { resolve as resolve42, join as join51 } from "path";
18946
+ import { resolve as resolve42, join as join52 } from "path";
18780
18947
  import { existsSync as existsSync42, readFileSync as readFileSync40 } from "fs";
18781
- import { mkdir as mkdir14, writeFile as writeFile15 } from "fs/promises";
18948
+ import { mkdir as mkdir14, writeFile as writeFile16 } from "fs/promises";
18782
18949
  var isCommandService3 = (service) => service.kind === "command" || Array.isArray(service.command), resolveConfigPath = (configPath2) => resolve42(configPath2 ?? process.env.ABSOLUTE_CONFIG ?? "absolute.config.ts"), getTypecheckTargets = async (configPath2) => {
18783
18950
  if (!existsSync42(resolveConfigPath(configPath2))) {
18784
18951
  const defaultService = {};
@@ -18885,8 +19052,8 @@ Found ${errorCount} error${suffix}.`;
18885
19052
  console.error("\x1B[31m\u2717\x1B[0m vue-tsc is required for Vue type checking. Install it: bun add -d vue-tsc");
18886
19053
  process.exit(1);
18887
19054
  }
18888
- const vueTsconfigPath = join51(cacheDir, "tsconfig.vue-check.json");
18889
- await writeFile15(vueTsconfigPath, JSON.stringify({
19055
+ const vueTsconfigPath = join52(cacheDir, "tsconfig.vue-check.json");
19056
+ await writeFile16(vueTsconfigPath, JSON.stringify({
18890
19057
  compilerOptions: {
18891
19058
  rootDir: ".."
18892
19059
  },
@@ -18905,7 +19072,7 @@ Found ${errorCount} error${suffix}.`;
18905
19072
  ...base,
18906
19073
  "--incremental",
18907
19074
  "--tsBuildInfoFile",
18908
- join51(cacheDir, "vue-tsc.tsbuildinfo")
19075
+ join52(cacheDir, "vue-tsc.tsbuildinfo")
18909
19076
  ]);
18910
19077
  if (cached.exitCode === 0 || cached.output.length > 0)
18911
19078
  return cached;
@@ -18916,8 +19083,8 @@ Found ${errorCount} error${suffix}.`;
18916
19083
  console.error("\x1B[31m\u2717\x1B[0m @angular/compiler-cli is required for Angular type checking. Install it: bun add -d @angular/compiler-cli");
18917
19084
  process.exit(1);
18918
19085
  }
18919
- const angularTsconfigPath = join51(cacheDir, "tsconfig.angular-check.json");
18920
- await writeFile15(angularTsconfigPath, JSON.stringify({
19086
+ const angularTsconfigPath = join52(cacheDir, "tsconfig.angular-check.json");
19087
+ await writeFile16(angularTsconfigPath, JSON.stringify({
18921
19088
  angularCompilerOptions: {
18922
19089
  strictTemplates: true
18923
19090
  },
@@ -18936,8 +19103,8 @@ Found ${errorCount} error${suffix}.`;
18936
19103
  console.error("\x1B[31m\u2717\x1B[0m typescript is required for type checking. Install it: bun add -d typescript");
18937
19104
  process.exit(1);
18938
19105
  }
18939
- const tscConfigPath = join51(cacheDir, "tsconfig.typecheck.json");
18940
- return writeFile15(tscConfigPath, JSON.stringify({
19106
+ const tscConfigPath = join52(cacheDir, "tsconfig.typecheck.json");
19107
+ return writeFile16(tscConfigPath, JSON.stringify({
18941
19108
  compilerOptions: {
18942
19109
  rootDir: ".."
18943
19110
  },
@@ -18951,7 +19118,7 @@ Found ${errorCount} error${suffix}.`;
18951
19118
  resolve42(tscConfigPath),
18952
19119
  "--incremental",
18953
19120
  "--tsBuildInfoFile",
18954
- join51(cacheDir, "tsc.tsbuildinfo"),
19121
+ join52(cacheDir, "tsc.tsbuildinfo"),
18955
19122
  "--pretty"
18956
19123
  ]));
18957
19124
  }, buildSvelteCheck = async (cacheDir, svelteDir) => {
@@ -18960,8 +19127,8 @@ Found ${errorCount} error${suffix}.`;
18960
19127
  console.error("\x1B[31m\u2717\x1B[0m svelte-check is required for Svelte type checking. Install it: bun add -d svelte-check");
18961
19128
  process.exit(1);
18962
19129
  }
18963
- const svelteTsconfigPath = join51(cacheDir, "tsconfig.svelte-check.json");
18964
- await writeFile15(svelteTsconfigPath, JSON.stringify({
19130
+ const svelteTsconfigPath = join52(cacheDir, "tsconfig.svelte-check.json");
19131
+ await writeFile16(svelteTsconfigPath, JSON.stringify({
18965
19132
  extends: resolve42("tsconfig.json"),
18966
19133
  files: ABSOLUTE_TYPECHECK_FILES,
18967
19134
  include: [`../${svelteDir}/**/*`]