@absolutejs/absolute 0.20.0-beta.22 → 0.20.0-beta.24

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, ANDROID_PERMISSION_PATTERN, IOS_USAGE_DESCRIPTIONS, 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, IOS_PRIVACY_ACCESSED_API_REASONS, IOS_PRIVACY_ACCESSED_APIS, 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.`);
@@ -6781,15 +6781,37 @@ var DEVICES_PACKAGE = "@absolutejs/devices", CAPACITOR_ADAPTER = "@absolutejs/de
6781
6781
  if (!Array.isArray(permissions) || !permissions.every((permission) => typeof permission === "string" && ANDROID_PERMISSION_PATTERN.test(permission)))
6782
6782
  throw new TypeError(`${field}.permissions must contain Android permission names.`);
6783
6783
  return [...permissions];
6784
- }, iosUsageDescriptions = (value, field) => {
6784
+ }, iosPrivacyAccessedApis = (value, field) => {
6785
6785
  if (value === undefined)
6786
6786
  return;
6787
6787
  if (!object2(value))
6788
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)))
6789
+ const privacy = {};
6790
+ for (const api of IOS_PRIVACY_ACCESSED_APIS) {
6791
+ const reasons = value[api];
6792
+ if (reasons === undefined)
6793
+ continue;
6794
+ const supported = IOS_PRIVACY_ACCESSED_API_REASONS[api];
6795
+ if (!Array.isArray(reasons) || reasons.length === 0 || !reasons.every((reason) => typeof reason === "string" && supported.has(reason)))
6796
+ throw new TypeError(`${field} contains an unsupported API or reason.`);
6797
+ privacy[api] = [...reasons];
6798
+ }
6799
+ if (Object.keys(value).some((api) => !IOS_PRIVACY_ACCESSED_APIS.some((known) => known === api)))
6800
+ throw new TypeError(`${field} contains an unsupported API or reason.`);
6801
+ return privacy;
6802
+ }, iosNativeRequirements = (value, field) => {
6803
+ if (value === undefined)
6804
+ return;
6805
+ if (!object2(value))
6806
+ throw new TypeError(`${field} must be an object.`);
6807
+ const { privacyAccessedApis, usageDescriptions } = value;
6808
+ if (usageDescriptions !== undefined && (!Array.isArray(usageDescriptions) || !usageDescriptions.every((purpose) => typeof purpose === "string" && IOS_USAGE_DESCRIPTIONS.has(purpose))))
6791
6809
  throw new TypeError(`${field}.usageDescriptions contains an unsupported purpose.`);
6792
- return [...usageDescriptions];
6810
+ const privacy = iosPrivacyAccessedApis(privacyAccessedApis, `${field}.privacyAccessedApis`);
6811
+ return {
6812
+ ...privacy === undefined ? {} : { privacyAccessedApis: privacy },
6813
+ ...usageDescriptions === undefined ? {} : { usageDescriptions: [...usageDescriptions] }
6814
+ };
6793
6815
  }, parseProvider = (name, value) => {
6794
6816
  if (!IDENTIFIER_PATTERN.test(name))
6795
6817
  throw new TypeError("Device capability names must be identifiers.");
@@ -6810,10 +6832,10 @@ var DEVICES_PACKAGE = "@absolutejs/devices", CAPACITOR_ADAPTER = "@absolutejs/de
6810
6832
  throw new TypeError(`${name}.native must be an object.`);
6811
6833
  const { android, ios } = nativeMetadata;
6812
6834
  const permissions = androidPermissions(android, `${name}.native.android`);
6813
- const usageDescriptions = iosUsageDescriptions(ios, `${name}.native.ios`);
6835
+ const iosRequirements = iosNativeRequirements(ios, `${name}.native.ios`);
6814
6836
  native = {
6815
6837
  ...permissions === undefined ? {} : { android: { permissions } },
6816
- ...usageDescriptions === undefined ? {} : { ios: { usageDescriptions } }
6838
+ ...iosRequirements === undefined ? {} : { ios: iosRequirements }
6817
6839
  };
6818
6840
  }
6819
6841
  return {
@@ -6822,14 +6844,32 @@ var DEVICES_PACKAGE = "@absolutejs/devices", CAPACITOR_ADAPTER = "@absolutejs/de
6822
6844
  ...native === undefined ? {} : { native },
6823
6845
  packages: [...value.packages]
6824
6846
  };
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) => {
6847
+ }, absoluteDeviceNativeRequirements = (plan) => {
6848
+ const privacy = plan.capabilities.reduce((requirements, name) => {
6849
+ for (const api of IOS_PRIVACY_ACCESSED_APIS) {
6850
+ const reasons = plan.providers[name]?.native?.ios?.privacyAccessedApis?.[api] ?? [];
6851
+ if (reasons.length === 0)
6852
+ continue;
6853
+ const current = requirements[api] ?? new Set;
6854
+ for (const reason of reasons)
6855
+ current.add(reason);
6856
+ requirements[api] = current;
6857
+ }
6858
+ return requirements;
6859
+ }, {});
6860
+ return {
6861
+ androidPermissions: [
6862
+ ...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.android?.permissions ?? []))
6863
+ ].sort(),
6864
+ iosPrivacyAccessedApis: IOS_PRIVACY_ACCESSED_APIS.flatMap((api) => {
6865
+ const reasons = privacy[api];
6866
+ return reasons ? [{ api, reasons: [...reasons].sort() }] : [];
6867
+ }),
6868
+ iosUsageDescriptions: [
6869
+ ...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.ios?.usageDescriptions ?? []))
6870
+ ].sort()
6871
+ };
6872
+ }, loadAbsoluteDeviceCapabilityProviders = (projectRoot) => {
6833
6873
  const path = join20(resolve16(projectRoot), "node_modules", CAPACITOR_ADAPTER, "package.json");
6834
6874
  const manifest = readJson(path);
6835
6875
  const { absolutejs } = manifest;
@@ -6953,6 +6993,12 @@ var init_deviceCapabilities = __esm(() => {
6953
6993
  "photo-library",
6954
6994
  "photo-library-add"
6955
6995
  ]);
6996
+ IOS_PRIVACY_ACCESSED_API_REASONS = {
6997
+ NSPrivacyAccessedAPICategoryFileTimestamp: new Set(["C617.1"])
6998
+ };
6999
+ IOS_PRIVACY_ACCESSED_APIS = [
7000
+ "NSPrivacyAccessedAPICategoryFileTimestamp"
7001
+ ];
6956
7002
  });
6957
7003
 
6958
7004
  // src/mobile/buildPipeline.ts
@@ -16469,7 +16515,7 @@ var init_nativeDeepLinks = () => {};
16469
16515
  // src/mobile/nativeDeviceCapabilities.ts
16470
16516
  import { readFile as readFile12, rename as rename9, writeFile as writeFile10 } from "fs/promises";
16471
16517
  import { join as join47 } from "path";
16472
- 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) => {
16518
+ var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->", END_MARKER2 = "<!-- absolutejs:device-capabilities:end -->", NOT_FOUND2 = -1, IOS_PRIVACY_FILE_REFERENCE = "A85D0C000000000000000001", IOS_PRIVACY_BUILD_FILE = "A85D0C000000000000000002", escapeXml2 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;"), writeChangedFile2 = async (path, source) => {
16473
16519
  const current = await readFile12(path, "utf8");
16474
16520
  if (current === source)
16475
16521
  return false;
@@ -16477,6 +16523,14 @@ var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->", END_MARKER2
16477
16523
  await writeFile10(temporary, source, { flag: "wx" });
16478
16524
  await rename9(temporary, path);
16479
16525
  return true;
16526
+ }, optionalSource = async (path) => {
16527
+ try {
16528
+ return await readFile12(path, "utf8");
16529
+ } catch (error) {
16530
+ if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
16531
+ return null;
16532
+ throw error;
16533
+ }
16480
16534
  }, managed = (source, region, insertion) => {
16481
16535
  const start2 = source.indexOf(START_MARKER2);
16482
16536
  const end = source.indexOf(END_MARKER2);
@@ -16505,6 +16559,129 @@ var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->", END_MARKER2
16505
16559
  if (purpose === "location-always")
16506
16560
  return `${appName} does not track location in the background; this description supports the foreground location provider required by the native runtime.`;
16507
16561
  return `${appName} adds to your photo library only for photo actions you choose.`;
16562
+ }, privacyEntries = (requirements) => requirements.iosPrivacyAccessedApis.map(({ api, reasons }) => ` <dict>
16563
+ <key>NSPrivacyAccessedAPIType</key>
16564
+ <string>${escapeXml2(api)}</string>
16565
+ <key>NSPrivacyAccessedAPITypeReasons</key>
16566
+ <array>
16567
+ ${reasons.map((reason) => ` <string>${escapeXml2(reason)}</string>`).join(`
16568
+ `)}
16569
+ </array>
16570
+ </dict>`).join(`
16571
+ `), privacyManifestSource = (source, requirements) => {
16572
+ const entries = privacyEntries(requirements);
16573
+ const wholeRegion = entries ? ` ${START_MARKER2}
16574
+ <key>NSPrivacyAccessedAPITypes</key>
16575
+ <array>
16576
+ ${entries}
16577
+ </array>
16578
+ ${END_MARKER2}
16579
+ ` : "";
16580
+ if (source === null)
16581
+ return entries ? `<?xml version="1.0" encoding="UTF-8"?>
16582
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
16583
+ <plist version="1.0">
16584
+ <dict>
16585
+ ${wholeRegion} <key>NSPrivacyCollectedDataTypes</key>
16586
+ <array/>
16587
+ <key>NSPrivacyTracking</key>
16588
+ <false/>
16589
+ </dict>
16590
+ </plist>
16591
+ ` : null;
16592
+ const start2 = source.indexOf(START_MARKER2);
16593
+ if (start2 !== NOT_FOUND2) {
16594
+ const end = source.indexOf(END_MARKER2);
16595
+ if (end === NOT_FOUND2)
16596
+ throw new TypeError("AbsoluteJS device-capability ownership markers are malformed.");
16597
+ const owned = source.slice(start2, end);
16598
+ const ownsWholeKey = owned.includes("NSPrivacyAccessedAPITypes");
16599
+ let region = "";
16600
+ if (ownsWholeKey)
16601
+ region = wholeRegion;
16602
+ else if (entries)
16603
+ region = ` ${START_MARKER2}
16604
+ ${entries}
16605
+ ${END_MARKER2}
16606
+ `;
16607
+ return managed(source, region, source.lastIndexOf("</dict>"));
16608
+ }
16609
+ const key = source.indexOf("<key>NSPrivacyAccessedAPITypes</key>");
16610
+ if (key === NOT_FOUND2)
16611
+ return managed(source, wholeRegion, source.lastIndexOf("</dict>"));
16612
+ if (!entries)
16613
+ return source;
16614
+ const array = source.indexOf("<array>", key);
16615
+ if (array === NOT_FOUND2)
16616
+ throw new TypeError("iOS PrivacyInfo.xcprivacy has a malformed NSPrivacyAccessedAPITypes value.");
16617
+ const insertion = source.indexOf(`
16618
+ `, array);
16619
+ if (insertion === NOT_FOUND2)
16620
+ throw new TypeError("iOS PrivacyInfo.xcprivacy array is malformed.");
16621
+ return managed(source, ` ${START_MARKER2}
16622
+ ${entries}
16623
+ ${END_MARKER2}
16624
+ `, insertion + 1);
16625
+ }, writeIosPrivacyManifest = async (path, current, source) => {
16626
+ if (source === null)
16627
+ return false;
16628
+ if (current !== null)
16629
+ return writeChangedFile2(path, source);
16630
+ await writeFile10(path, source, { flag: "wx" });
16631
+ return true;
16632
+ }, configureIosPrivacyProject = async (config, requirements) => {
16633
+ if (requirements.iosPrivacyAccessedApis.length === 0)
16634
+ return false;
16635
+ const projectPath = join47(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
16636
+ const project = await readFile12(projectPath, "utf8");
16637
+ return writeChangedFile2(projectPath, addIosPrivacyProjectReference(project));
16638
+ }, addIosPrivacyProjectReference = (source) => {
16639
+ const fileMatch = source.match(/([A-F0-9]{24}) \/\* PrivacyInfo\.xcprivacy \*\/ = \{isa = PBXFileReference;/u);
16640
+ const fileReference = fileMatch?.[1] ?? IOS_PRIVACY_FILE_REFERENCE;
16641
+ const buildMatch = source.match(/([A-F0-9]{24}) \/\* PrivacyInfo\.xcprivacy in Resources \*\/ = \{isa = PBXBuildFile;/u);
16642
+ const buildFile = buildMatch?.[1] ?? IOS_PRIVACY_BUILD_FILE;
16643
+ if (!fileMatch && source.includes(fileReference) || !buildMatch && source.includes(buildFile))
16644
+ throw new TypeError("AbsoluteJS iOS privacy-manifest identifiers collide.");
16645
+ let next = source;
16646
+ if (!buildMatch) {
16647
+ const marker = "/* End PBXBuildFile section */";
16648
+ const index = next.indexOf(marker);
16649
+ if (index === NOT_FOUND2)
16650
+ throw new TypeError("Could not find the iOS PBXBuildFile section.");
16651
+ next = `${next.slice(0, index)} ${buildFile} /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = ${fileReference} /* PrivacyInfo.xcprivacy */; };
16652
+ ${next.slice(index)}`;
16653
+ }
16654
+ if (!fileMatch) {
16655
+ const marker = "/* End PBXFileReference section */";
16656
+ const index = next.indexOf(marker);
16657
+ if (index === NOT_FOUND2)
16658
+ throw new TypeError("Could not find the iOS PBXFileReference section.");
16659
+ next = `${next.slice(0, index)} ${fileReference} /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
16660
+ ${next.slice(index)}`;
16661
+ }
16662
+ const groupsStart = next.indexOf("/* Begin PBXGroup section */");
16663
+ const groupsEnd = next.indexOf("/* End PBXGroup section */");
16664
+ const groups = next.slice(groupsStart, groupsEnd);
16665
+ if (!groups.includes(`${fileReference} /* PrivacyInfo.xcprivacy */`)) {
16666
+ const appGroup = groups.match(/[A-F0-9]{24} \/\* App \*\/ = \{\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = \(\n/u);
16667
+ if (!appGroup || appGroup.index === undefined)
16668
+ throw new TypeError("Could not find the iOS App PBXGroup.");
16669
+ const index = groupsStart + appGroup.index + appGroup[0].length;
16670
+ next = `${next.slice(0, index)} ${fileReference} /* PrivacyInfo.xcprivacy */,
16671
+ ${next.slice(index)}`;
16672
+ }
16673
+ const resourcesStart = next.indexOf("/* Begin PBXResourcesBuildPhase section */");
16674
+ const resourcesEnd = next.indexOf("/* End PBXResourcesBuildPhase section */");
16675
+ const resources = next.slice(resourcesStart, resourcesEnd);
16676
+ if (!resources.includes(`${buildFile} /* PrivacyInfo.xcprivacy in Resources */`)) {
16677
+ const files = resources.match(/isa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = \d+;\n\t\t\tfiles = \(\n/u);
16678
+ if (!files || files.index === undefined)
16679
+ throw new TypeError("Could not find the iOS Resources build phase.");
16680
+ const index = resourcesStart + files.index + files[0].length;
16681
+ next = `${next.slice(0, index)} ${buildFile} /* PrivacyInfo.xcprivacy in Resources */,
16682
+ ${next.slice(index)}`;
16683
+ }
16684
+ return next;
16508
16685
  }, configureIos2 = async (config, plan) => {
16509
16686
  const path = join47(config.nativeProjectDirectory, "ios/App/App/Info.plist");
16510
16687
  const source = await readFile12(path, "utf8");
@@ -16516,7 +16693,15 @@ var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->", END_MARKER2
16516
16693
  ${content}
16517
16694
  ${END_MARKER2}
16518
16695
  ` : "";
16519
- return writeChangedFile2(path, managed(source, region, source.lastIndexOf("</dict>")));
16696
+ const infoChanged = await writeChangedFile2(path, managed(source, region, source.lastIndexOf("</dict>")));
16697
+ const privacyPath = join47(config.nativeProjectDirectory, "ios/App/App/PrivacyInfo.xcprivacy");
16698
+ const privacyCurrent = await optionalSource(privacyPath);
16699
+ const privacySource = privacyManifestSource(privacyCurrent, requirements);
16700
+ const [privacyChanged, projectChanged] = await Promise.all([
16701
+ writeIosPrivacyManifest(privacyPath, privacyCurrent, privacySource),
16702
+ configureIosPrivacyProject(config, requirements)
16703
+ ]);
16704
+ return infoChanged || privacyChanged || projectChanged;
16520
16705
  }, configureAndroid2 = async (config, plan) => {
16521
16706
  const path = join47(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
16522
16707
  const source = await readFile12(path, "utf8");
@@ -17706,24 +17891,25 @@ var init_iosConformance = __esm(() => {
17706
17891
  HMR_LINE = new RegExp(String.raw`\[hmr:ios\]\s+([^\n]*?)\s+(applied in|falling back to reload after|failed after)\s+(\d+)ms(?:; server\s+(\d+)ms, client\s+(\d+)ms)?`, "u");
17707
17892
  });
17708
17893
 
17709
- // src/mobile/iosTestReport.ts
17894
+ // src/mobile/nativeTestReport.ts
17710
17895
  import { mkdir as mkdir13, readFile as readFile18, writeFile as writeFile15 } from "fs/promises";
17711
17896
  import { join as join51 } from "path";
17712
- var MANUAL_CHECKS, secretPattern, bearerPattern, coordinatePattern, sanitizeIosReportText = (value) => value.replace(bearerPattern, "Bearer [REDACTED]").replace(secretPattern, "$1$2[REDACTED]").replace(coordinatePattern, "$1$2[REDACTED]").replace(/(https?:\/\/[^\s?#]+)[?#][^\s]*/giu, "$1?[REDACTED]"), markdownCell = (value) => sanitizeIosReportText(value).replaceAll("|", "\\|").replaceAll(`
17713
- `, "<br>"), createAbsoluteIosPartnerReport = (options) => {
17714
- const { run } = options;
17715
- const evidence = run.screenshot ? `Simulator screenshot: ${run.screenshot}` : undefined;
17897
+ var secretPattern, bearerPattern, coordinatePattern, sanitizeNativeReportText = (value) => value.replace(bearerPattern, "Bearer [REDACTED]").replace(secretPattern, "$1$2[REDACTED]").replace(coordinatePattern, "$1$2[REDACTED]").replace(/(https?:\/\/[^\s?#]+)[?#][^\s]*/giu, "$1?[REDACTED]"), markdownCell = (value) => sanitizeNativeReportText(value).replaceAll("|", "\\|").replaceAll(`
17898
+ `, "<br>"), createAbsoluteNativeAutomatedChecks = (run) => {
17899
+ const target = `${run.targetKind} ${run.targetId}`;
17900
+ const evidence = run.screenshot ? `Screenshot: ${run.screenshot}` : undefined;
17716
17901
  let hmrResult = "NOT_RUN";
17717
17902
  if (run.hmr)
17718
17903
  hmrResult = run.hmr.outcome === "failed" ? "FAIL" : "PASS";
17719
- const automatedChecks = [
17904
+ const routeDetails = run.routes?.length ? ` Routes: ${run.routes.join(", ")}.` : "";
17905
+ return [
17720
17906
  {
17721
- details: `Captured macOS, Xcode, Bun, and AbsoluteJS versions for simulator ${run.udid}.`,
17907
+ details: `Captured host, toolchain, Bun, and AbsoluteJS metadata for ${target}.`,
17722
17908
  id: "AUTO-SETUP-01",
17723
17909
  result: "PASS"
17724
17910
  },
17725
17911
  {
17726
- details: run.status === "pass" ? `The app was installed, launched, and connected to native HMR in ${run.durationMs}ms.` : `The automated simulator run failed after ${run.durationMs}ms: ${run.error ?? "No error detail was available."}`,
17912
+ details: run.status === "pass" ? `The app launched and connected to native HMR in ${run.durationMs}ms.${routeDetails}` : `The automated native run failed after ${run.durationMs}ms: ${run.error ?? "No error detail was available."}`,
17727
17913
  ...evidence ? { evidence } : {},
17728
17914
  id: "AUTO-DEV-01",
17729
17915
  result: run.status === "pass" ? "PASS" : "FAIL"
@@ -17734,55 +17920,46 @@ var MANUAL_CHECKS, secretPattern, bearerPattern, coordinatePattern, sanitizeIosR
17734
17920
  result: hmrResult
17735
17921
  },
17736
17922
  {
17737
- details: run.screenshot ? "A simulator screenshot was captured. Visually review it before sharing this directory." : "No simulator screenshot was captured.",
17923
+ details: run.screenshot ? "A target screenshot was captured. Visually review it before sharing this directory." : "No target screenshot was captured.",
17738
17924
  ...evidence ? { evidence } : {},
17739
17925
  id: "AUTO-ARTIFACT-01",
17740
17926
  result: run.screenshot ? "PASS" : "FAIL"
17741
17927
  }
17742
17928
  ];
17743
- const manualChecks = MANUAL_CHECKS.map(([id, details]) => ({
17929
+ }, createAbsoluteNativeTestReport = (options) => ({
17930
+ automatedChecks: options.automatedChecks ?? createAbsoluteNativeAutomatedChecks(options.run),
17931
+ generatedAt: options.generatedAt ?? new Date().toISOString(),
17932
+ manualChecks: options.manualChecks.map(([id, details]) => ({
17744
17933
  details,
17745
17934
  id,
17746
17935
  result: "NOT_RUN"
17747
- }));
17748
- return {
17749
- automatedChecks,
17750
- generatedAt: options.generatedAt ?? new Date().toISOString(),
17751
- manualChecks,
17752
- metadata: {
17753
- absolutejsVersion: options.absolutejsVersion,
17754
- bunVersion: options.bunVersion,
17755
- macosVersion: options.macosVersion,
17756
- provider: "capacitor",
17757
- xcodeVersion: options.xcodeVersion
17758
- },
17759
- overallResult: run.status === "fail" ? "FAIL" : "INCOMPLETE",
17760
- platform: "ios",
17761
- reportVersion: 1,
17762
- run
17763
- };
17764
- }, readPackageVersionForIosReport = async (packageJsonPath) => {
17936
+ })),
17937
+ metadata: options.metadata,
17938
+ overallResult: options.run.status === "fail" ? "FAIL" : "INCOMPLETE",
17939
+ platform: options.run.platform,
17940
+ reportVersion: 1,
17941
+ run: options.run
17942
+ }), readPackageVersionForNativeReport = async (packageJsonPath) => {
17765
17943
  const manifest = JSON.parse(await readFile18(packageJsonPath, "utf8"));
17766
17944
  if (typeof manifest !== "object" || manifest === null)
17767
17945
  return "unknown";
17768
17946
  const version2 = Reflect.get(manifest, "version");
17769
17947
  return typeof version2 === "string" ? version2 : "unknown";
17770
- }, renderAbsoluteIosPartnerReport = (report) => {
17948
+ }, renderAbsoluteNativeTestReport = (report) => {
17771
17949
  const table = (checks) => checks.map((check2) => `| ${check2.id} | ${check2.result} | ${markdownCell(check2.details)} | ${markdownCell(check2.evidence ?? "")} |`).join(`
17772
17950
  `);
17773
- return `# AbsoluteJS iOS test report
17951
+ const metadata = Object.entries(report.metadata).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `- ${key}: ${markdownCell(value)}`).join(`
17952
+ `);
17953
+ return `# AbsoluteJS ${report.platform} test report
17774
17954
 
17775
17955
  - Overall result: ${report.overallResult}
17776
17956
  - Generated: ${report.generatedAt}
17777
- - AbsoluteJS: ${markdownCell(report.metadata.absolutejsVersion)}
17778
- - macOS: ${markdownCell(report.metadata.macosVersion)}
17779
- - Xcode: ${markdownCell(report.metadata.xcodeVersion)}
17780
- - Bun: ${markdownCell(report.metadata.bunVersion)}
17781
17957
  - App bundle ID: ${markdownCell(report.run.appId)}
17782
- - Simulator UDID: ${markdownCell(report.run.udid)}
17958
+ - Target: ${report.run.targetKind} ${markdownCell(report.run.targetId)}
17783
17959
  - Automated run: ${report.run.status.toUpperCase()} (${report.run.durationMs}ms)
17960
+ ${metadata}
17784
17961
 
17785
- ` + `This report is local and is never uploaded by AbsoluteJS. Before sharing its directory, visually inspect screenshots and logs. Never add passwords, signing material, tokens, cookies, private Sync data, or exact coordinates.
17962
+ This report is local and is never uploaded by AbsoluteJS. Before sharing its directory, visually inspect screenshots and logs. Never add passwords, signing material, tokens, cookies, private Sync data, or exact coordinates.
17786
17963
 
17787
17964
  ## Automated checks
17788
17965
 
@@ -17790,26 +17967,54 @@ var MANUAL_CHECKS, secretPattern, bearerPattern, coordinatePattern, sanitizeIosR
17790
17967
  | --- | --- | --- | --- |
17791
17968
  ${table(report.automatedChecks)}
17792
17969
 
17793
- ## Partner checklist
17970
+ ## Manual checklist
17794
17971
 
17795
- Replace each \`NOT_RUN\` with \`PASS\`, \`FAIL\`, or \`SKIPPED\` after following \`docs/IOS_MACOS_TESTING.md\`. A failure must name sanitized evidence and state actual versus expected behavior.
17972
+ Replace each \`NOT_RUN\` with \`PASS\`, \`FAIL\`, or \`SKIPPED\` after completing the platform runbook. A failure must name sanitized evidence and state actual versus expected behavior.
17796
17973
 
17797
17974
  | Test ID | Result | Observed result / timing | Evidence or failure details |
17798
17975
  | --- | --- | --- | --- |
17799
17976
  ${table(report.manualChecks)}
17800
17977
  `;
17801
- }, writeAbsoluteIosPartnerReport = async (directory, report) => {
17978
+ }, writeAbsoluteNativeTestReport = async (directory, report) => {
17802
17979
  await mkdir13(directory, { recursive: true });
17803
17980
  const jsonPath = join51(directory, "report.json");
17804
17981
  const markdownPath = join51(directory, "report.md");
17805
17982
  await Promise.all([
17806
17983
  writeFile15(jsonPath, `${JSON.stringify(report, null, 2)}
17807
17984
  `),
17808
- writeFile15(markdownPath, renderAbsoluteIosPartnerReport(report))
17985
+ writeFile15(markdownPath, renderAbsoluteNativeTestReport(report))
17809
17986
  ]);
17810
17987
  return { directory, jsonPath, markdownPath };
17811
17988
  };
17989
+ var init_nativeTestReport = __esm(() => {
17990
+ secretPattern = /(authorization|access[_ -]?token|refresh[_ -]?token|socket[_ -]?ticket|password|cookie)(\s*[=:]\s*)([^\s,;]+)/giu;
17991
+ bearerPattern = /bearer\s+[^\s,;]+/giu;
17992
+ coordinatePattern = /\b(latitude|longitude|lat|lng)(\s*[=:]\s*)-?\d+(?:\.\d+)?/giu;
17993
+ });
17994
+
17995
+ // src/mobile/iosTestReport.ts
17996
+ var MANUAL_CHECKS, readPackageVersionForIosReport, sanitizeIosReportText, writeAbsoluteIosPartnerReport, createAbsoluteIosPartnerReport = (options) => {
17997
+ const { udid, ...run } = options.run;
17998
+ return createAbsoluteNativeTestReport({
17999
+ ...options.generatedAt ? { generatedAt: options.generatedAt } : {},
18000
+ manualChecks: MANUAL_CHECKS,
18001
+ metadata: {
18002
+ absolutejsVersion: options.absolutejsVersion,
18003
+ bunVersion: options.bunVersion,
18004
+ macosVersion: options.macosVersion,
18005
+ provider: "capacitor",
18006
+ xcodeVersion: options.xcodeVersion
18007
+ },
18008
+ run: {
18009
+ ...run,
18010
+ platform: "ios",
18011
+ targetId: udid,
18012
+ targetKind: "simulator"
18013
+ }
18014
+ });
18015
+ };
17812
18016
  var init_iosTestReport = __esm(() => {
18017
+ init_nativeTestReport();
17813
18018
  MANUAL_CHECKS = [
17814
18019
  [
17815
18020
  "SETUP-01",
@@ -17822,6 +18027,14 @@ var init_iosTestReport = __esm(() => {
17822
18027
  ["DEV-01", "Record cold and warm bun dev startup timings."],
17823
18028
  ["DEV-02", "Complete route traversal, HMR, relaunch, and recovery checks."],
17824
18029
  ["CAP-01", "Complete automatic device-capability provisioning checks."],
18030
+ ...Array.from({ length: 8 }, (_, index) => [
18031
+ `FILES-${String(index + 1).padStart(2, "0")}`,
18032
+ `Complete provider-neutral Documents runbook check FILES-${String(index + 1).padStart(2, "0")}.`
18033
+ ]),
18034
+ ...Array.from({ length: 8 }, (_, index) => [
18035
+ `NOTIF-${String(index + 1).padStart(2, "0")}`,
18036
+ `Complete provider-neutral Local Notifications runbook check NOTIF-${String(index + 1).padStart(2, "0")}.`
18037
+ ]),
17825
18038
  ...Array.from({ length: 14 }, (_, index) => [
17826
18039
  `LOC-${String(index + 1).padStart(2, "0")}`,
17827
18040
  `Complete foreground-location runbook check LOC-${String(index + 1).padStart(2, "0")} without recording exact coordinates.`
@@ -17847,9 +18060,65 @@ var init_iosTestReport = __esm(() => {
17847
18060
  "Review this directory for sensitive content and complete every row."
17848
18061
  ]
17849
18062
  ];
17850
- secretPattern = /(authorization|access[_ -]?token|refresh[_ -]?token|socket[_ -]?ticket|password|cookie)(\s*[=:]\s*)([^\s,;]+)/giu;
17851
- bearerPattern = /bearer\s+[^\s,;]+/giu;
17852
- coordinatePattern = /\b(latitude|longitude|lat|lng)(\s*[=:]\s*)-?\d+(?:\.\d+)?/giu;
18063
+ readPackageVersionForIosReport = readPackageVersionForNativeReport;
18064
+ sanitizeIosReportText = sanitizeNativeReportText;
18065
+ writeAbsoluteIosPartnerReport = writeAbsoluteNativeTestReport;
18066
+ });
18067
+
18068
+ // src/mobile/androidTestReport.ts
18069
+ var MANUAL_CHECKS2, createAbsoluteAndroidTestReport = (options) => {
18070
+ const { serial, ...run } = options.run;
18071
+ return createAbsoluteNativeTestReport({
18072
+ ...options.generatedAt ? { generatedAt: options.generatedAt } : {},
18073
+ manualChecks: MANUAL_CHECKS2,
18074
+ metadata: {
18075
+ absolutejsVersion: options.absolutejsVersion,
18076
+ adbVersion: options.adbVersion,
18077
+ bunVersion: options.bunVersion,
18078
+ host: options.host,
18079
+ provider: "capacitor"
18080
+ },
18081
+ run: {
18082
+ ...run,
18083
+ platform: "android",
18084
+ targetId: serial,
18085
+ targetKind: serial.startsWith("emulator-") ? "emulator" : "device"
18086
+ }
18087
+ });
18088
+ };
18089
+ var init_androidTestReport = __esm(() => {
18090
+ init_nativeTestReport();
18091
+ MANUAL_CHECKS2 = [
18092
+ [
18093
+ "SETUP-01",
18094
+ "Confirm Android SDK, emulator/device, Bun, and package versions."
18095
+ ],
18096
+ ["DEV-01", "Record cold and warm native startup timings."],
18097
+ ["DEV-02", "Complete route traversal, HMR, relaunch, and recovery checks."],
18098
+ ["CAP-01", "Complete automatic device-capability provisioning checks."],
18099
+ [
18100
+ "FILES-01",
18101
+ "Complete provider-neutral file pick, export, open, and cleanup checks."
18102
+ ],
18103
+ ...Array.from({ length: 6 }, (_, index) => [
18104
+ `NOTIF-${String(index + 1).padStart(2, "0")}`,
18105
+ `Complete provider-neutral Local Notifications check NOTIF-${String(index + 1).padStart(2, "0")}.`
18106
+ ]),
18107
+ [
18108
+ "AUTH-01",
18109
+ "Complete system-browser sign-in, callback, restore, and sign-out checks."
18110
+ ],
18111
+ [
18112
+ "SYNC-01",
18113
+ "Complete online, offline, reconnect, isolation, and conflict checks."
18114
+ ],
18115
+ ["BGSYNC-01", "Complete WorkManager background Sync acceptance."],
18116
+ ["BUILD-01", "Pass release doctor and produce a signed AAB."],
18117
+ [
18118
+ "REPORT-01",
18119
+ "Review this directory for sensitive content and complete every row."
18120
+ ]
18121
+ ];
17853
18122
  });
17854
18123
 
17855
18124
  // src/mobile/releasePublisher.ts
@@ -18728,7 +18997,8 @@ Emulator setup verification:`);
18728
18997
  const routes = valuesAfter(args, "--route");
18729
18998
  if (routes.length === 0)
18730
18999
  routes.push(mobile.entry);
18731
- const artifactRoot = safeArtifactRoot(projectRoot, valueAfter(args, "--artifacts"));
19000
+ const reportRoot = nativeReportRoot(args, projectRoot, "android");
19001
+ const artifactRoot = reportRoot ?? safeArtifactRoot(projectRoot, valueAfter(args, "--artifacts"));
18732
19002
  const startedAt = performance.now();
18733
19003
  let session;
18734
19004
  try {
@@ -18767,6 +19037,30 @@ Emulator setup verification:`);
18767
19037
  console.log(JSON.stringify(report, null, 2));
18768
19038
  else
18769
19039
  printAndroidTestReport(report);
19040
+ const screenshot = reportRoot ? await session.screenshot(join52(artifactRoot, "android-emulator.png")) : undefined;
19041
+ await writeRequestedAndroidReport({
19042
+ adb,
19043
+ args,
19044
+ projectRoot,
19045
+ run: {
19046
+ appId: mobile.appId,
19047
+ durationMs: report.durationMs,
19048
+ ...hmrApply ? {
19049
+ hmr: {
19050
+ clientMs: hmrApply.clientMs,
19051
+ durationMs: hmrApply.duration,
19052
+ outcome: hmrApply.outcome,
19053
+ serverMs: hmrApply.serverMs
19054
+ }
19055
+ } : {},
19056
+ hmrConnected: true,
19057
+ port,
19058
+ routes: checks.map(({ route }) => route),
19059
+ ...screenshot ? { screenshot } : {},
19060
+ serial,
19061
+ status: "pass"
19062
+ }
19063
+ });
18770
19064
  return report;
18771
19065
  } catch (error) {
18772
19066
  const durationMs = Math.round(performance.now() - startedAt);
@@ -18785,6 +19079,22 @@ Emulator setup verification:`);
18785
19079
  serial,
18786
19080
  session
18787
19081
  });
19082
+ await writeRequestedAndroidReport({
19083
+ adb,
19084
+ args,
19085
+ projectRoot,
19086
+ run: {
19087
+ appId: mobile.appId,
19088
+ durationMs,
19089
+ error: sanitizeNativeReportText(error instanceof Error ? error.message : String(error)),
19090
+ hmrConnected: false,
19091
+ port,
19092
+ routes,
19093
+ ...screenshot ? { screenshot } : {},
19094
+ serial,
19095
+ status: "fail"
19096
+ }
19097
+ });
18788
19098
  throw new Error(`${error instanceof Error ? error.message : String(error)} Failure diagnostics: ${diagnosticPath}${screenshot ? `; screenshot: ${screenshot}` : ""}`, { cause: error });
18789
19099
  } finally {
18790
19100
  await session?.close();
@@ -18875,7 +19185,7 @@ Emulator setup verification:`);
18875
19185
  if (!selected)
18876
19186
  throw new TypeError("No ready AbsoluteJS iOS simulator was found. Start `bun dev` and wait for the iOS target to become ready.");
18877
19187
  return selected;
18878
- }, requireCapturedIosCommand = (command, label) => {
19188
+ }, requireCapturedCommand = (command, label) => {
18879
19189
  const result = captureCommand4(command);
18880
19190
  if (result.exitCode !== 0)
18881
19191
  throw new Error(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `status ${result.exitCode}`}`);
@@ -18906,37 +19216,56 @@ Emulator setup verification:`);
18906
19216
  diagnosticPath,
18907
19217
  screenshot: screenshotResult.exitCode === 0 ? screenshot : undefined
18908
19218
  };
18909
- }, iosReportRoot = (args, projectRoot) => {
19219
+ }, nativeReportRoot = (args, projectRoot, platform6) => {
18910
19220
  const index = args.indexOf("--report");
18911
19221
  if (index === NOT_FOUND3)
18912
19222
  return;
18913
19223
  const candidate = args[index + 1];
18914
19224
  const explicit = candidate?.startsWith("--") ? undefined : candidate;
18915
19225
  const timestamp = new Date().toISOString().replaceAll(":", "-");
18916
- return safeArtifactRoot(projectRoot, explicit ?? `.absolutejs/mobile/test-reports/ios-${timestamp}`);
18917
- }, iosReportMetadata = async (xcrun) => {
18918
- const xcodebuildPath = requireCapturedIosCommand([xcrun, "--find", "xcodebuild"], "Xcode version inspection").stdout.trim();
18919
- const xcodeVersion = requireCapturedIosCommand([xcodebuildPath, "-version"], "Xcode version inspection").stdout.trim();
18920
- const macosVersion = requireCapturedIosCommand(["/usr/bin/sw_vers", "-productVersion"], "macOS version inspection").stdout.trim();
19226
+ return safeArtifactRoot(projectRoot, explicit ?? `.absolutejs/mobile/test-reports/${platform6}-${timestamp}`);
19227
+ }, absolutejsVersionForReport = async () => {
18921
19228
  let absolutejsVersion = process.env.ABSOLUTE_VERSION ?? "unknown";
18922
- for (const candidate of [
19229
+ const versions = await Promise.all([
18923
19230
  resolve41(import.meta.dir, "..", "..", "package.json"),
18924
19231
  resolve41(import.meta.dir, "..", "..", "..", "package.json")
18925
- ]) {
18926
- const version2 = await readPackageVersionForIosReport(candidate).catch(() => "unknown");
19232
+ ].map((candidate) => readPackageVersionForIosReport(candidate).catch(() => "unknown")));
19233
+ for (const version2 of versions) {
18927
19234
  if (version2 === "unknown")
18928
19235
  continue;
18929
19236
  absolutejsVersion = version2;
18930
19237
  break;
18931
19238
  }
19239
+ return absolutejsVersion;
19240
+ }, writeRequestedAndroidReport = async (options) => {
19241
+ const reportRoot = nativeReportRoot(options.args, options.projectRoot, "android");
19242
+ if (!reportRoot)
19243
+ return;
19244
+ const adbVersion = requireCapturedCommand([options.adb, "version"], "ADB version inspection").stdout.trim();
19245
+ const report = createAbsoluteAndroidTestReport({
19246
+ absolutejsVersion: await absolutejsVersionForReport(),
19247
+ adbVersion,
19248
+ bunVersion: Bun.version,
19249
+ host: `${process.platform}-${process.arch}`,
19250
+ run: options.run
19251
+ });
19252
+ const paths = await writeAbsoluteNativeTestReport(reportRoot, report);
19253
+ const print = options.args.includes("--json") ? console.error : console.log;
19254
+ print(`Android test report: ${paths.markdownPath}`);
19255
+ print(`Return this report directory: ${paths.directory}`);
19256
+ return paths;
19257
+ }, iosReportMetadata = async (xcrun) => {
19258
+ const xcodebuildPath = requireCapturedCommand([xcrun, "--find", "xcodebuild"], "Xcode version inspection").stdout.trim();
19259
+ const xcodeVersion = requireCapturedCommand([xcodebuildPath, "-version"], "Xcode version inspection").stdout.trim();
19260
+ const macosVersion = requireCapturedCommand(["/usr/bin/sw_vers", "-productVersion"], "macOS version inspection").stdout.trim();
18932
19261
  return {
18933
- absolutejsVersion,
19262
+ absolutejsVersion: await absolutejsVersionForReport(),
18934
19263
  bunVersion: Bun.version,
18935
19264
  macosVersion,
18936
19265
  xcodeVersion
18937
19266
  };
18938
19267
  }, writeRequestedIosReport = async (options) => {
18939
- const reportRoot = iosReportRoot(options.args, options.projectRoot);
19268
+ const reportRoot = nativeReportRoot(options.args, options.projectRoot, "ios");
18940
19269
  if (!reportRoot)
18941
19270
  return;
18942
19271
  const metadata = await iosReportMetadata(options.xcrun);
@@ -18955,11 +19284,11 @@ Emulator setup verification:`);
18955
19284
  const timeoutMs = androidTestTimeout(args);
18956
19285
  const xcrun = await requireIosXcrun();
18957
19286
  const simulator = selectIosSimulator(xcrun, valueAfter(args, "--udid") ?? valueAfter(args, "--serial"));
18958
- const reportRoot = iosReportRoot(args, projectRoot);
19287
+ const reportRoot = nativeReportRoot(args, projectRoot, "ios");
18959
19288
  const artifactRoot = reportRoot ?? safeArtifactRoot(projectRoot, valueAfter(args, "--artifacts"));
18960
19289
  const startedAt = performance.now();
18961
19290
  try {
18962
- requireCapturedIosCommand([
19291
+ requireCapturedCommand([
18963
19292
  xcrun,
18964
19293
  "simctl",
18965
19294
  "get_app_container",
@@ -18967,7 +19296,7 @@ Emulator setup verification:`);
18967
19296
  mobile.appId,
18968
19297
  "app"
18969
19298
  ], "iOS installed-app inspection");
18970
- requireCapturedIosCommand([
19299
+ requireCapturedCommand([
18971
19300
  xcrun,
18972
19301
  "simctl",
18973
19302
  "launch",
@@ -18978,7 +19307,7 @@ Emulator setup verification:`);
18978
19307
  await waitForIosHmrClient({ https, port, timeoutMs });
18979
19308
  await mkdir14(artifactRoot, { recursive: true });
18980
19309
  const screenshot = join52(artifactRoot, "ios-simulator.png");
18981
- requireCapturedIosCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
19310
+ requireCapturedCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
18982
19311
  const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
18983
19312
  const report = {
18984
19313
  appId: mobile.appId,
@@ -19111,7 +19440,7 @@ Emulator setup verification:`);
19111
19440
  await publishIos(args.slice(2));
19112
19441
  return;
19113
19442
  }
19114
- throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [--json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | associations [--outdir dir] [--verify] | doctor [ios|android|release] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--outdir dir] [--web-outdir dir] [--unsigned] | publish android [server-entry] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--route path] [--wait-for-hmr] [--timeout ms] [--port n] [--serial id] [--artifacts dir] [--json] | test ios [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--udid id] [--artifacts dir] [--json]> [--config path]");
19443
+ throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [--json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | associations [--outdir dir] [--verify] | doctor [ios|android|release] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--outdir dir] [--web-outdir dir] [--unsigned] | publish android [server-entry] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--route path] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--serial id] [--artifacts dir] [--json] | test ios [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--udid id] [--artifacts dir] [--json]> [--config path]");
19115
19444
  };
19116
19445
  var init_mobile = __esm(() => {
19117
19446
  init_dependencies();
@@ -19134,6 +19463,8 @@ var init_mobile = __esm(() => {
19134
19463
  init_iosSimulatorController();
19135
19464
  init_iosConformance();
19136
19465
  init_iosTestReport();
19466
+ init_androidTestReport();
19467
+ init_nativeTestReport();
19137
19468
  init_releasePublisher();
19138
19469
  init_start();
19139
19470
  init_utils();
@@ -19163,8 +19494,8 @@ var init_mobile = __esm(() => {
19163
19494
  "@capacitor/cli@8.5.0",
19164
19495
  "@capacitor/android@8.5.0",
19165
19496
  "@capacitor/ios@8.5.0",
19166
- "@absolutejs/devices@0.3.0",
19167
- "@absolutejs/devices-capacitor@0.4.0"
19497
+ "@absolutejs/devices@0.5.0",
19498
+ "@absolutejs/devices-capacitor@0.6.1"
19168
19499
  ];
19169
19500
  CAPACITOR_SYNC_PACKAGE_SPECS = [
19170
19501
  "@absolutejs/sync-capacitor@0.9.1",