@absolutejs/absolute 0.20.0-beta.107 → 0.20.0-beta.109

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.
@@ -15,7 +15,7 @@ import {
15
15
  createIslandRegistryDefinitionPlugin,
16
16
  finalizeAbsoluteMobileCompatibilityBuild,
17
17
  resolveServerBundleExternals
18
- } from "./index-hrzd7e5x.js";
18
+ } from "./index-dfqzrznk.js";
19
19
  import {
20
20
  loadIslandRegistryBuildInfo
21
21
  } from "./index-bzs19w3r.js";
@@ -35,7 +35,7 @@ import {
35
35
  import"./index-w5vswatm.js";
36
36
  import {
37
37
  normalizeAbsoluteMobileConfig
38
- } from "./index-rd2hbttb.js";
38
+ } from "./index-df2mjbdy.js";
39
39
  import"./index-0zcebhm4.js";
40
40
  import {
41
41
  ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV,
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  normalizeAbsoluteMobileConfig
4
- } from "./index-rd2hbttb.js";
4
+ } from "./index-df2mjbdy.js";
5
5
  import"./index-mywsk201.js";
6
6
  export {
7
7
  normalizeAbsoluteMobileConfig
@@ -1285,7 +1285,7 @@ var loadAbsoluteMobileDevModules = async () => {
1285
1285
  iosNativeWatcher,
1286
1286
  expoNativeWatcher
1287
1287
  ] = await Promise.all([
1288
- import("./config-vy04mqce.js"),
1288
+ import("./config-vq2qcc4p.js"),
1289
1289
  import("./expoProject-zkprmjjz.js"),
1290
1290
  import("./expoDevController-zz516pdd.js"),
1291
1291
  import("./nativeAuth-zx4tsb1f.js"),
@@ -8,6 +8,7 @@ var SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*$/;
8
8
  var APPLE_APP_ID_PREFIX_PATTERN = /^[A-Z0-9]{10}$/;
9
9
  var CERTIFICATE_FINGERPRINT_PATTERN = /^[0-9A-F]{64}$/;
10
10
  var UPDATE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
11
+ var RELEASE_TARGET_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/u;
11
12
  var UPDATE_PUBLIC_KEY_PATTERN = /^[A-Za-z0-9+/]+={0,2}$/u;
12
13
  var ENVIRONMENT_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/u;
13
14
  var DEFAULT_UPDATE_BOOT_TIMEOUT_MS = 20000;
@@ -75,6 +76,57 @@ var normalizePlatforms = (platforms) => {
75
76
  }
76
77
  return normalized;
77
78
  };
79
+ var normalizeAndroidCertificationPolicy = (value) => {
80
+ if (value === false || value === undefined)
81
+ return;
82
+ if (value !== "installed")
83
+ throw new TypeError("mobile.release.certification channel Android policy must be installed or false.");
84
+ return value;
85
+ };
86
+ var normalizeIosCertificationPolicy = (value) => {
87
+ if (value === false || value === undefined)
88
+ return;
89
+ if (!["simulator", "device", "store"].includes(value))
90
+ throw new TypeError("mobile.release.certification channel iOS policy must be simulator, device, store, or false.");
91
+ return value;
92
+ };
93
+ var normalizeReleaseCertification = (config) => {
94
+ const configured = config.release?.certification;
95
+ if (configured === false)
96
+ return { channels: {}, googlePlayTracks: {} };
97
+ const channels = {
98
+ production: { android: "installed", ios: "store" }
99
+ };
100
+ const googlePlayTracks = {
101
+ production: "installed"
102
+ };
103
+ for (const [name, policy] of Object.entries(configured?.channels ?? {})) {
104
+ if (!RELEASE_TARGET_PATTERN.test(name))
105
+ throw new TypeError("mobile.release.certification.channels contains an invalid channel.");
106
+ const normalized = {};
107
+ const android = normalizeAndroidCertificationPolicy(policy.android);
108
+ const ios = normalizeIosCertificationPolicy(policy.ios);
109
+ if (android)
110
+ normalized.android = android;
111
+ if (ios)
112
+ normalized.ios = ios;
113
+ channels[name] = normalized;
114
+ }
115
+ for (const [name, requirement] of Object.entries(configured?.googlePlayTracks ?? {})) {
116
+ if (!RELEASE_TARGET_PATTERN.test(name))
117
+ throw new TypeError("mobile.release.certification.googlePlayTracks contains an invalid track.");
118
+ if (requirement === false)
119
+ delete googlePlayTracks[name];
120
+ else if (requirement === "installed")
121
+ googlePlayTracks[name] = requirement;
122
+ else
123
+ throw new TypeError("mobile.release.certification Google Play policy must be installed or false.");
124
+ }
125
+ return {
126
+ channels: Object.fromEntries(Object.entries(channels).sort(([left], [right]) => left.localeCompare(right))),
127
+ googlePlayTracks: Object.fromEntries(Object.entries(googlePlayTracks).sort(([left], [right]) => left.localeCompare(right)))
128
+ };
129
+ };
78
130
  var normalizeHosts = (hosts, productionOrigin) => {
79
131
  const normalizeHostname = (host) => {
80
132
  const value = requireText(host, "mobile.deepLinks.hosts").toLowerCase();
@@ -411,6 +463,7 @@ var normalizeAbsoluteMobileConfig = (config, projectRoot) => {
411
463
  platforms: normalizePlatforms(config.platforms),
412
464
  productionOrigin,
413
465
  pushAndroidGoogleServicesFile: resolveProjectPath(projectRoot, config.pushNotifications?.android?.googleServicesFile ?? "google-services.json", "mobile.pushNotifications.android.googleServicesFile"),
466
+ releaseCertification: normalizeReleaseCertification(config),
414
467
  ...updates ? { updates } : {},
415
468
  ...updateServer ? { updateServer } : {}
416
469
  };
@@ -4,7 +4,7 @@ import {
4
4
  } from "./index-s2tazax7.js";
5
5
  import {
6
6
  normalizeAbsoluteMobileConfig
7
- } from "./index-rd2hbttb.js";
7
+ } from "./index-df2mjbdy.js";
8
8
  import {
9
9
  assertAbsoluteDeviceCapabilityPackages,
10
10
  resolveAbsoluteMobileUpdateRuntime,
@@ -6,7 +6,7 @@ import {
6
6
  createIslandRegistryDefinitionPlugin,
7
7
  finalizeAbsoluteMobileCompatibilityBuild,
8
8
  resolveServerBundleExternals
9
- } from "./index-hrzd7e5x.js";
9
+ } from "./index-dfqzrznk.js";
10
10
  import {
11
11
  loadIslandRegistryBuildInfo
12
12
  } from "./index-bzs19w3r.js";
@@ -26,7 +26,7 @@ import {
26
26
  } from "./index-9kwmb3c0.js";
27
27
  import {
28
28
  normalizeAbsoluteMobileConfig
29
- } from "./index-rd2hbttb.js";
29
+ } from "./index-df2mjbdy.js";
30
30
  import {
31
31
  installAbsoluteMobileAuthEnvironment
32
32
  } from "./index-ar4gz15x.js";
package/dist/cli/index.js CHANGED
@@ -58,7 +58,7 @@ if (command === "dev") {
58
58
  }
59
59
  const positionalArgs = stripNamedArgs("--config", "--android-device", "--ios-device").filter((arg) => arg !== "--no-mobile" && arg !== "--eager");
60
60
  const serverEntry = positionalArgs[0] ?? DEFAULT_SERVER_ENTRY;
61
- const { dev } = await import("./dev-f6b6hkke.js");
61
+ const { dev } = await import("./dev-f0safhsr.js");
62
62
  await dev(serverEntry, configPath, {
63
63
  androidDevice,
64
64
  eager: args.includes("--eager"),
@@ -71,7 +71,7 @@ if (command === "dev") {
71
71
  const configPath = parseNamedArg("--config");
72
72
  const positionalArgs = stripNamedArgs("--outdir", "--config").filter((arg) => arg !== "--prebuilt");
73
73
  const serverEntry = positionalArgs[0] ?? DEFAULT_SERVER_ENTRY;
74
- const { start } = await import("./start-5grq7nfr.js");
74
+ const { start } = await import("./start-8v6g2y31.js");
75
75
  await start(serverEntry, outdir, configPath, {
76
76
  prebuilt: args.includes("--prebuilt")
77
77
  });
@@ -81,7 +81,7 @@ if (command === "dev") {
81
81
  const configPath = parseNamedArg("--config");
82
82
  const positionalArgs = stripNamedArgs("--outdir", "--config");
83
83
  const serverEntry = positionalArgs[0] ?? DEFAULT_SERVER_ENTRY;
84
- const { start } = await import("./start-5grq7nfr.js");
84
+ const { start } = await import("./start-8v6g2y31.js");
85
85
  await start(serverEntry, outdir, configPath, { prepareOnly: true });
86
86
  } else if (command === "build") {
87
87
  sendTelemetryEvent("cli:command", { command });
@@ -197,13 +197,13 @@ if (command === "dev") {
197
197
  const configPath = parseNamedArg("--config");
198
198
  const positionalArgs = stripNamedArgs("--outdir", "--outfile", "--config");
199
199
  const serverEntry = positionalArgs[0] ?? DEFAULT_SERVER_ENTRY;
200
- const { compile } = await import("./compile-hsbs1xf6.js");
200
+ const { compile } = await import("./compile-1d5arbqj.js");
201
201
  await compile(serverEntry, outdir, outfile, configPath);
202
202
  } else if (command === "mobile") {
203
203
  sendTelemetryEvent("cli:command", {
204
204
  command: `mobile:${workspaceCommand ?? "unknown"}`
205
205
  });
206
- const { runMobile } = await import("./mobile-7v8hfmdq.js");
206
+ const { runMobile } = await import("./mobile-e4ygngfk.js");
207
207
  try {
208
208
  await runMobile(args);
209
209
  } catch (error) {
@@ -7,12 +7,12 @@ import {
7
7
  } from "./index-zh6hhrwy.js";
8
8
  import {
9
9
  start
10
- } from "./index-mazxtfh4.js";
10
+ } from "./index-gps0zk0m.js";
11
11
  import {
12
12
  ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
13
13
  ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION,
14
14
  resolveAbsoluteMobileRoute
15
- } from "./index-hrzd7e5x.js";
15
+ } from "./index-dfqzrznk.js";
16
16
  import"./index-bzs19w3r.js";
17
17
  import"./index-s2tazax7.js";
18
18
  import {
@@ -56,7 +56,7 @@ import {
56
56
  import"./index-w5vswatm.js";
57
57
  import {
58
58
  normalizeAbsoluteMobileConfig
59
- } from "./index-rd2hbttb.js";
59
+ } from "./index-df2mjbdy.js";
60
60
  import {
61
61
  ABSOLUTE_MOBILE_UPDATE_FORMAT,
62
62
  absoluteDeviceNativeRequirements,
@@ -22353,6 +22353,14 @@ var satisfiesRequirement = (strength, requirement) => {
22353
22353
  return false;
22354
22354
  return STRENGTH_RANK[strength] >= STRENGTH_RANK[requirement];
22355
22355
  };
22356
+ var resolveAbsoluteMobileCertificationRequirement = (config, target) => {
22357
+ const channelRequirement = target.channel ? config.releaseCertification.channels[target.channel]?.[target.platform] : undefined;
22358
+ const playRequirement = target.googlePlayTrack ? config.releaseCertification.googlePlayTracks[target.googlePlayTrack] : undefined;
22359
+ const requirements = [channelRequirement, playRequirement].filter((value) => value !== undefined);
22360
+ if (requirements.length === 0)
22361
+ return;
22362
+ return requirements.reduce((strongest, candidate) => STRENGTH_RANK[candidate] > STRENGTH_RANK[strongest] ? candidate : strongest);
22363
+ };
22356
22364
  var validateCertifiedEvidence = (platform, evidence) => {
22357
22365
  if (platform === "android") {
22358
22366
  if (evidence.strength !== "installed" || evidence.networkUnavailable !== "proven" || evidence.artifactExactness !== undefined || evidence.distribution !== undefined || evidence.remote)
@@ -22599,6 +22607,15 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
22599
22607
  }
22600
22608
  return versionCode;
22601
22609
  };
22610
+ var requireCertificationReceipt = (publication, certification, requirement, releaseId) => {
22611
+ if (!certification && !requirement)
22612
+ return;
22613
+ if (!certification || !requirement)
22614
+ throw new TypeError("Native release publication certification contract is incomplete.");
22615
+ const receipt = publication.certification;
22616
+ if (!receipt || receipt.certificationId !== certification.certificationId || receipt.releaseId !== releaseId || receipt.requirement !== requirement || receipt.strength !== certification.strength)
22617
+ throw new TypeError("Native release registry did not retain the required release certification.");
22618
+ };
22602
22619
  var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
22603
22620
  var isPublisher = (value) => isRecord7(value) && typeof value.publish === "function";
22604
22621
  var publisherModulePath = (projectRoot, requested) => {
@@ -22623,9 +22640,16 @@ var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath
22623
22640
  return publisher;
22624
22641
  };
22625
22642
  var publishAbsoluteAndroidRelease = async (options) => {
22643
+ const certificationRequirement = options.certificationRequirement ?? options.certification?.requirement;
22644
+ if (options.certification)
22645
+ verifyAbsoluteMobileReleaseCertification(options.certification, options.release, certificationRequirement);
22646
+ else if (certificationRequirement)
22647
+ throw new TypeError(`Android release publication requires ${certificationRequirement} certification.`);
22626
22648
  const publisher = await loadAbsoluteNativeReleasePublisher(options.projectRoot, options.modulePath);
22627
22649
  const publication = await publisher.publish({
22628
22650
  allowUnsigned: options.allowUnsigned,
22651
+ certification: options.certification,
22652
+ certificationRequirement,
22629
22653
  channel: options.channel,
22630
22654
  googlePlay: options.googlePlay,
22631
22655
  releaseRoot: options.release.releaseRoot,
@@ -22636,6 +22660,7 @@ var publishAbsoluteAndroidRelease = async (options) => {
22636
22660
  if (!actual || actual.appId !== expected.appId || actual.platform !== "android" || actual.releaseId !== expected.releaseId || actual.sha256 !== expected.sha256 || actual.signed !== expected.signed || actual.versionCode !== expected.versionCode || typeof publication.reused !== "boolean") {
22637
22661
  throw new TypeError("Native release registry returned a different Android release identity.");
22638
22662
  }
22663
+ requireCertificationReceipt(publication, options.certification, certificationRequirement, expected.releaseId);
22639
22664
  if (options.channel !== undefined && (publication.channel?.channel !== options.channel || publication.channel.releaseId !== expected.releaseId)) {
22640
22665
  throw new TypeError("Native release registry did not promote the requested channel.");
22641
22666
  }
@@ -22648,10 +22673,17 @@ var publishAbsoluteAndroidRelease = async (options) => {
22648
22673
  return publication;
22649
22674
  };
22650
22675
  var publishAbsoluteIosRelease = async (options) => {
22676
+ const certificationRequirement = options.certificationRequirement ?? options.certification?.requirement;
22677
+ if (options.certification)
22678
+ verifyAbsoluteMobileReleaseCertification(options.certification, options.release, certificationRequirement);
22679
+ else if (certificationRequirement)
22680
+ throw new TypeError(`iOS release publication requires ${certificationRequirement} certification.`);
22651
22681
  const publisher = await loadAbsoluteNativeReleasePublisher(options.projectRoot, options.modulePath);
22652
22682
  const publication = await publisher.publish({
22653
22683
  allowUnsigned: options.allowUnsigned,
22654
22684
  appStoreConnect: options.appStoreConnect,
22685
+ certification: options.certification,
22686
+ certificationRequirement,
22655
22687
  channel: options.channel,
22656
22688
  releaseRoot: options.release.releaseRoot,
22657
22689
  signal: options.signal
@@ -22661,6 +22693,7 @@ var publishAbsoluteIosRelease = async (options) => {
22661
22693
  if (!actual || actual.appId !== expected.appId || actual.platform !== "ios" || actual.releaseId !== expected.releaseId || actual.sha256 !== expected.sha256 || actual.signed !== expected.signed || actual.buildNumber !== expected.buildNumber || actual.marketingVersion !== expected.marketingVersion || typeof publication.reused !== "boolean") {
22662
22694
  throw new TypeError("Native release registry returned a different iOS release identity.");
22663
22695
  }
22696
+ requireCertificationReceipt(publication, options.certification, certificationRequirement, expected.releaseId);
22664
22697
  if (options.channel !== undefined && (publication.channel?.channel !== options.channel || publication.channel.releaseId !== expected.releaseId)) {
22665
22698
  throw new TypeError("Native release registry did not promote the requested channel.");
22666
22699
  }
@@ -23023,12 +23056,9 @@ var androidJob = (options) => {
23023
23056
  ABSOLUTE_GOOGLE_CREDENTIALS_BASE64: \${{ secrets.ABSOLUTE_GOOGLE_CREDENTIALS_BASE64 }}
23024
23057
  GOOGLE_APPLICATION_CREDENTIALS: \${{ runner.temp }}/absolute-google-credentials.json` : "";
23025
23058
  const publishCommand = options.includePublishing ? `if [[ "$ABSOLUTE_PUBLISH" == "true" ]]; then
23026
- args=(bunx absolute mobile publish android "$ABSOLUTE_SERVER_ENTRY" --registry "$ABSOLUTE_REGISTRY_MODULE")
23027
- if [[ -n "$ABSOLUTE_RELEASE_CHANNEL" ]]; then
23028
- args+=(--channel "$ABSOLUTE_RELEASE_CHANNEL")
23029
- fi
23059
+ args=(bunx absolute mobile build android "$ABSOLUTE_SERVER_ENTRY")
23030
23060
  if [[ "$ABSOLUTE_PLAY_TRACK" != "registry-only" ]]; then
23031
- args+=(--play-track "$ABSOLUTE_PLAY_TRACK")
23061
+ args+=(--registry "$ABSOLUTE_REGISTRY_MODULE" --play-track "$ABSOLUTE_PLAY_TRACK")
23032
23062
  fi
23033
23063
  else
23034
23064
  args=(bunx absolute mobile build android "$ABSOLUTE_SERVER_ENTRY")
@@ -23042,6 +23072,58 @@ var androidJob = (options) => {
23042
23072
  printf '%s' "$ABSOLUTE_GOOGLE_CREDENTIALS_BASE64" | base64 --decode > "$GOOGLE_APPLICATION_CREDENTIALS"
23043
23073
  chmod 600 "$GOOGLE_APPLICATION_CREDENTIALS"
23044
23074
  fi` : "";
23075
+ const certificationSteps = options.includePublishing ? `
23076
+ - name: Install-test and certify exact Android release
23077
+ if: inputs.publish
23078
+ shell: bash
23079
+ run: |
23080
+ args=(bunx absolute mobile doctor android --fix --yes)
23081
+ ${appendConfigArgument}
23082
+ "\${args[@]}"
23083
+ shopt -s nullglob
23084
+ releases=(.absolutejs/mobile/releases/android/*)
23085
+ if [[ "\${#releases[@]}" -ne 1 || ! -d "\${releases[0]}" ]]; then
23086
+ echo "Expected exactly one immutable Android release directory." >&2
23087
+ exit 1
23088
+ fi
23089
+ RELEASE_DIRECTORY="\${releases[0]}"
23090
+ args=(bunx absolute mobile test android --release "$RELEASE_DIRECTORY" --report .absolutejs/mobile-ci/acceptance/android --yes)
23091
+ ${appendConfigArgument}
23092
+ "\${args[@]}"
23093
+ args=(bunx absolute mobile certify "$RELEASE_DIRECTORY" --evidence .absolutejs/mobile-ci/acceptance/android --require installed --outdir .absolutejs/mobile-ci/certifications/android --json)
23094
+ "\${args[@]}" > .absolutejs/mobile-ci/android-certification.json
23095
+ - name: Publish certified Android release
23096
+ if: inputs.publish
23097
+ shell: bash
23098
+ run: |
23099
+ shopt -s nullglob
23100
+ releases=(.absolutejs/mobile/releases/android/*)
23101
+ certifications=(.absolutejs/mobile-ci/certifications/android/*)
23102
+ if [[ "\${#releases[@]}" -ne 1 || "\${#certifications[@]}" -ne 1 || ! -d "\${releases[0]}" || ! -d "\${certifications[0]}" ]]; then
23103
+ echo "Expected exactly one Android release and certification." >&2
23104
+ exit 1
23105
+ fi
23106
+ args=(bunx absolute mobile publish android --release "\${releases[0]}" --certification "\${certifications[0]}" --registry "$ABSOLUTE_REGISTRY_MODULE")
23107
+ if [[ -n "$ABSOLUTE_RELEASE_CHANNEL" ]]; then
23108
+ args+=(--channel "$ABSOLUTE_RELEASE_CHANNEL")
23109
+ fi
23110
+ if [[ "$ABSOLUTE_PLAY_TRACK" != "registry-only" ]]; then
23111
+ args+=(--play-track "$ABSOLUTE_PLAY_TRACK")
23112
+ fi
23113
+ ${appendConfigArgument}
23114
+ "\${args[@]}"
23115
+ - name: Upload Android acceptance and certification
23116
+ if: always() && inputs.publish
23117
+ uses: actions/upload-artifact@v7
23118
+ with:
23119
+ name: absolute-mobile-android-certification
23120
+ path: |
23121
+ .absolutejs/mobile-ci/acceptance/android/
23122
+ .absolutejs/mobile-ci/certifications/android/
23123
+ .absolutejs/mobile-ci/android-certification.json
23124
+ if-no-files-found: error
23125
+ retention-days: 30
23126
+ include-hidden-files: true` : "";
23045
23127
  return `
23046
23128
  android:
23047
23129
  name: Signed Android release
@@ -23080,13 +23162,14 @@ ${installSteps}
23080
23162
  done
23081
23163
  printf '%s' "$ABSOLUTE_ANDROID_KEYSTORE_BASE64" | base64 --decode > "\${{ runner.temp }}/absolute-release.jks"
23082
23164
  chmod 600 "\${{ runner.temp }}/absolute-release.jks"${googleSetup}
23083
- - name: Build or publish Android
23165
+ - name: Build Android
23084
23166
  shell: bash
23085
23167
  run: |
23086
23168
  ${publishCommand}
23087
23169
  ${appendConfigArgument}
23088
23170
  "\${args[@]}"
23089
23171
  ${releaseAuditSteps("android")}
23172
+ ${certificationSteps}
23090
23173
  - name: Attest Android App Bundle
23091
23174
  if: inputs.attest
23092
23175
  uses: actions/attest@v4
@@ -23119,15 +23202,9 @@ var iosJob = (options) => {
23119
23202
  APP_STORE_CONNECT_PRIVATE_KEY_BASE64: \${{ secrets.APP_STORE_CONNECT_PRIVATE_KEY_BASE64 }}
23120
23203
  APP_STORE_CONNECT_PRIVATE_KEY_PATH: \${{ runner.temp }}/AuthKey_AbsoluteJS.p8` : "";
23121
23204
  const publishCommand = options.includePublishing ? `if [[ "$ABSOLUTE_PUBLISH" == "true" ]]; then
23122
- args=(bunx absolute mobile publish ios "$ABSOLUTE_SERVER_ENTRY" --registry "$ABSOLUTE_REGISTRY_MODULE")
23123
- if [[ -n "$ABSOLUTE_RELEASE_CHANNEL" ]]; then
23124
- args+=(--channel "$ABSOLUTE_RELEASE_CHANNEL")
23125
- fi
23205
+ args=(bunx absolute mobile build ios "$ABSOLUTE_SERVER_ENTRY")
23126
23206
  if [[ -n "$ABSOLUTE_TESTFLIGHT_GROUP" ]]; then
23127
- args+=(--testflight-group "$ABSOLUTE_TESTFLIGHT_GROUP")
23128
- fi
23129
- if [[ "$ABSOLUTE_TESTFLIGHT_SUBMIT_REVIEW" == "true" ]]; then
23130
- args+=(--testflight-submit-review)
23207
+ args+=(--registry "$ABSOLUTE_REGISTRY_MODULE" --testflight-group "$ABSOLUTE_TESTFLIGHT_GROUP")
23131
23208
  fi
23132
23209
  else
23133
23210
  args=(bunx absolute mobile build ios "$ABSOLUTE_SERVER_ENTRY")
@@ -23141,6 +23218,58 @@ var iosJob = (options) => {
23141
23218
  printf '%s' "$APP_STORE_CONNECT_PRIVATE_KEY_BASE64" | base64 --decode > "$APP_STORE_CONNECT_PRIVATE_KEY_PATH"
23142
23219
  chmod 600 "$APP_STORE_CONNECT_PRIVATE_KEY_PATH"
23143
23220
  fi` : "";
23221
+ const certificationSteps = options.includePublishing ? `
23222
+ - name: Simulator-test and certify exact iOS release
23223
+ if: inputs.publish
23224
+ shell: bash
23225
+ run: |
23226
+ shopt -s nullglob
23227
+ releases=(.absolutejs/mobile/releases/ios/*)
23228
+ if [[ "\${#releases[@]}" -ne 1 || ! -d "\${releases[0]}" ]]; then
23229
+ echo "Expected exactly one immutable iOS release directory." >&2
23230
+ exit 1
23231
+ fi
23232
+ RELEASE_DIRECTORY="\${releases[0]}"
23233
+ args=(bunx absolute mobile test ios --release "$RELEASE_DIRECTORY" --report .absolutejs/mobile-ci/acceptance/ios --yes)
23234
+ ${appendConfigArgument}
23235
+ "\${args[@]}"
23236
+ args=(bunx absolute mobile certify "$RELEASE_DIRECTORY" --evidence .absolutejs/mobile-ci/acceptance/ios --require simulator --outdir .absolutejs/mobile-ci/certifications/ios --json)
23237
+ "\${args[@]}" > .absolutejs/mobile-ci/ios-certification.json
23238
+ - name: Publish certified iOS release
23239
+ if: inputs.publish
23240
+ shell: bash
23241
+ run: |
23242
+ shopt -s nullglob
23243
+ releases=(.absolutejs/mobile/releases/ios/*)
23244
+ certifications=(.absolutejs/mobile-ci/certifications/ios/*)
23245
+ if [[ "\${#releases[@]}" -ne 1 || "\${#certifications[@]}" -ne 1 || ! -d "\${releases[0]}" || ! -d "\${certifications[0]}" ]]; then
23246
+ echo "Expected exactly one iOS release and certification." >&2
23247
+ exit 1
23248
+ fi
23249
+ args=(bunx absolute mobile publish ios --release "\${releases[0]}" --certification "\${certifications[0]}" --registry "$ABSOLUTE_REGISTRY_MODULE")
23250
+ if [[ -n "$ABSOLUTE_RELEASE_CHANNEL" ]]; then
23251
+ args+=(--channel "$ABSOLUTE_RELEASE_CHANNEL")
23252
+ fi
23253
+ if [[ -n "$ABSOLUTE_TESTFLIGHT_GROUP" ]]; then
23254
+ args+=(--testflight-group "$ABSOLUTE_TESTFLIGHT_GROUP")
23255
+ fi
23256
+ if [[ "$ABSOLUTE_TESTFLIGHT_SUBMIT_REVIEW" == "true" ]]; then
23257
+ args+=(--testflight-submit-review)
23258
+ fi
23259
+ ${appendConfigArgument}
23260
+ "\${args[@]}"
23261
+ - name: Upload iOS acceptance and certification
23262
+ if: always() && inputs.publish
23263
+ uses: actions/upload-artifact@v7
23264
+ with:
23265
+ name: absolute-mobile-ios-certification
23266
+ path: |
23267
+ .absolutejs/mobile-ci/acceptance/ios/
23268
+ .absolutejs/mobile-ci/certifications/ios/
23269
+ .absolutejs/mobile-ci/ios-certification.json
23270
+ if-no-files-found: error
23271
+ retention-days: 30
23272
+ include-hidden-files: true` : "";
23144
23273
  return `
23145
23274
  ios:
23146
23275
  name: Signed iOS release
@@ -23192,13 +23321,14 @@ ${installSteps}
23192
23321
  security list-keychain -d user -s "$KEYCHAIN_PATH"
23193
23322
  mkdir -p "$(dirname "$PROFILE_DESTINATION")"
23194
23323
  cp "$PROFILE_PATH" "$PROFILE_DESTINATION"${appStoreDecode}
23195
- - name: Build or publish iOS
23324
+ - name: Build iOS
23196
23325
  shell: bash
23197
23326
  run: |
23198
23327
  ${publishCommand}
23199
23328
  ${appendConfigArgument}
23200
23329
  "\${args[@]}"
23201
23330
  ${releaseAuditSteps("ios")}
23331
+ ${certificationSteps}
23202
23332
  - name: Attest iOS IPA
23203
23333
  if: inputs.attest
23204
23334
  uses: actions/attest@v4
@@ -24439,7 +24569,7 @@ var provisionMobileUpdate = async (args) => {
24439
24569
  throw new TypeError("--storage must be local or s3.");
24440
24570
  const modulePath = valueAfter(args, "--registry") ?? mobile.updateServer.registryModule;
24441
24571
  const packages = [
24442
- "@absolutejs/deploy@0.25.13",
24572
+ "@absolutejs/deploy@0.26.0",
24443
24573
  "@absolutejs/blob@0.5.2",
24444
24574
  ...requestedStorage === "s3" ? [
24445
24575
  "@aws-sdk/client-s3@3.1095.0",
@@ -24957,25 +25087,70 @@ var printAppStoreConnectPublication = (publication) => {
24957
25087
  const { receipt } = publication;
24958
25088
  console.log(`${publication.reused ? "Reused" : "Uploaded"} App Store Connect build ${receipt.marketingVersion} (${receipt.buildNumber}); ${receipt.stage}.`);
24959
25089
  };
25090
+ var buildAndroidCommand = async (args) => {
25091
+ const googlePlay = googlePlayTarget(args);
25092
+ if (!googlePlay)
25093
+ return buildAndroid(args);
25094
+ const registryModule = args.includes("--registry") ? requireValueAfter(args, "--registry") : "mobile.release.ts";
25095
+ const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
25096
+ const publisher = await loadAbsoluteNativeReleasePublisher(projectRoot, registryModule);
25097
+ return buildAndroid(args, (buildIdentity) => prepareAbsoluteAndroidRelease(publisher, {
25098
+ buildIdentity,
25099
+ googlePlay,
25100
+ packageName: mobile.appId
25101
+ }));
25102
+ };
25103
+ var requirePublicationReleaseIdentity = (mobile, release, platform2) => {
25104
+ if (release.metadata.platform !== platform2 || release.metadata.appId !== mobile.appId || release.metadata.engine !== mobile.engine)
25105
+ throw new TypeError(`The existing ${platform2} release does not match this application's platform, app ID, or engine.`);
25106
+ };
25107
+ var loadPublicationCertification = async (options) => {
25108
+ const requested = valueAfter(options.args, "--certification");
25109
+ if (options.args.includes("--certification") && (!requested || requested.startsWith("-")))
25110
+ throw new TypeError(`mobile publish ${options.platform} requires --certification <directory-or-json>.`);
25111
+ if (!requested) {
25112
+ if (options.requirement)
25113
+ throw new TypeError(`mobile publish ${options.platform} requires --certification with ${options.requirement} evidence for this release target.`);
25114
+ return;
25115
+ }
25116
+ const loaded = await readAbsoluteMobileReleaseCertification(options.projectRoot, requested);
25117
+ return verifyAbsoluteMobileReleaseCertification(loaded.certification, options.release, options.requirement);
25118
+ };
24960
25119
  var publishAndroid = async (args) => {
24961
25120
  const registryModule = args.includes("--registry") ? requireValueAfter(args, "--registry") : "mobile.release.ts";
24962
25121
  const channel = args.includes("--channel") ? requireValueAfter(args, "--channel") : undefined;
24963
25122
  const configPath = valueAfter(args, "--config");
24964
25123
  const googlePlay = googlePlayTarget(args);
24965
25124
  const { mobile, projectRoot } = await loadMobile(configPath);
25125
+ const certificationRequirement = resolveAbsoluteMobileCertificationRequirement(mobile, {
25126
+ channel,
25127
+ googlePlayTrack: googlePlay?.track,
25128
+ platform: "android"
25129
+ });
24966
25130
  const startedAt = performance.now();
24967
25131
  let reused = false;
24968
25132
  let success = false;
24969
25133
  try {
24970
25134
  const publisher = await loadAbsoluteNativeReleasePublisher(projectRoot, registryModule);
24971
- const release = await buildAndroid(args, googlePlay ? (buildIdentity) => prepareAbsoluteAndroidRelease(publisher, {
25135
+ const requestedRelease = args.includes("--release") ? requireValueAfter(args, "--release") : undefined;
25136
+ const release = requestedRelease ? await readAbsoluteAndroidRelease(projectRoot, requestedRelease) : await buildAndroid(args, googlePlay ? (buildIdentity) => prepareAbsoluteAndroidRelease(publisher, {
24972
25137
  buildIdentity,
24973
25138
  googlePlay,
24974
25139
  packageName: mobile.appId
24975
25140
  }) : undefined);
25141
+ requirePublicationReleaseIdentity(mobile, release, "android");
25142
+ const certification = await loadPublicationCertification({
25143
+ args,
25144
+ platform: "android",
25145
+ projectRoot,
25146
+ release,
25147
+ ...certificationRequirement ? { requirement: certificationRequirement } : {}
25148
+ });
24976
25149
  const publication = await publishAbsoluteAndroidRelease({
24977
25150
  allowUnsigned: args.includes("--unsigned"),
24978
25151
  channel,
25152
+ ...certification ? { certification } : {},
25153
+ ...certificationRequirement ? { certificationRequirement } : {},
24979
25154
  googlePlay,
24980
25155
  modulePath: registryModule,
24981
25156
  projectRoot,
@@ -25111,18 +25286,39 @@ var buildIos = async (args, prepareBuildNumber) => {
25111
25286
  });
25112
25287
  }
25113
25288
  };
25289
+ var buildIosCommand = async (args) => {
25290
+ const appStoreConnect = appStoreConnectTarget(args);
25291
+ if (!appStoreConnect)
25292
+ return buildIos(args);
25293
+ const registryModule = args.includes("--registry") ? requireIosValueAfter(args, "--registry") : "mobile.release.ts";
25294
+ const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
25295
+ const marketingVersion = mobile.iosVersion;
25296
+ if (!marketingVersion)
25297
+ throw new TypeError("iOS App Store build allocation requires mobile.ios.version.");
25298
+ const publisher = await loadAbsoluteNativeReleasePublisher(projectRoot, registryModule);
25299
+ return buildIos(args, (buildIdentity) => prepareAbsoluteIosRelease(publisher, {
25300
+ buildIdentity,
25301
+ bundleId: mobile.appId,
25302
+ marketingVersion
25303
+ }));
25304
+ };
25114
25305
  var publishIos = async (args) => {
25115
25306
  const registryModule = args.includes("--registry") ? requireIosValueAfter(args, "--registry") : "mobile.release.ts";
25116
25307
  const channel = args.includes("--channel") ? requireIosValueAfter(args, "--channel") : undefined;
25117
25308
  const configPath = valueAfter(args, "--config");
25118
25309
  const appStoreConnect = appStoreConnectTarget(args);
25119
25310
  const { mobile, projectRoot } = await loadMobile(configPath);
25311
+ const certificationRequirement = resolveAbsoluteMobileCertificationRequirement(mobile, {
25312
+ channel,
25313
+ platform: "ios"
25314
+ });
25120
25315
  const startedAt = performance.now();
25121
25316
  let reused = false;
25122
25317
  let success = false;
25123
25318
  try {
25124
25319
  const publisher = await loadAbsoluteNativeReleasePublisher(projectRoot, registryModule);
25125
- const release = await buildIos(args, appStoreConnect ? (buildIdentity) => {
25320
+ const requestedRelease = args.includes("--release") ? requireIosValueAfter(args, "--release") : undefined;
25321
+ const release = requestedRelease ? await readAbsoluteIosRelease(projectRoot, requestedRelease) : await buildIos(args, appStoreConnect ? (buildIdentity) => {
25126
25322
  if (!mobile.iosVersion)
25127
25323
  throw new TypeError("iOS publishing requires mobile.ios.version.");
25128
25324
  return prepareAbsoluteIosRelease(publisher, {
@@ -25131,10 +25327,20 @@ var publishIos = async (args) => {
25131
25327
  marketingVersion: mobile.iosVersion
25132
25328
  });
25133
25329
  } : undefined);
25330
+ requirePublicationReleaseIdentity(mobile, release, "ios");
25331
+ const certification = await loadPublicationCertification({
25332
+ args,
25333
+ platform: "ios",
25334
+ projectRoot,
25335
+ release,
25336
+ ...certificationRequirement ? { requirement: certificationRequirement } : {}
25337
+ });
25134
25338
  const publication = await publishAbsoluteIosRelease({
25135
25339
  allowUnsigned: args.includes("--unsigned"),
25136
25340
  appStoreConnect,
25137
25341
  channel,
25342
+ ...certification ? { certification } : {},
25343
+ ...certificationRequirement ? { certificationRequirement } : {},
25138
25344
  modulePath: registryModule,
25139
25345
  projectRoot,
25140
25346
  release
@@ -26621,11 +26827,11 @@ var runMobile = async (args) => {
26621
26827
  return;
26622
26828
  }
26623
26829
  if (command === "build" && args[1] === "android") {
26624
- await buildAndroid(args.slice(2));
26830
+ await buildAndroidCommand(args.slice(2));
26625
26831
  return;
26626
26832
  }
26627
26833
  if (command === "build" && args[1] === "ios") {
26628
- await buildIos(args.slice(2));
26834
+ await buildIosCommand(args.slice(2));
26629
26835
  return;
26630
26836
  }
26631
26837
  if (command === "update" && args[1] === "build") {
@@ -26676,7 +26882,7 @@ var runMobile = async (args) => {
26676
26882
  await publishIos(args.slice(2));
26677
26883
  return;
26678
26884
  }
26679
- throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [inspect [name] [--json] | clean [name] --yes | --json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] [--require-bundle] | certify <release-dir> [--evidence report-dir]... [--require installed|simulator|device|store] [--outdir dir] [--json] | certify <release-dir> --verify certification-dir [--require installed|simulator|device|store] [--json] | associations [--outdir dir] [--verify] | ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--remote name] [--registered-device-artifact] [--outdir dir] [--web-outdir dir] [--unsigned] | update provision [--storage local|s3] [--registry module] [--force] [--yes] | update signing generate --private-key path [--certificate path] [--public-key path] [--key-id id] [--common-name name] [--validity-years n] | update build [server-entry] --classification bug-fix|content|security --key-id id --signing-key path --within-submitted-purpose [--outdir dir] [--web-outdir dir] | update publish <release-directory> [--rollout fraction] [--registry module] | update promote --release id --rollout fraction [--registry module] | update rollback [--release id] [--registry module] | update status [--registry module] [--json] | update advance [--rollout fraction] [--registry module] [--json] | update pause|resume|cancel|reconcile [--registry module] [--json] | update storage [--retain count] [--min-age-days days] [--registry module] [--json] | update gc [--retain count] [--min-age-days days] [--grace-days days] [--apply] [--registry module] [--json] | 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] [--remote name] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--release release-dir [--yes] | --route path [--wait-for-hmr] [--port n]] [--report [dir]] [--serial id] [--artifacts dir] [--json] [--config path] | test ios [--release release-dir [--remote name] [--device id [--testflight] --yes] | --wait-for-hmr] [--report [dir]] [--udid id] [--artifacts dir] [--json] [--config path]");
26885
+ throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [inspect [name] [--json] | clean [name] --yes | --json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] [--require-bundle] | certify <release-dir> [--evidence report-dir]... [--require installed|simulator|device|store] [--outdir dir] [--json] | certify <release-dir> --verify certification-dir [--require installed|simulator|device|store] [--json] | associations [--outdir dir] [--verify] | ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--remote name] [--registered-device-artifact] [--outdir dir] [--web-outdir dir] [--unsigned] | update provision [--storage local|s3] [--registry module] [--force] [--yes] | update signing generate --private-key path [--certificate path] [--public-key path] [--key-id id] [--common-name name] [--validity-years n] | update build [server-entry] --classification bug-fix|content|security --key-id id --signing-key path --within-submitted-purpose [--outdir dir] [--web-outdir dir] | update publish <release-directory> [--rollout fraction] [--registry module] | update promote --release id --rollout fraction [--registry module] | update rollback [--release id] [--registry module] | update status [--registry module] [--json] | update advance [--rollout fraction] [--registry module] [--json] | update pause|resume|cancel|reconcile [--registry module] [--json] | update storage [--retain count] [--min-age-days days] [--registry module] [--json] | update gc [--retain count] [--min-age-days days] [--grace-days days] [--apply] [--registry module] [--json] | publish android [server-entry] [--release release-dir] [--certification certification-dir] [--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] [--release release-dir] [--certification certification-dir] [--remote name] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--release release-dir [--yes] | --route path [--wait-for-hmr] [--port n]] [--report [dir]] [--serial id] [--artifacts dir] [--json] [--config path] | test ios [--release release-dir [--remote name] [--device id [--testflight] --yes] | --wait-for-hmr] [--report [dir]] [--udid id] [--artifacts dir] [--json] [--config path]");
26680
26886
  };
26681
26887
  export {
26682
26888
  runMobile