@absolutejs/absolute 0.20.0-beta.1 → 0.20.0-beta.2

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.
@@ -615,7 +615,7 @@ var verifyAbsoluteMobileCompatibilityProducer = async (release, maxProducerBytes
615
615
  return release;
616
616
  };
617
617
  // src/mobile/androidRelease.ts
618
- import { createHash as createHash3 } from "crypto";
618
+ import { createHash as createHash4 } from "crypto";
619
619
  import {
620
620
  access as access3,
621
621
  copyFile as copyFile2,
@@ -702,6 +702,7 @@ import {
702
702
  rm as rm2,
703
703
  writeFile as writeFile3
704
704
  } from "fs/promises";
705
+ import { createHash as createHash3, randomUUID } from "crypto";
705
706
  import {
706
707
  dirname,
707
708
  isAbsolute,
@@ -762,6 +763,8 @@ var writeAbsoluteCapacitorConfig = async (config, options) => {
762
763
  // src/mobile/androidEmulatorController.ts
763
764
  init_getDurationString();
764
765
  var HASH_RADIX = 16;
766
+ var EXECUTABLE_MODE_MASK = 73;
767
+ var NATIVE_PUBLIC_PATH_SEGMENTS = 5;
765
768
  var CAPACITOR_PROJECT_DIRECTORY_PATTERN = /project\(['"](:[^'"]+)['"]\)\.projectDir\s*=\s*new File\(['"]([^'"]+)['"]\)/gu;
766
769
  var pathExists = async (path) => {
767
770
  try {
@@ -826,6 +829,75 @@ var throwIfAborted = (signal) => {
826
829
  return;
827
830
  throw new DOMException("Android development startup was cancelled.", "AbortError");
828
831
  };
832
+ var nativeDependencySources = async (nativeDirectory) => {
833
+ const settings = await readFile3(join3(nativeDirectory, "capacitor.settings.gradle"), "utf8");
834
+ const pattern = new RegExp(CAPACITOR_PROJECT_DIRECTORY_PATTERN.source, CAPACITOR_PROJECT_DIRECTORY_PATTERN.flags);
835
+ const dependencies = [...settings.matchAll(pattern)].map((match) => ({
836
+ name: (match[1] ?? "").slice(1).replaceAll(/[^a-zA-Z0-9_.-]/gu, "_"),
837
+ source: resolve2(nativeDirectory, match[2] ?? "")
838
+ }));
839
+ if (dependencies.length === 0) {
840
+ throw new Error("Capacitor Android settings did not declare any native dependencies.");
841
+ }
842
+ return { dependencies, settings };
843
+ };
844
+ var shouldIgnoreNativePath = (relativePath, ignorePublicBundle) => {
845
+ const parts = relativePath.split(sep);
846
+ if (parts.includes(".gradle") || parts.includes("build") || parts.includes(".absolutejs-dependencies")) {
847
+ return true;
848
+ }
849
+ return ignorePublicBundle && parts.slice(0, NATIVE_PUBLIC_PATH_SEGMENTS).join("/") === "app/src/main/assets/public";
850
+ };
851
+ var collectNativePath = async (root, label, path, isDirectory, isFile, isSymbolicLink, ignorePublicBundle) => {
852
+ const relativePath = relative2(root, path);
853
+ if (shouldIgnoreNativePath(relativePath, ignorePublicBundle))
854
+ return [];
855
+ const identity = `${label}:${relativePath.split(sep).join("/")}\x00`;
856
+ if (isDirectory) {
857
+ return collectNativeDirectory(root, label, path, ignorePublicBundle);
858
+ }
859
+ if (isSymbolicLink) {
860
+ return [`link\x00${identity}${await readlink(path)}\x00`];
861
+ }
862
+ if (!isFile)
863
+ return [];
864
+ const [metadata, contents] = await Promise.all([
865
+ lstat(path),
866
+ readFile3(path)
867
+ ]);
868
+ const contentDigest = createHash3("sha256").update(contents).digest("hex");
869
+ return [
870
+ `file\x00${identity}${metadata.mode & EXECUTABLE_MODE_MASK}\x00${contentDigest}\x00`
871
+ ];
872
+ };
873
+ var collectNativeDirectory = async (root, label, directory, ignorePublicBundle) => {
874
+ const entries = await readdir2(directory, { withFileTypes: true });
875
+ entries.sort((left, right) => left.name.localeCompare(right.name));
876
+ const records = await Promise.all(entries.map((entry) => collectNativePath(root, label, join3(directory, entry.name), entry.isDirectory(), entry.isFile(), entry.isSymbolicLink(), ignorePublicBundle)));
877
+ return records.flat();
878
+ };
879
+ var hashNativeTree = async (root, label, ignorePublicBundle) => {
880
+ const resolvedRoot = await realpath(root);
881
+ const records = await collectNativeDirectory(resolvedRoot, label, resolvedRoot, ignorePublicBundle);
882
+ return createHash3("sha256").update(records.join("")).digest("hex");
883
+ };
884
+ var fingerprintAbsoluteAndroidNativeProject = async (project) => {
885
+ const { dependencies } = await nativeDependencySources(project.nativeDirectory);
886
+ const roots = [
887
+ {
888
+ ignorePublicBundle: true,
889
+ label: "android",
890
+ source: project.nativeDirectory
891
+ },
892
+ ...dependencies.sort((left, right) => left.name.localeCompare(right.name)).map((dependency) => ({
893
+ ignorePublicBundle: false,
894
+ label: `dependency:${dependency.name}`,
895
+ source: dependency.source
896
+ }))
897
+ ];
898
+ const treeDigests = await Promise.all(roots.map((root) => hashNativeTree(root.source, root.label, root.ignorePublicBundle)));
899
+ return createHash3("sha256").update(treeDigests.join("\x00")).digest("hex");
900
+ };
829
901
  var windowsPathFromWsl = (path, capture) => {
830
902
  const result = capture(["wslpath", "-w", path]);
831
903
  if (result.exitCode !== 0 || !result.stdout.trim()) {
@@ -851,12 +923,13 @@ var mirroredCapacitorDependencies = async (project, capture) => {
851
923
  }
852
924
  return { dependencies, rewrittenSettings };
853
925
  };
854
- var encodedWindowsGradleCommand = (windowsSource, windowsDirectory, windowsAndroidRoot, dependencies, rewrittenSettings, task) => {
926
+ var encodedWindowsGradleCommand = (windowsSource, windowsDirectory, windowsAndroidRoot, dependencies, rewrittenSettings, task, gradleArguments) => {
855
927
  const sourceDirectory = Buffer.from(windowsSource, "utf8").toString("base64");
856
928
  const buildDirectory = Buffer.from(windowsDirectory, "utf8").toString("base64");
857
929
  const androidRoot = Buffer.from(windowsAndroidRoot, "utf8").toString("base64");
858
930
  const dependencyData = Buffer.from(JSON.stringify(dependencies), "utf8").toString("base64");
859
931
  const settingsData = Buffer.from(rewrittenSettings, "utf8").toString("base64");
932
+ const argumentsData = Buffer.from(JSON.stringify(gradleArguments), "utf8").toString("base64");
860
933
  const source = [
861
934
  "$ErrorActionPreference = 'Stop'",
862
935
  `$source = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${sourceDirectory}'))`,
@@ -864,6 +937,7 @@ var encodedWindowsGradleCommand = (windowsSource, windowsDirectory, windowsAndro
864
937
  `$androidHome = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${androidRoot}'))`,
865
938
  `$dependencies = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${dependencyData}')) | ConvertFrom-Json`,
866
939
  `$settings = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${settingsData}'))`,
940
+ `$gradleArguments = @([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${argumentsData}')) | ConvertFrom-Json)`,
867
941
  "$env:ANDROID_HOME = $androidHome",
868
942
  "$env:ANDROID_SDK_ROOT = $androidHome",
869
943
  "New-Item -ItemType Directory -Force -Path $directory | Out-Null",
@@ -873,7 +947,7 @@ var encodedWindowsGradleCommand = (windowsSource, windowsDirectory, windowsAndro
873
947
  "foreach ($dependency in @($dependencies)) { $target = Join-Path $directory ('.absolutejs-dependencies\\' + $dependency.name); New-Item -ItemType Directory -Force -Path $target | Out-Null; & robocopy.exe $dependency.windowsSource $target /MIR /XD .gradle build /NFL /NDL /NJH /NJS /NP; if ($LASTEXITCODE -ge 8) { exit $LASTEXITCODE } }",
874
948
  "[IO.File]::WriteAllText((Join-Path $directory 'capacitor.settings.gradle'), $settings)",
875
949
  "$wrapper = Join-Path $directory 'gradlew.bat'",
876
- `& $wrapper --no-daemon --console=plain -p $directory ${task}`,
950
+ `& $wrapper --no-daemon --console=plain -p $directory @gradleArguments ${task}`,
877
951
  "if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }",
878
952
  "exit 0"
879
953
  ].join("; ");
@@ -901,6 +975,7 @@ var buildAbsoluteAndroidGradleArtifact = async (options) => {
901
975
  const capture = options.capture ?? captureCommand;
902
976
  const run = options.run ?? runCommand;
903
977
  const env = options.env ?? process.env;
978
+ const gradleArguments = options.gradleArguments ?? [];
904
979
  if (project.host === "wsl") {
905
980
  const windowsSource = windowsPathFromWsl(project.nativeDirectory, capture);
906
981
  const buildId = Bun.hash(project.projectRoot).toString(HASH_RADIX);
@@ -912,7 +987,7 @@ var buildAbsoluteAndroidGradleArtifact = async (options) => {
912
987
  "powershell.exe",
913
988
  "-NoProfile",
914
989
  "-EncodedCommand",
915
- encodedWindowsGradleCommand(windowsSource, windowsDirectory, windowsAndroidRoot, dependencies, rewrittenSettings, task)
990
+ encodedWindowsGradleCommand(windowsSource, windowsDirectory, windowsAndroidRoot, dependencies, rewrittenSettings, task, gradleArguments)
916
991
  ], "Android Gradle build", run, { env, signal: options.signal });
917
992
  const artifactPath2 = await resolveGradleArtifactPath(managedBuildDirectory, task);
918
993
  const unsigned = artifactPath2.endsWith("-unsigned.apk");
@@ -922,7 +997,7 @@ var buildAbsoluteAndroidGradleArtifact = async (options) => {
922
997
  };
923
998
  }
924
999
  const wrapper = project.host === "windows" ? "gradlew.bat" : "./gradlew";
925
- await requireSuccess([wrapper, "--no-daemon", "--console=plain", task], "Android Gradle build", run, { cwd: project.nativeDirectory, env, signal: options.signal });
1000
+ await requireSuccess([wrapper, "--no-daemon", "--console=plain", ...gradleArguments, task], "Android Gradle build", run, { cwd: project.nativeDirectory, env, signal: options.signal });
926
1001
  const artifactPath = await resolveGradleArtifactPath(project.nativeDirectory, task);
927
1002
  return { artifactPath, installPath: artifactPath };
928
1003
  };
@@ -983,7 +1058,7 @@ var verifyAabSignature = (artifactPath, capture, jarsigner) => {
983
1058
  ]);
984
1059
  return result.exitCode === 0 && /jar verified/iu.test(result.stdout);
985
1060
  };
986
- var sha256File = async (path) => createHash3("sha256").update(await readFile4(path)).digest("hex");
1061
+ var sha256File = async (path) => createHash4("sha256").update(await readFile4(path)).digest("hex");
987
1062
  var safeOutputDirectory = (projectRoot, requested) => {
988
1063
  const root = resolve3(projectRoot);
989
1064
  const output = resolve3(root, requested ?? ".absolutejs/mobile/releases/android");
@@ -1037,12 +1112,32 @@ var requireManifestIdentity = (value, expected) => {
1037
1112
  return { ...expected, artifact };
1038
1113
  };
1039
1114
  var buildAbsoluteAndroidRelease = async (options) => {
1115
+ if (options.versionCode !== undefined && options.prepareVersionCode) {
1116
+ throw new TypeError("Android release versionCode and prepareVersionCode cannot be combined.");
1117
+ }
1118
+ if (options.versionCode !== undefined && (!Number.isSafeInteger(options.versionCode) || options.versionCode < 1 || options.versionCode > 2100000000)) {
1119
+ throw new TypeError("Android versionCode must be an integer from 1 through 2100000000.");
1120
+ }
1040
1121
  const projectRoot = resolve3(options.projectRoot);
1041
1122
  const host = options.host ?? detectAbsoluteMobileHost();
1042
1123
  const androidRoot = options.androidRoot ?? process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host);
1043
1124
  const nativeDirectory = join4(options.config.nativeProjectDirectory, "android");
1125
+ const manifest = requireManifest(JSON.parse(await readFile4(join4(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
1126
+ if (manifest.appId !== options.config.appId) {
1127
+ throw new TypeError("Embedded mobile manifest appId does not match mobile.appId.");
1128
+ }
1129
+ let { versionCode } = options;
1130
+ if (options.prepareVersionCode) {
1131
+ const nativeFingerprint = await fingerprintAbsoluteAndroidNativeProject({ nativeDirectory });
1132
+ const buildIdentity = createHash4("sha256").update(`${manifest.appBuild}\x00${nativeFingerprint}`).digest("hex");
1133
+ versionCode = await options.prepareVersionCode(buildIdentity);
1134
+ }
1135
+ if (versionCode !== undefined && (!Number.isSafeInteger(versionCode) || versionCode < 1 || versionCode > 2100000000)) {
1136
+ throw new TypeError("Android versionCode must be an integer from 1 through 2100000000.");
1137
+ }
1044
1138
  const { artifactPath } = await buildAbsoluteAndroidGradleArtifact({
1045
1139
  capture: options.capture,
1140
+ gradleArguments: versionCode === undefined ? [] : [`-Pandroid.injected.version.code=${versionCode}`],
1046
1141
  project: {
1047
1142
  androidRoot,
1048
1143
  config: options.config,
@@ -1064,10 +1159,6 @@ var buildAbsoluteAndroidRelease = async (options) => {
1064
1159
  if (!signed && !options.allowUnsigned) {
1065
1160
  throw new TypeError("Android Gradle produced an unsigned App Bundle. Configure the release signingConfig in the source-owned Android project (prefer external Gradle properties), or pass --unsigned only for a non-publishable build.");
1066
1161
  }
1067
- const manifest = requireManifest(JSON.parse(await readFile4(join4(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
1068
- if (manifest.appId !== options.config.appId) {
1069
- throw new TypeError("Embedded mobile manifest appId does not match mobile.appId.");
1070
- }
1071
1162
  const [bytes, sha256] = await Promise.all([
1072
1163
  stat(artifactPath).then(({ size }) => size),
1073
1164
  sha256File(artifactPath)
@@ -1084,32 +1175,314 @@ var buildAbsoluteAndroidRelease = async (options) => {
1084
1175
  runtime: manifest.runtime,
1085
1176
  sha256,
1086
1177
  signed: signed === true,
1087
- type: "aab"
1178
+ type: "aab",
1179
+ ...versionCode === undefined ? {} : { versionCode }
1088
1180
  };
1089
1181
  return installRelease(artifactPath, metadata, safeOutputDirectory(projectRoot, options.outputDirectory));
1090
1182
  };
1091
- // src/mobile/associationFiles.ts
1183
+ // src/mobile/iosRelease.ts
1184
+ import { createHash as createHash5 } from "crypto";
1092
1185
  import {
1093
1186
  access as access4,
1187
+ copyFile as copyFile3,
1094
1188
  mkdir as mkdir4,
1189
+ mkdtemp as mkdtemp3,
1190
+ readdir as readdir3,
1095
1191
  readFile as readFile5,
1096
1192
  rename as rename5,
1097
1193
  rm as rm4,
1194
+ stat as stat2,
1098
1195
  writeFile as writeFile5
1099
1196
  } from "fs/promises";
1100
- import { resolve as resolve5 } from "path";
1197
+ import { dirname as dirname3, isAbsolute as isAbsolute3, join as join5, relative as relative4, resolve as resolve4, sep as sep3 } from "path";
1198
+ var ABSOLUTE_IOS_RELEASE_FORMAT = 1;
1199
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1200
+ var requireManifest2 = (value) => {
1201
+ if (!isRecord2(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
1202
+ throw new TypeError("Invalid embedded AbsoluteJS mobile manifest.");
1203
+ }
1204
+ return {
1205
+ appBuild: value.appBuild,
1206
+ appId: value.appId,
1207
+ runtime: value.runtime
1208
+ };
1209
+ };
1210
+ var pathExists3 = async (path) => {
1211
+ try {
1212
+ await access4(path);
1213
+ return true;
1214
+ } catch {
1215
+ return false;
1216
+ }
1217
+ };
1218
+ var defaultRun = async (command, options = {}) => {
1219
+ const process2 = Bun.spawn(command, {
1220
+ cwd: options.cwd,
1221
+ env: options.env,
1222
+ stderr: "inherit",
1223
+ stdin: "inherit",
1224
+ stdout: "inherit"
1225
+ });
1226
+ return process2.exited;
1227
+ };
1228
+ var defaultCapture2 = (command, options = {}) => {
1229
+ try {
1230
+ const result = Bun.spawnSync(command, {
1231
+ cwd: options.cwd,
1232
+ env: options.env,
1233
+ stderr: "pipe",
1234
+ stdin: "ignore",
1235
+ stdout: "pipe"
1236
+ });
1237
+ return {
1238
+ exitCode: result.exitCode,
1239
+ stderr: result.stderr.toString(),
1240
+ stdout: result.stdout.toString()
1241
+ };
1242
+ } catch (error) {
1243
+ return {
1244
+ exitCode: 1,
1245
+ stderr: error instanceof Error ? error.message : String(error),
1246
+ stdout: ""
1247
+ };
1248
+ }
1249
+ };
1250
+ var ignoredFingerprintDirectories = new Set([
1251
+ "Pods",
1252
+ "DerivedData",
1253
+ "build",
1254
+ "xcuserdata"
1255
+ ]);
1256
+ var fingerprintFiles = async (root, current = root) => {
1257
+ const entries = await readdir3(current, { withFileTypes: true });
1258
+ const nested = await Promise.all(entries.sort((left, right) => left.name.localeCompare(right.name)).map(async (entry) => {
1259
+ const path = join5(current, entry.name);
1260
+ const projectRelative = relative4(root, path).replaceAll("\\", "/");
1261
+ const ignored = entry.isDirectory() && (ignoredFingerprintDirectories.has(entry.name) || projectRelative === "App/App/public");
1262
+ if (ignored)
1263
+ return [];
1264
+ if (entry.isDirectory())
1265
+ return fingerprintFiles(root, path);
1266
+ return entry.isFile() ? [path] : [];
1267
+ }));
1268
+ return nested.flat();
1269
+ };
1270
+ var fingerprintAbsoluteIosNativeProject = async (nativeDirectory) => {
1271
+ const hasher = createHash5("sha256");
1272
+ const files = await fingerprintFiles(nativeDirectory);
1273
+ const contents = await Promise.all(files.map((file) => readFile5(file)));
1274
+ files.forEach((file, index) => {
1275
+ hasher.update(relative4(nativeDirectory, file).replaceAll("\\", "/"));
1276
+ hasher.update("\x00");
1277
+ hasher.update(contents[index] ?? new Uint8Array);
1278
+ hasher.update("\x00");
1279
+ });
1280
+ return hasher.digest("hex");
1281
+ };
1282
+ var safeOutputDirectory2 = (projectRoot, requested) => {
1283
+ const root = resolve4(projectRoot);
1284
+ const output = resolve4(root, requested ?? ".absolutejs/mobile/releases/ios");
1285
+ const projectRelative = relative4(root, output);
1286
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep3}`) || isAbsolute3(projectRelative)) {
1287
+ throw new TypeError("mobile build --outdir must remain inside the project.");
1288
+ }
1289
+ return output;
1290
+ };
1291
+ var sha256File2 = async (path) => createHash5("sha256").update(await readFile5(path)).digest("hex");
1292
+ var findByExtension = async (root, extension) => {
1293
+ if (!await pathExists3(root))
1294
+ return;
1295
+ const entries = await readdir3(root, { withFileTypes: true });
1296
+ const matches = await Promise.all(entries.map(async (entry) => {
1297
+ const path = join5(root, entry.name);
1298
+ if (entry.isDirectory() && entry.name.endsWith(extension))
1299
+ return path;
1300
+ if (entry.isFile() && entry.name.endsWith(extension))
1301
+ return path;
1302
+ return entry.isDirectory() ? findByExtension(path, extension) : undefined;
1303
+ }));
1304
+ return matches.find((match) => match !== undefined);
1305
+ };
1306
+ var exportOptions = () => `<?xml version="1.0" encoding="UTF-8"?>
1307
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1308
+ <plist version="1.0"><dict>
1309
+ <key>destination</key><string>export</string>
1310
+ <key>manageAppVersionAndBuildNumber</key><false/>
1311
+ <key>method</key><string>app-store-connect</string>
1312
+ <key>signingStyle</key><string>automatic</string>
1313
+ <key>stripSwiftSymbols</key><true/>
1314
+ <key>uploadSymbols</key><true/>
1315
+ </dict></plist>
1316
+ `;
1317
+ var requireBuildNumber = (value) => {
1318
+ if (value !== undefined && (!Number.isSafeInteger(value) || value < 1))
1319
+ throw new TypeError("iOS build number must be a positive integer.");
1320
+ return value;
1321
+ };
1322
+ var installRelease2 = async (artifactPath, metadata, outputRoot) => {
1323
+ const releaseRoot = join5(outputRoot, metadata.releaseId);
1324
+ const destination = join5(releaseRoot, "App.ipa");
1325
+ if (await pathExists3(releaseRoot)) {
1326
+ const value = JSON.parse(await readFile5(join5(releaseRoot, "release.json"), "utf8"));
1327
+ if (!isRecord2(value) || value.artifact !== "App.ipa" || Object.entries(metadata).some(([key, expected]) => Reflect.get(value, key) !== expected)) {
1328
+ throw new TypeError(`Immutable iOS release ${metadata.releaseId} does not match its content.`);
1329
+ }
1330
+ const [bytes, sha256] = await Promise.all([
1331
+ stat2(destination).then(({ size }) => size),
1332
+ sha256File2(destination)
1333
+ ]);
1334
+ if (bytes !== metadata.bytes || sha256 !== metadata.sha256)
1335
+ throw new TypeError(`Immutable iOS release ${metadata.releaseId} artifact is missing or modified.`);
1336
+ return {
1337
+ artifactPath: destination,
1338
+ metadata: {
1339
+ ...metadata,
1340
+ artifact: "App.ipa"
1341
+ },
1342
+ releaseRoot
1343
+ };
1344
+ }
1345
+ await mkdir4(dirname3(releaseRoot), { recursive: true });
1346
+ const staging = await mkdtemp3(join5(dirname3(releaseRoot), ".ios-stage-"));
1347
+ try {
1348
+ await copyFile3(artifactPath, join5(staging, "App.ipa"));
1349
+ const complete = {
1350
+ ...metadata,
1351
+ artifact: "App.ipa"
1352
+ };
1353
+ await writeFile5(join5(staging, "release.json"), `${JSON.stringify(complete, null, "\t")}
1354
+ `, { flag: "wx" });
1355
+ await rename5(staging, releaseRoot);
1356
+ return { artifactPath: destination, metadata: complete, releaseRoot };
1357
+ } finally {
1358
+ await rm4(staging, { force: true, recursive: true }).catch(() => {
1359
+ return;
1360
+ });
1361
+ }
1362
+ };
1363
+ var buildAbsoluteIosRelease = async (options) => {
1364
+ if (options.buildNumber !== undefined && options.prepareBuildNumber)
1365
+ throw new TypeError("iOS release buildNumber and prepareBuildNumber cannot be combined.");
1366
+ if ((options.host ?? detectAbsoluteMobileHost()) !== "macos")
1367
+ throw new TypeError("iOS release builds require macOS and Xcode.");
1368
+ const marketingVersion = options.config.iosVersion;
1369
+ if (!marketingVersion)
1370
+ throw new TypeError("iOS release builds require mobile.ios.version in absolutejs.config.ts.");
1371
+ const manifest = requireManifest2(JSON.parse(await readFile5(join5(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
1372
+ if (manifest.appId !== options.config.appId)
1373
+ throw new TypeError("Embedded mobile manifest appId does not match mobile.appId.");
1374
+ const nativeDirectory = join5(options.config.nativeProjectDirectory, "ios");
1375
+ let buildNumber = requireBuildNumber(options.buildNumber);
1376
+ if (options.prepareBuildNumber) {
1377
+ const nativeFingerprint = await fingerprintAbsoluteIosNativeProject(nativeDirectory);
1378
+ const buildIdentity = createHash5("sha256").update(`${manifest.appBuild}\x00${nativeFingerprint}\x00${marketingVersion}`).digest("hex");
1379
+ buildNumber = requireBuildNumber(await options.prepareBuildNumber(buildIdentity));
1380
+ }
1381
+ const stagingParent = resolve4(options.projectRoot, ".absolutejs/mobile");
1382
+ await mkdir4(stagingParent, { recursive: true });
1383
+ const staging = await mkdtemp3(join5(stagingParent, ".ios-build-"));
1384
+ const archivePath = join5(staging, "App.xcarchive");
1385
+ const exportPath = join5(staging, "export");
1386
+ const exportPlist = join5(staging, "ExportOptions.plist");
1387
+ await mkdir4(exportPath, { recursive: true });
1388
+ await writeFile5(exportPlist, exportOptions());
1389
+ const run = options.run ?? defaultRun;
1390
+ try {
1391
+ const versionArguments = [
1392
+ `MARKETING_VERSION=${marketingVersion}`,
1393
+ ...buildNumber === undefined ? [] : [`CURRENT_PROJECT_VERSION=${buildNumber}`]
1394
+ ];
1395
+ const archiveExit = await run([
1396
+ "xcodebuild",
1397
+ "-workspace",
1398
+ join5(nativeDirectory, "App", "App.xcworkspace"),
1399
+ "-scheme",
1400
+ "App",
1401
+ "-configuration",
1402
+ "Release",
1403
+ "-destination",
1404
+ "generic/platform=iOS",
1405
+ "-archivePath",
1406
+ archivePath,
1407
+ ...versionArguments,
1408
+ "archive"
1409
+ ], { cwd: nativeDirectory });
1410
+ if (archiveExit !== 0)
1411
+ throw new TypeError("Xcode failed to archive the iOS app.");
1412
+ const archivedApp = await findByExtension(join5(archivePath, "Products", "Applications"), ".app");
1413
+ const capture = options.capture ?? defaultCapture2;
1414
+ const signed = archivedApp ? capture([
1415
+ "codesign",
1416
+ "--verify",
1417
+ "--deep",
1418
+ "--strict",
1419
+ archivedApp
1420
+ ]).exitCode === 0 : false;
1421
+ if (!signed && !options.allowUnsigned)
1422
+ throw new TypeError("Xcode produced an unsigned iOS archive. Configure signing in the source-owned Xcode project, or pass --unsigned only for a non-publishable build.");
1423
+ const exportExit = await run([
1424
+ "xcodebuild",
1425
+ "-exportArchive",
1426
+ "-archivePath",
1427
+ archivePath,
1428
+ "-exportPath",
1429
+ exportPath,
1430
+ "-exportOptionsPlist",
1431
+ exportPlist
1432
+ ], { cwd: nativeDirectory });
1433
+ if (exportExit !== 0)
1434
+ throw new TypeError("Xcode failed to export the App Store IPA.");
1435
+ const artifactPath = await findByExtension(exportPath, ".ipa");
1436
+ if (!artifactPath)
1437
+ throw new TypeError("Xcode did not produce an exported IPA.");
1438
+ const [bytes, sha256] = await Promise.all([
1439
+ stat2(artifactPath).then(({ size }) => size),
1440
+ sha256File2(artifactPath)
1441
+ ]);
1442
+ const releaseId = `amobile_ios_${sha256}`;
1443
+ return await installRelease2(artifactPath, {
1444
+ appBuild: manifest.appBuild,
1445
+ appId: manifest.appId,
1446
+ ...buildNumber === undefined ? {} : { buildNumber },
1447
+ bytes,
1448
+ engine: "capacitor",
1449
+ format: 1,
1450
+ marketingVersion,
1451
+ platform: "ios",
1452
+ releaseId,
1453
+ runtime: manifest.runtime,
1454
+ sha256,
1455
+ signed,
1456
+ type: "ipa"
1457
+ }, safeOutputDirectory2(options.projectRoot, options.outputDirectory));
1458
+ } finally {
1459
+ await rm4(staging, { force: true, recursive: true }).catch(() => {
1460
+ return;
1461
+ });
1462
+ }
1463
+ };
1464
+ // src/mobile/associationFiles.ts
1465
+ import {
1466
+ access as access5,
1467
+ mkdir as mkdir5,
1468
+ readFile as readFile6,
1469
+ rename as rename6,
1470
+ rm as rm5,
1471
+ writeFile as writeFile6
1472
+ } from "fs/promises";
1473
+ import { resolve as resolve6 } from "path";
1101
1474
  import { Elysia } from "elysia";
1102
1475
 
1103
1476
  // src/mobile/config.ts
1104
- import { resolve as resolve4 } from "path";
1477
+ import { resolve as resolve5 } from "path";
1105
1478
  var APP_ID_PATTERN = /^[A-Za-z][\w]*(?:\.[A-Za-z][\w]*)+$/;
1106
1479
  var SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*$/;
1107
1480
  var APPLE_APP_ID_PREFIX_PATTERN = /^[A-Z0-9]{10}$/;
1108
1481
  var CERTIFICATE_FINGERPRINT_PATTERN = /^[0-9A-F]{64}$/;
1109
1482
  var HOSTNAME_PATTERN = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?))*$/;
1110
1483
  var resolveProjectPath = (projectRoot, value, field) => {
1111
- const root = resolve4(projectRoot);
1112
- const path = resolve4(root, value);
1484
+ const root = resolve5(projectRoot);
1485
+ const path = resolve5(root, value);
1113
1486
  if (path !== root && !path.startsWith(`${root}/`)) {
1114
1487
  throw new TypeError(`${field} must remain inside the project root.`);
1115
1488
  }
@@ -1172,6 +1545,15 @@ var normalizeAppleAppIdPrefix = (value) => {
1172
1545
  }
1173
1546
  return normalized;
1174
1547
  };
1548
+ var normalizeIosVersion = (value) => {
1549
+ if (value === undefined)
1550
+ return;
1551
+ const normalized = requireText(value, "mobile.ios.version");
1552
+ if (!/^\d+(?:\.\d+){0,2}$/u.test(normalized)) {
1553
+ throw new TypeError("mobile.ios.version must contain one to three dot-separated integer components, for example 1.4.0.");
1554
+ }
1555
+ return normalized;
1556
+ };
1175
1557
  var normalizeCertificateFingerprints = (values) => [
1176
1558
  ...new Set((values ?? []).map((value) => requireText(value, "mobile.deepLinks.android.sha256CertificateFingerprints").replaceAll(":", "").toUpperCase()).map((value) => {
1177
1559
  if (!CERTIFICATE_FINGERPRINT_PATTERN.test(value)) {
@@ -1200,6 +1582,7 @@ var normalizeAbsoluteMobileConfig = (config, projectRoot) => {
1200
1582
  deepLinkScheme,
1201
1583
  engine: "capacitor",
1202
1584
  entry: normalizeEntry(config.entry),
1585
+ iosVersion: normalizeIosVersion(config.ios?.version),
1203
1586
  nativeProjectDirectory: resolveProjectPath(projectRoot, config.nativeProject?.directory ?? "mobile", "mobile.nativeProject.directory"),
1204
1587
  platforms: normalizePlatforms(config.platforms),
1205
1588
  productionOrigin
@@ -1287,7 +1670,7 @@ var createAbsoluteMobileAssociationPlugin = (mobile, projectRoot, options = {})
1287
1670
  var writeAtomic = async (path, source) => {
1288
1671
  let current;
1289
1672
  try {
1290
- current = await readFile5(path, "utf8");
1673
+ current = await readFile6(path, "utf8");
1291
1674
  } catch (error) {
1292
1675
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
1293
1676
  throw error;
@@ -1296,23 +1679,23 @@ var writeAtomic = async (path, source) => {
1296
1679
  if (current === source)
1297
1680
  return false;
1298
1681
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
1299
- await writeFile5(temporary, source, { flag: "wx" });
1300
- await rename5(temporary, path);
1682
+ await writeFile6(temporary, source, { flag: "wx" });
1683
+ await rename6(temporary, path);
1301
1684
  return true;
1302
1685
  };
1303
1686
  var exists2 = async (path) => {
1304
1687
  try {
1305
- await access4(path);
1688
+ await access5(path);
1306
1689
  return true;
1307
1690
  } catch {
1308
1691
  return false;
1309
1692
  }
1310
1693
  };
1311
1694
  var assertOwnedOutput = async (root) => {
1312
- const path = resolve5(root, OWNERSHIP_FILE);
1695
+ const path = resolve6(root, OWNERSHIP_FILE);
1313
1696
  let ownership;
1314
1697
  try {
1315
- ownership = JSON.parse(await readFile5(path, "utf8"));
1698
+ ownership = JSON.parse(await readFile6(path, "utf8"));
1316
1699
  } catch {
1317
1700
  throw new TypeError(`Association output ${root} already exists and is not owned by AbsoluteJS.`);
1318
1701
  }
@@ -1326,22 +1709,22 @@ var publishGeneratedDirectory = async (temporary, root) => {
1326
1709
  await assertOwnedOutput(root);
1327
1710
  const backup = `${root}.${crypto.randomUUID()}.previous`;
1328
1711
  if (hasCurrent)
1329
- await rename5(root, backup);
1712
+ await rename6(root, backup);
1330
1713
  try {
1331
- await rename5(temporary, root);
1714
+ await rename6(temporary, root);
1332
1715
  } catch (error) {
1333
1716
  if (hasCurrent)
1334
- await rename5(backup, root);
1717
+ await rename6(backup, root);
1335
1718
  throw error;
1336
1719
  }
1337
1720
  if (hasCurrent)
1338
- await rm4(backup, { force: true, recursive: true });
1721
+ await rm5(backup, { force: true, recursive: true });
1339
1722
  };
1340
1723
  var materializeHost = async (root, host, files) => {
1341
- const directory = resolve5(root, host, ".well-known");
1342
- await mkdir4(directory, { recursive: true });
1724
+ const directory = resolve6(root, host, ".well-known");
1725
+ await mkdir5(directory, { recursive: true });
1343
1726
  return Promise.all(files.map(async ([name, document]) => {
1344
- const path = resolve5(directory, name);
1727
+ const path = resolve6(directory, name);
1345
1728
  await writeAtomic(path, `${JSON.stringify(document, null, 2)}
1346
1729
  `);
1347
1730
  return path;
@@ -1366,7 +1749,7 @@ var associationEndpoints = (config, documents) => config.deepLinkHosts.flatMap((
1366
1749
  return endpoints;
1367
1750
  });
1368
1751
  var materializeAbsoluteMobileAssociationFiles = async (config, outputDirectory) => {
1369
- const root = resolve5(outputDirectory);
1752
+ const root = resolve6(outputDirectory);
1370
1753
  const temporary = `${root}.${crypto.randomUUID()}.tmp`;
1371
1754
  const documents = createAbsoluteMobileAssociationDocuments(config, {
1372
1755
  requireAll: true
@@ -1377,16 +1760,16 @@ var materializeAbsoluteMobileAssociationFiles = async (config, outputDirectory)
1377
1760
  if (documents.apple) {
1378
1761
  files.push(["apple-app-site-association", documents.apple]);
1379
1762
  }
1380
- await mkdir4(temporary, { recursive: true });
1763
+ await mkdir5(temporary, { recursive: true });
1381
1764
  try {
1382
1765
  const temporaryPaths = (await Promise.all(config.deepLinkHosts.map((host) => materializeHost(temporary, host, files)))).flat();
1383
- await writeAtomic(resolve5(temporary, OWNERSHIP_FILE), `${JSON.stringify({ format: 1, hosts: config.deepLinkHosts }, null, 2)}
1766
+ await writeAtomic(resolve6(temporary, OWNERSHIP_FILE), `${JSON.stringify({ format: 1, hosts: config.deepLinkHosts }, null, 2)}
1384
1767
  `);
1385
1768
  await publishGeneratedDirectory(temporary, root);
1386
- const written = temporaryPaths.map((path) => resolve5(root, path.slice(temporary.length + 1)));
1769
+ const written = temporaryPaths.map((path) => resolve6(root, path.slice(temporary.length + 1)));
1387
1770
  return { root, written };
1388
1771
  } catch (error) {
1389
- await rm4(temporary, { force: true, recursive: true });
1772
+ await rm5(temporary, { force: true, recursive: true });
1390
1773
  throw error;
1391
1774
  }
1392
1775
  };
@@ -1431,10 +1814,10 @@ var frameworks2 = new Set([
1431
1814
  "svelte",
1432
1815
  "vue"
1433
1816
  ]);
1434
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1817
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1435
1818
  var isPageFramework2 = (value) => typeof value === "string" && frameworks2.has(value);
1436
1819
  var parseAbsoluteMobileBuildPageMetadata = (value) => {
1437
- if (!isRecord2(value))
1820
+ if (!isRecord3(value))
1438
1821
  return;
1439
1822
  if (typeof value.bundleKey !== "string" || typeof value.contract !== "string" || !isPageFramework2(value.framework) || typeof value.pageId !== "string" || typeof value.propsSchemaHash !== "string") {
1440
1823
  return;
@@ -1448,23 +1831,23 @@ var parseAbsoluteMobileBuildPageMetadata = (value) => {
1448
1831
  };
1449
1832
  };
1450
1833
  // src/mobile/buildPipeline.ts
1451
- import { readFile as readFile9 } from "fs/promises";
1452
- import { join as join8, resolve as resolve8 } from "path";
1834
+ import { readFile as readFile10 } from "fs/promises";
1835
+ import { join as join9, resolve as resolve9 } from "path";
1453
1836
  import { pathToFileURL as pathToFileURL2 } from "url";
1454
1837
 
1455
1838
  // src/mobile/buildRelease.ts
1456
- import { createHash as createHash4 } from "crypto";
1457
- import { readFile as readFile6 } from "fs/promises";
1458
- import { join as join5, relative as relative4, resolve as resolve6 } from "path";
1459
- var sha256 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
1839
+ import { createHash as createHash6 } from "crypto";
1840
+ import { readFile as readFile7 } from "fs/promises";
1841
+ import { join as join6, relative as relative5, resolve as resolve7 } from "path";
1842
+ var sha256 = (bytes) => createHash6("sha256").update(bytes).digest("hex");
1460
1843
  var readPageMetadata = (route) => parseAbsoluteMobileBuildPageMetadata(route.hooks?.detail?.[ABSOLUTE_MOBILE_ROUTE_DETAIL]);
1461
1844
  var resolveAssetPath = (buildDirectory, assetPath) => {
1462
- const resolvedBuildDirectory = resolve6(buildDirectory);
1463
- const resolvedAsset = resolve6(assetPath);
1845
+ const resolvedBuildDirectory = resolve7(buildDirectory);
1846
+ const resolvedAsset = resolve7(assetPath);
1464
1847
  if (resolvedAsset.startsWith(`${resolvedBuildDirectory}/`)) {
1465
1848
  return resolvedAsset;
1466
1849
  }
1467
- return join5(buildDirectory, assetPath.replace(/^\/+/, ""));
1850
+ return join6(buildDirectory, assetPath.replace(/^\/+/, ""));
1468
1851
  };
1469
1852
  var pageFor = async (metadata, manifest, buildDirectory) => {
1470
1853
  const assetPath = manifest[metadata.bundleKey];
@@ -1472,8 +1855,8 @@ var pageFor = async (metadata, manifest, buildDirectory) => {
1472
1855
  throw new TypeError(`Mobile page ${metadata.pageId} references missing manifest asset ${metadata.bundleKey}.`);
1473
1856
  }
1474
1857
  const resolvedAssetPath = resolveAssetPath(buildDirectory, assetPath);
1475
- const bytes = await readFile6(resolvedAssetPath);
1476
- const bundlePath = `/${relative4(resolve6(buildDirectory), resolvedAssetPath).replaceAll("\\", "/")}`;
1858
+ const bytes = await readFile7(resolvedAssetPath);
1859
+ const bundlePath = `/${relative5(resolve7(buildDirectory), resolvedAssetPath).replaceAll("\\", "/")}`;
1477
1860
  return {
1478
1861
  bundleHash: sha256(bytes),
1479
1862
  bundlePath,
@@ -1486,7 +1869,7 @@ var pageFor = async (metadata, manifest, buildDirectory) => {
1486
1869
  var buildAbsoluteMobileCompatibilityRelease = async (options) => {
1487
1870
  const [captured, producerBytes] = await Promise.all([
1488
1871
  captureAbsoluteMobileRouteGraph(options.app),
1489
- readFile6(options.producerPath)
1872
+ readFile7(options.producerPath)
1490
1873
  ]);
1491
1874
  if (captured.length === 0) {
1492
1875
  throw new TypeError("No instrumented AbsoluteJS mobile page routes were found in the finalized Elysia route graph.");
@@ -1566,16 +1949,16 @@ var captureAbsoluteMobileRouteGraph = async (app) => {
1566
1949
 
1567
1950
  // src/mobile/capacitorBundle.ts
1568
1951
  import {
1569
- copyFile as copyFile3,
1570
- mkdir as mkdir5,
1571
- mkdtemp as mkdtemp3,
1572
- readFile as readFile7,
1573
- rename as rename6,
1574
- rm as rm5,
1575
- writeFile as writeFile6
1952
+ copyFile as copyFile4,
1953
+ mkdir as mkdir6,
1954
+ mkdtemp as mkdtemp4,
1955
+ readFile as readFile8,
1956
+ rename as rename7,
1957
+ rm as rm6,
1958
+ writeFile as writeFile7
1576
1959
  } from "fs/promises";
1577
1960
  import { existsSync as existsSync2 } from "fs";
1578
- import { basename, dirname as dirname3, extname, join as join6, resolve as resolve7 } from "path";
1961
+ import { basename, dirname as dirname4, extname, join as join7, resolve as resolve8 } from "path";
1579
1962
 
1580
1963
  // src/mobile/routeMatcher.ts
1581
1964
  var REGEXP_SPECIAL_CHARACTERS = /[.*+?^${}()|[\]\\]/g;
@@ -1731,14 +2114,14 @@ var envelopeResponse = (response, status) => new Response(JSON.stringify({
1731
2114
  protocol: ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION,
1732
2115
  response
1733
2116
  }), { headers: responseHeaders(), status });
1734
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2117
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1735
2118
  var normalizeJsonValue = (value) => {
1736
2119
  const serialized = JSON.stringify(value);
1737
2120
  if (serialized === undefined) {
1738
2121
  throw new TypeError("Mobile page props must be JSON-serializable.");
1739
2122
  }
1740
2123
  const parsed = JSON.parse(serialized);
1741
- if (!isRecord3(parsed)) {
2124
+ if (!isRecord4(parsed)) {
1742
2125
  throw new TypeError("Mobile page props must serialize to an object.");
1743
2126
  }
1744
2127
  return parsed;
@@ -1863,14 +2246,14 @@ var upgradeReasons = new Set([
1863
2246
  "protocol",
1864
2247
  "runtime"
1865
2248
  ]);
1866
- var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2249
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1867
2250
  var isFramework = (value) => typeof value === "string" && frameworks4.has(value);
1868
2251
  var isUpgradeReason = (value) => typeof value === "string" && upgradeReasons.has(value);
1869
2252
  var parsePageResult = (value) => {
1870
2253
  if (typeof value.contract !== "string" || !isFramework(value.framework) || typeof value.pageId !== "string" || typeof value.status !== "number") {
1871
2254
  throw new AbsoluteMobilePageProtocolError("invalid-envelope", "The mobile page response is missing required page metadata.");
1872
2255
  }
1873
- if (!isRecord4(value.props)) {
2256
+ if (!isRecord5(value.props)) {
1874
2257
  throw new AbsoluteMobilePageProtocolError("invalid-props", "The mobile page response must contain an object props value.");
1875
2258
  }
1876
2259
  return {
@@ -1916,7 +2299,7 @@ var activateAbsoluteMobilePage = async (value, options) => {
1916
2299
  };
1917
2300
  };
1918
2301
  var parseAbsoluteMobilePageEnvelope = (value) => {
1919
- if (!isRecord4(value) || !isRecord4(value.response)) {
2302
+ if (!isRecord5(value) || !isRecord5(value.response)) {
1920
2303
  throw new AbsoluteMobilePageProtocolError("invalid-envelope", "The server did not return an AbsoluteJS mobile page envelope.");
1921
2304
  }
1922
2305
  if (value.protocol !== ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION) {
@@ -2009,7 +2392,7 @@ var INDEX_FILE = "index.html";
2009
2392
  var CLIENT_IMPORT_PATTERN = /(?:\bfrom\s*|\bimport\s*\(\s*|\bimport\s*)["'](\/[^"']+)["']/gu;
2010
2393
  var errorHasCode2 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code;
2011
2394
  var shellBootstrapModule = () => {
2012
- const candidate = ["js", "ts"].map((extension) => join6(import.meta.dir, `shellBootstrap.${extension}`)).find(existsSync2);
2395
+ const candidate = ["js", "ts"].map((extension) => join7(import.meta.dir, `shellBootstrap.${extension}`)).find(existsSync2);
2013
2396
  if (candidate)
2014
2397
  return candidate;
2015
2398
  throw new TypeError("AbsoluteJS mobile shell bootstrap module is missing.");
@@ -2030,8 +2413,8 @@ var indexHtml = (appName) => `<!doctype html>
2030
2413
  </html>
2031
2414
  `;
2032
2415
  var sourceAssetPath = (buildDirectory, bundlePath) => {
2033
- const root = resolve7(buildDirectory);
2034
- const asset = resolve7(root, bundlePath.replace(/^\/+/, ""));
2416
+ const root = resolve8(buildDirectory);
2417
+ const asset = resolve8(root, bundlePath.replace(/^\/+/, ""));
2035
2418
  if (!asset.startsWith(`${root}/`)) {
2036
2419
  throw new TypeError("Mobile page bundle escaped the build directory.");
2037
2420
  }
@@ -2039,8 +2422,8 @@ var sourceAssetPath = (buildDirectory, bundlePath) => {
2039
2422
  };
2040
2423
  var buildShellBootstrap = async (staging) => {
2041
2424
  const modulePath = shellBootstrapModule();
2042
- const entryPath = join6(staging, ".absolute-mobile-entry.ts");
2043
- await writeFile6(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
2425
+ const entryPath = join7(staging, ".absolute-mobile-entry.ts");
2426
+ await writeFile7(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
2044
2427
  void startAbsoluteMobileShell();
2045
2428
  `);
2046
2429
  const build = await Bun.build({
@@ -2052,31 +2435,31 @@ void startAbsoluteMobileShell();
2052
2435
  if (!build.success || build.outputs.length !== 1) {
2053
2436
  throw new AggregateError(build.logs, "Failed to build the AbsoluteJS Capacitor shell.");
2054
2437
  }
2055
- await rename6(build.outputs[0]?.path ?? "", join6(staging, BOOTSTRAP_FILE));
2056
- await rm5(entryPath, { force: true });
2438
+ await rename7(build.outputs[0]?.path ?? "", join7(staging, BOOTSTRAP_FILE));
2439
+ await rm6(entryPath, { force: true });
2057
2440
  };
2058
2441
  var removePreviousBundle = async (backup, moved) => {
2059
2442
  if (!moved)
2060
2443
  return;
2061
- await rm5(backup, { force: true, recursive: true });
2444
+ await rm6(backup, { force: true, recursive: true });
2062
2445
  };
2063
2446
  var restorePreviousBundle = async (backup, destination, moved) => {
2064
2447
  if (!moved)
2065
2448
  return;
2066
- await rename6(backup, destination);
2449
+ await rename7(backup, destination);
2067
2450
  };
2068
2451
  var installBundle = async (staging, destination) => {
2069
2452
  const backup = `${destination}.previous-${crypto.randomUUID()}`;
2070
2453
  let movedPrevious = false;
2071
2454
  try {
2072
- await rename6(destination, backup);
2455
+ await rename7(destination, backup);
2073
2456
  movedPrevious = true;
2074
2457
  } catch (error) {
2075
2458
  if (!errorHasCode2(error, "ENOENT"))
2076
2459
  throw error;
2077
2460
  }
2078
2461
  try {
2079
- await rename6(staging, destination);
2462
+ await rename7(staging, destination);
2080
2463
  await removePreviousBundle(backup, movedPrevious);
2081
2464
  } catch (error) {
2082
2465
  await restorePreviousBundle(backup, destination, movedPrevious);
@@ -2090,12 +2473,12 @@ var copyClientPage = async (page, buildDirectory, staging, copiedDependencies) =
2090
2473
  const extension = extname(page.bundlePath) || ".js";
2091
2474
  const localBundlePath = `./pages/${page.bundleHash}${extension}`;
2092
2475
  const source = sourceAssetPath(buildDirectory, page.bundlePath);
2093
- await copyFile3(source, join6(staging, localBundlePath));
2476
+ await copyFile4(source, join7(staging, localBundlePath));
2094
2477
  await copyAbsoluteClientDependencies(source, buildDirectory, staging, copiedDependencies);
2095
2478
  return { ...page, localBundlePath };
2096
2479
  };
2097
2480
  var absoluteClientImports = async (sourcePath) => {
2098
- const source = await readFile7(sourcePath, "utf8");
2481
+ const source = await readFile8(sourcePath, "utf8");
2099
2482
  return [...source.matchAll(CLIENT_IMPORT_PATTERN)].flatMap((match) => {
2100
2483
  const [specifier] = match.slice(1);
2101
2484
  return specifier ? [specifier.split(/[?#]/u, 1)[0] ?? specifier] : [];
@@ -2106,9 +2489,9 @@ var copyAbsoluteClientDependency = async (specifier, buildDirectory, staging, co
2106
2489
  return;
2107
2490
  copied.add(specifier);
2108
2491
  const source = sourceAssetPath(buildDirectory, specifier);
2109
- const destination = join6(staging, specifier.replace(/^\/+/, ""));
2110
- await mkdir5(dirname3(destination), { recursive: true });
2111
- await copyFile3(source, destination);
2492
+ const destination = join7(staging, specifier.replace(/^\/+/, ""));
2493
+ await mkdir6(dirname4(destination), { recursive: true });
2494
+ await copyFile4(source, destination);
2112
2495
  await copyAbsoluteClientDependencies(source, buildDirectory, staging, copied);
2113
2496
  };
2114
2497
  var copyAbsoluteClientDependencies = async (sourcePath, buildDirectory, staging, copied) => {
@@ -2120,11 +2503,11 @@ var materializeAbsoluteCapacitorWebBundle = async (options) => {
2120
2503
  throw new TypeError(`mobile.entry ${options.config.entry} is not a captured mobile page route.`);
2121
2504
  }
2122
2505
  const destination = options.config.bundleDirectory;
2123
- await mkdir5(dirname3(destination), { recursive: true });
2124
- const staging = await mkdtemp3(join6(dirname3(destination), `.${basename(destination)}.stage-`));
2506
+ await mkdir6(dirname4(destination), { recursive: true });
2507
+ const staging = await mkdtemp4(join7(dirname4(destination), `.${basename(destination)}.stage-`));
2125
2508
  try {
2126
- const pageDirectory = join6(staging, "pages");
2127
- await mkdir5(pageDirectory, { recursive: true });
2509
+ const pageDirectory = join7(staging, "pages");
2510
+ await mkdir6(pageDirectory, { recursive: true });
2128
2511
  const copiedDependencies = new Set;
2129
2512
  const pages = await Promise.all(options.artifact.pages.map((page) => copyClientPage(page, options.buildDirectory, staging, copiedDependencies)));
2130
2513
  const manifest = {
@@ -2141,48 +2524,48 @@ var materializeAbsoluteCapacitorWebBundle = async (options) => {
2141
2524
  runtime: options.artifact.runtime
2142
2525
  };
2143
2526
  await Promise.all([
2144
- writeFile6(join6(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
2527
+ writeFile7(join7(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
2145
2528
  `),
2146
- writeFile6(join6(staging, INDEX_FILE), indexHtml(options.config.appName)),
2529
+ writeFile7(join7(staging, INDEX_FILE), indexHtml(options.config.appName)),
2147
2530
  buildShellBootstrap(staging)
2148
2531
  ]);
2149
2532
  await installBundle(staging, destination);
2150
2533
  return manifest;
2151
2534
  } catch (error) {
2152
- await rm5(staging, { force: true, recursive: true });
2535
+ await rm6(staging, { force: true, recursive: true });
2153
2536
  throw error;
2154
2537
  }
2155
2538
  };
2156
2539
 
2157
2540
  // src/mobile/materializedBundle.ts
2158
- import { createHash as createHash5 } from "crypto";
2541
+ import { createHash as createHash7 } from "crypto";
2159
2542
  import {
2160
- access as access5,
2161
- mkdir as mkdir6,
2162
- mkdtemp as mkdtemp4,
2163
- readFile as readFile8,
2164
- rename as rename7,
2165
- rm as rm6,
2166
- writeFile as writeFile7
2543
+ access as access6,
2544
+ mkdir as mkdir7,
2545
+ mkdtemp as mkdtemp5,
2546
+ readFile as readFile9,
2547
+ rename as rename8,
2548
+ rm as rm7,
2549
+ writeFile as writeFile8
2167
2550
  } from "fs/promises";
2168
- import { dirname as dirname4, join as join7, resolve as resolvePath2 } from "path";
2551
+ import { dirname as dirname5, join as join8, resolve as resolvePath2 } from "path";
2169
2552
  import { pathToFileURL } from "url";
2170
2553
  var ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT = 1;
2171
2554
  var CURRENT_BUNDLE_FILE = "current.json";
2172
2555
  var BUNDLES_DIRECTORY = "bundles";
2173
2556
  var ARTIFACT_FILE2 = "artifact.json";
2174
2557
  var BUNDLE_ID_PATTERN = /^amb_[a-f0-9]{64}$/;
2175
- var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2558
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2176
2559
  var errorHasCode3 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code;
2177
2560
  var bundleIdFor = (currentReleaseId, releases) => {
2178
2561
  const identity = JSON.stringify({
2179
2562
  currentReleaseId,
2180
2563
  releases: releases.map(({ releaseId }) => releaseId)
2181
2564
  });
2182
- return `amb_${createHash5("sha256").update(identity).digest("hex")}`;
2565
+ return `amb_${createHash7("sha256").update(identity).digest("hex")}`;
2183
2566
  };
2184
2567
  var parseBundleIndex = (value) => {
2185
- if (!isRecord5(value) || value.format !== ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT || typeof value.bundleId !== "string" || !BUNDLE_ID_PATTERN.test(value.bundleId) || typeof value.currentReleaseId !== "string" || !Array.isArray(value.releases)) {
2568
+ if (!isRecord6(value) || value.format !== ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT || typeof value.bundleId !== "string" || !BUNDLE_ID_PATTERN.test(value.bundleId) || typeof value.currentReleaseId !== "string" || !Array.isArray(value.releases)) {
2186
2569
  throw new TypeError("Invalid materialized mobile compatibility bundle.");
2187
2570
  }
2188
2571
  const releases = value.releases.map(parseAbsoluteMobileCompatibilityArtifact);
@@ -2205,30 +2588,30 @@ var parseBundleIndex = (value) => {
2205
2588
  };
2206
2589
  };
2207
2590
  var writeRelease = async (root, release) => {
2208
- const directory = join7(root, release.artifact.releaseId);
2209
- const producerPath = join7(directory, release.artifact.producer.module);
2210
- await mkdir6(dirname4(producerPath), { recursive: true });
2591
+ const directory = join8(root, release.artifact.releaseId);
2592
+ const producerPath = join8(directory, release.artifact.producer.module);
2593
+ await mkdir7(dirname5(producerPath), { recursive: true });
2211
2594
  await Promise.all([
2212
- writeFile7(join7(directory, ARTIFACT_FILE2), `${JSON.stringify(release.artifact, null, "\t")}
2595
+ writeFile8(join8(directory, ARTIFACT_FILE2), `${JSON.stringify(release.artifact, null, "\t")}
2213
2596
  `),
2214
- writeFile7(producerPath, new Uint8Array(await release.producer.arrayBuffer()))
2597
+ writeFile8(producerPath, new Uint8Array(await release.producer.arrayBuffer()))
2215
2598
  ]);
2216
2599
  };
2217
2600
  var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
2218
- const destination = join7(bundlesRoot, bundleId);
2601
+ const destination = join8(bundlesRoot, bundleId);
2219
2602
  try {
2220
- await access5(destination);
2603
+ await access6(destination);
2221
2604
  return destination;
2222
2605
  } catch (error) {
2223
2606
  if (!errorHasCode3(error, "ENOENT"))
2224
2607
  throw error;
2225
2608
  }
2226
- const staging = await mkdtemp4(join7(bundlesRoot, ".stage-"));
2609
+ const staging = await mkdtemp5(join8(bundlesRoot, ".stage-"));
2227
2610
  try {
2228
2611
  await Promise.all(releases.map((release) => writeRelease(staging, release)));
2229
- await rename7(staging, destination);
2612
+ await rename8(staging, destination);
2230
2613
  } catch (error) {
2231
- await rm6(staging, { force: true, recursive: true });
2614
+ await rm7(staging, { force: true, recursive: true });
2232
2615
  if (errorHasCode3(error, "EEXIST") || errorHasCode3(error, "ENOTEMPTY")) {
2233
2616
  return destination;
2234
2617
  }
@@ -2239,7 +2622,7 @@ var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
2239
2622
  var readCompatibilityModule = (modulePath) => import(pathToFileURL(modulePath).href);
2240
2623
  var resolveProducerHandler = (loaded, exportName) => {
2241
2624
  const value = loaded[exportName];
2242
- if (!isRecord5(value) || typeof value.handle !== "function") {
2625
+ if (!isRecord6(value) || typeof value.handle !== "function") {
2243
2626
  throw new TypeError(`Compatibility producer export ${exportName} must expose handle(request).`);
2244
2627
  }
2245
2628
  const { handle } = value;
@@ -2257,15 +2640,15 @@ var resolveProducerHandler = (loaded, exportName) => {
2257
2640
  };
2258
2641
  var loadAbsoluteMobileMaterializedBundle = async (root) => {
2259
2642
  const resolvedRoot = resolvePath2(root);
2260
- const serialized = await readFile8(join7(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
2643
+ const serialized = await readFile9(join8(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
2261
2644
  const parsed = JSON.parse(serialized);
2262
2645
  const index = parseBundleIndex(parsed);
2263
- const bundleRoot = join7(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
2646
+ const bundleRoot = join8(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
2264
2647
  return {
2265
2648
  artifacts: index.releases,
2266
2649
  currentReleaseId: index.currentReleaseId,
2267
2650
  loadProducer: async (artifact) => {
2268
- const modulePath = join7(bundleRoot, artifact.releaseId, artifact.producer.module);
2651
+ const modulePath = join8(bundleRoot, artifact.releaseId, artifact.producer.module);
2269
2652
  await verifyAbsoluteMobileCompatibilityProducer({
2270
2653
  artifact,
2271
2654
  producer: Bun.file(modulePath)
@@ -2292,8 +2675,8 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
2292
2675
  return release;
2293
2676
  });
2294
2677
  const root = resolvePath2(input.root);
2295
- const bundlesRoot = join7(root, BUNDLES_DIRECTORY);
2296
- await mkdir6(bundlesRoot, { recursive: true });
2678
+ const bundlesRoot = join8(root, BUNDLES_DIRECTORY);
2679
+ await mkdir7(bundlesRoot, { recursive: true });
2297
2680
  const bundleId = bundleIdFor(input.currentReleaseId, artifacts);
2298
2681
  await installImmutableBundle(bundlesRoot, bundleId, orderedReleases);
2299
2682
  const index = {
@@ -2302,22 +2685,22 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
2302
2685
  format: ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT,
2303
2686
  releases: artifacts
2304
2687
  };
2305
- const pointerPath = join7(root, CURRENT_BUNDLE_FILE);
2306
- const temporaryPointerPath = join7(root, `.current-${crypto.randomUUID()}.json`);
2307
- await writeFile7(temporaryPointerPath, `${JSON.stringify(index, null, "\t")}
2688
+ const pointerPath = join8(root, CURRENT_BUNDLE_FILE);
2689
+ const temporaryPointerPath = join8(root, `.current-${crypto.randomUUID()}.json`);
2690
+ await writeFile8(temporaryPointerPath, `${JSON.stringify(index, null, "\t")}
2308
2691
  `, { flag: "wx" });
2309
- await rename7(temporaryPointerPath, pointerPath);
2692
+ await rename8(temporaryPointerPath, pointerPath);
2310
2693
  return index;
2311
2694
  };
2312
2695
  var readAbsoluteMobileMaterializedReleases = async (root) => {
2313
2696
  const resolvedRoot = resolvePath2(root);
2314
2697
  try {
2315
- const serialized = await readFile8(join7(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
2698
+ const serialized = await readFile9(join8(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
2316
2699
  const parsed = JSON.parse(serialized);
2317
2700
  const index = parseBundleIndex(parsed);
2318
- const bundleRoot = join7(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
2701
+ const bundleRoot = join8(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
2319
2702
  return Promise.all(index.releases.map(async (artifact) => {
2320
- const producer = Bun.file(join7(bundleRoot, artifact.releaseId, artifact.producer.module));
2703
+ const producer = Bun.file(join8(bundleRoot, artifact.releaseId, artifact.producer.module));
2321
2704
  await verifyAbsoluteMobileCompatibilityProducer({
2322
2705
  artifact,
2323
2706
  producer
@@ -2366,11 +2749,11 @@ var loadServerApp = async (producerPath) => {
2366
2749
  return { app, exportName };
2367
2750
  };
2368
2751
  var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2369
- const buildDirectory = resolve8(options.buildDirectory);
2752
+ const buildDirectory = resolve9(options.buildDirectory);
2370
2753
  const mobile = normalizeAbsoluteMobileConfig(options.mobile, options.projectRoot);
2371
- const root = join8(buildDirectory, ".absolutejs", "mobile-compatibility");
2754
+ const root = join9(buildDirectory, ".absolutejs", "mobile-compatibility");
2372
2755
  const [manifestSource, previous] = await Promise.all([
2373
- readFile9(join8(buildDirectory, "manifest.json"), "utf8"),
2756
+ readFile10(join9(buildDirectory, "manifest.json"), "utf8"),
2374
2757
  readAbsoluteMobileMaterializedReleases(root)
2375
2758
  ]);
2376
2759
  const manifest = JSON.parse(manifestSource);
@@ -2381,7 +2764,7 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2381
2764
  process.env.ABSOLUTE_BUILD_DIR = buildDirectory;
2382
2765
  let loaded;
2383
2766
  try {
2384
- loaded = await loadServerApp(resolve8(options.producerPath));
2767
+ loaded = await loadServerApp(resolve9(options.producerPath));
2385
2768
  } finally {
2386
2769
  restoreBuildDirectory(previousBuildDirectory);
2387
2770
  }
@@ -2392,7 +2775,7 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2392
2775
  manifest,
2393
2776
  previousArtifacts: previous.map(({ artifact }) => artifact),
2394
2777
  producerExport: loaded.exportName,
2395
- producerPath: resolve8(options.producerPath),
2778
+ producerPath: resolve9(options.producerPath),
2396
2779
  runtime: String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION)
2397
2780
  });
2398
2781
  const releasesById = new Map([current, ...previous].map((release) => [
@@ -2486,20 +2869,20 @@ var createAbsoluteMobileCompatibilityDispatcher = (options) => {
2486
2869
  }).as("global");
2487
2870
  };
2488
2871
  // src/mobile/nativeDeepLinks.ts
2489
- import { readFile as readFile10, rename as rename8, writeFile as writeFile8 } from "fs/promises";
2490
- import { join as join9 } from "path";
2872
+ import { readFile as readFile11, rename as rename9, writeFile as writeFile9 } from "fs/promises";
2873
+ import { join as join10 } from "path";
2491
2874
  var START_MARKER = "<!-- absolutejs:deep-links:start -->";
2492
2875
  var END_MARKER = "<!-- absolutejs:deep-links:end -->";
2493
2876
  var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
2494
2877
  var NOT_FOUND = -1;
2495
2878
  var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
2496
2879
  var writeChangedFile = async (path, source) => {
2497
- const current = await readFile10(path, "utf8");
2880
+ const current = await readFile11(path, "utf8");
2498
2881
  if (current === source)
2499
2882
  return false;
2500
2883
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
2501
- await writeFile8(temporary, source, { flag: "wx" });
2502
- await rename8(temporary, path);
2884
+ await writeFile9(temporary, source, { flag: "wx" });
2885
+ await rename9(temporary, path);
2503
2886
  return true;
2504
2887
  };
2505
2888
  var replaceManagedRegion = (source, region, insertAt) => {
@@ -2544,8 +2927,8 @@ ${hosts}
2544
2927
  `;
2545
2928
  };
2546
2929
  var configureAndroid = async (config) => {
2547
- const path = join9(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
2548
- const source = await readFile10(path, "utf8");
2930
+ const path = join10(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
2931
+ const source = await readFile11(path, "utf8");
2549
2932
  const mainActivity = source.indexOf('android:name=".MainActivity"');
2550
2933
  if (mainActivity === NOT_FOUND) {
2551
2934
  throw new TypeError("Android MainActivity was not found.");
@@ -2570,8 +2953,8 @@ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
2570
2953
  ${END_MARKER}
2571
2954
  `;
2572
2955
  var configureIosInfo = async (config) => {
2573
- const path = join9(config.nativeProjectDirectory, "ios/App/App/Info.plist");
2574
- const source = await readFile10(path, "utf8");
2956
+ const path = join10(config.nativeProjectDirectory, "ios/App/App/Info.plist");
2957
+ const source = await readFile11(path, "utf8");
2575
2958
  const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
2576
2959
  ${END_MARKER}
2577
2960
  `;
@@ -2594,10 +2977,10 @@ ${domains}
2594
2977
  `;
2595
2978
  };
2596
2979
  var configureIosEntitlements = async (config) => {
2597
- const path = join9(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
2980
+ const path = join10(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
2598
2981
  let current = "";
2599
2982
  try {
2600
- current = await readFile10(path, "utf8");
2983
+ current = await readFile11(path, "utf8");
2601
2984
  } catch (error) {
2602
2985
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
2603
2986
  throw error;
@@ -2607,13 +2990,13 @@ var configureIosEntitlements = async (config) => {
2607
2990
  if (current === source)
2608
2991
  return false;
2609
2992
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
2610
- await writeFile8(temporary, source, { flag: "wx" });
2611
- await rename8(temporary, path);
2993
+ await writeFile9(temporary, source, { flag: "wx" });
2994
+ await rename9(temporary, path);
2612
2995
  return true;
2613
2996
  };
2614
2997
  var configureIosProject = async (config) => {
2615
- const path = join9(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
2616
- const source = await readFile10(path, "utf8");
2998
+ const path = join10(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
2999
+ const source = await readFile11(path, "utf8");
2617
3000
  const declarations = [
2618
3001
  ...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
2619
3002
  ].map((match) => match[1]);
@@ -2650,15 +3033,113 @@ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms)
2650
3033
  changed: results.filter(({ didChange }) => didChange).map(({ platform }) => platform)
2651
3034
  };
2652
3035
  };
3036
+ // src/mobile/releasePublisher.ts
3037
+ import { access as access7 } from "fs/promises";
3038
+ import { isAbsolute as isAbsolute4, relative as relative6, resolve as resolve10, sep as sep4 } from "path";
3039
+ import { pathToFileURL as pathToFileURL3 } from "url";
3040
+ var prepareAbsoluteIosRelease = async (publisher, options) => {
3041
+ if (typeof publisher.prepareIosRelease !== "function") {
3042
+ throw new TypeError("App Store Connect publishing requires a registry module created with @absolutejs/deploy/app-store-connect.");
3043
+ }
3044
+ const { buildNumber } = await publisher.prepareIosRelease(options);
3045
+ if (!Number.isSafeInteger(buildNumber) || buildNumber < 1) {
3046
+ throw new TypeError("App Store Connect publisher returned an invalid iOS build number.");
3047
+ }
3048
+ return buildNumber;
3049
+ };
3050
+ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
3051
+ if (typeof publisher.prepareAndroidRelease !== "function") {
3052
+ throw new TypeError("Google Play publishing requires a registry module created with @absolutejs/deploy/google-play.");
3053
+ }
3054
+ const prepared = await publisher.prepareAndroidRelease(options);
3055
+ const { versionCode } = prepared;
3056
+ if (typeof versionCode !== "number" || !Number.isSafeInteger(versionCode) || versionCode < 1 || versionCode > 2100000000) {
3057
+ throw new TypeError("Google Play publisher returned an invalid Android versionCode.");
3058
+ }
3059
+ return versionCode;
3060
+ };
3061
+ var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3062
+ var isPublisher = (value) => isRecord7(value) && typeof value.publish === "function";
3063
+ var publisherModulePath = (projectRoot, requested) => {
3064
+ const root = resolve10(projectRoot);
3065
+ const path = resolve10(root, requested);
3066
+ const projectRelative = relative6(root, path);
3067
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep4}`) || isAbsolute4(projectRelative)) {
3068
+ throw new TypeError("mobile publish --registry must remain inside the project.");
3069
+ }
3070
+ return path;
3071
+ };
3072
+ var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
3073
+ const modulePath = publisherModulePath(projectRoot, requestedModulePath);
3074
+ await access7(modulePath).catch(() => {
3075
+ throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
3076
+ });
3077
+ const loaded = await import(pathToFileURL3(modulePath).href);
3078
+ const publisher = isRecord7(loaded) ? loaded.default ?? loaded.registry : undefined;
3079
+ if (!isPublisher(publisher)) {
3080
+ throw new TypeError("Native release registry module must default-export a registry with publish(options).");
3081
+ }
3082
+ return publisher;
3083
+ };
3084
+ var publishAbsoluteAndroidRelease = async (options) => {
3085
+ const publisher = await loadAbsoluteNativeReleasePublisher(options.projectRoot, options.modulePath);
3086
+ const publication = await publisher.publish({
3087
+ allowUnsigned: options.allowUnsigned,
3088
+ channel: options.channel,
3089
+ googlePlay: options.googlePlay,
3090
+ releaseRoot: options.release.releaseRoot,
3091
+ signal: options.signal
3092
+ });
3093
+ const expected = options.release.metadata;
3094
+ const actual = publication.record?.metadata;
3095
+ 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") {
3096
+ throw new TypeError("Native release registry returned a different Android release identity.");
3097
+ }
3098
+ if (options.channel !== undefined && (publication.channel?.channel !== options.channel || publication.channel.releaseId !== expected.releaseId)) {
3099
+ throw new TypeError("Native release registry did not promote the requested channel.");
3100
+ }
3101
+ const { googlePlay } = publication;
3102
+ if (options.googlePlay) {
3103
+ if (!expected.versionCode || !googlePlay || googlePlay.receipt.provider !== "google-play" || googlePlay.receipt.packageName !== expected.appId || googlePlay.receipt.releaseId !== expected.releaseId || googlePlay.receipt.sha256 !== expected.sha256 || googlePlay.receipt.stage !== "committed" || googlePlay.receipt.intent.track !== options.googlePlay.track || typeof googlePlay.receipt.versionCode !== "string" || !/^\d+$/.test(googlePlay.receipt.versionCode) || Number(googlePlay.receipt.versionCode) !== expected.versionCode || typeof googlePlay.reused !== "boolean") {
3104
+ throw new TypeError("Native release publisher did not commit the requested Google Play release.");
3105
+ }
3106
+ }
3107
+ return publication;
3108
+ };
3109
+ var publishAbsoluteIosRelease = async (options) => {
3110
+ const publisher = await loadAbsoluteNativeReleasePublisher(options.projectRoot, options.modulePath);
3111
+ const publication = await publisher.publish({
3112
+ allowUnsigned: options.allowUnsigned,
3113
+ appStoreConnect: options.appStoreConnect,
3114
+ channel: options.channel,
3115
+ releaseRoot: options.release.releaseRoot,
3116
+ signal: options.signal
3117
+ });
3118
+ const expected = options.release.metadata;
3119
+ const actual = publication.record?.metadata;
3120
+ 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") {
3121
+ throw new TypeError("Native release registry returned a different iOS release identity.");
3122
+ }
3123
+ if (options.channel !== undefined && (publication.channel?.channel !== options.channel || publication.channel.releaseId !== expected.releaseId)) {
3124
+ throw new TypeError("Native release registry did not promote the requested channel.");
3125
+ }
3126
+ if (options.appStoreConnect) {
3127
+ const distributed = publication.appStoreConnect;
3128
+ if (!expected.buildNumber || !distributed || distributed.receipt.provider !== "app-store-connect" || distributed.receipt.releaseId !== expected.releaseId || distributed.receipt.sha256 !== expected.sha256 || distributed.receipt.buildNumber !== expected.buildNumber || distributed.receipt.marketingVersion !== expected.marketingVersion || !["distributed", "review-submitted"].includes(distributed.receipt.stage) || JSON.stringify([...distributed.receipt.intent.groups].sort()) !== JSON.stringify([...options.appStoreConnect.groups ?? []].sort()) || distributed.receipt.intent.submitForReview !== (options.appStoreConnect.submitForReview ?? false) || typeof distributed.reused !== "boolean") {
3129
+ throw new TypeError("Native release publisher did not complete the requested App Store Connect release.");
3130
+ }
3131
+ }
3132
+ return publication;
3133
+ };
2653
3134
  // src/mobile/routeMetadataTransform.ts
2654
3135
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
2655
- import { dirname as dirname5, extname as extname2, relative as relative5, resolve as resolve9 } from "path";
3136
+ import { dirname as dirname6, extname as extname2, relative as relative7, resolve as resolve11 } from "path";
2656
3137
  import ts from "typescript";
2657
3138
  var ROUTE_METHODS = new Set(["get", "head"]);
2658
3139
  var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
2659
3140
  var PAGE_HANDLER = "handleReactPageRequest";
2660
3141
  var posixPath = (value) => value.replace(/\\/g, "/");
2661
- var findTsconfig = (entry, projectRoot) => ts.findConfigFile(dirname5(entry), existsSync3, "tsconfig.json") ?? ts.findConfigFile(projectRoot, existsSync3, "tsconfig.json");
3142
+ var findTsconfig = (entry, projectRoot) => ts.findConfigFile(dirname6(entry), existsSync3, "tsconfig.json") ?? ts.findConfigFile(projectRoot, existsSync3, "tsconfig.json");
2662
3143
  var createProgram = (entry, projectRoot) => {
2663
3144
  const configPath = findTsconfig(entry, projectRoot);
2664
3145
  if (!configPath) {
@@ -2670,7 +3151,7 @@ var createProgram = (entry, projectRoot) => {
2670
3151
  target: ts.ScriptTarget.ESNext
2671
3152
  });
2672
3153
  }
2673
- const parsed = ts.parseJsonConfigFileContent(ts.readConfigFile(configPath, (path) => readFileSync2(path, "utf8")).config, ts.sys, dirname5(configPath));
3154
+ const parsed = ts.parseJsonConfigFileContent(ts.readConfigFile(configPath, (path) => readFileSync2(path, "utf8")).config, ts.sys, dirname6(configPath));
2674
3155
  if (!parsed.fileNames.includes(entry))
2675
3156
  parsed.fileNames.push(entry);
2676
3157
  return ts.createProgram(parsed.fileNames, parsed.options);
@@ -2776,7 +3257,7 @@ var resolvePageIdentity = (expression, sourceFile, checker, projectRoot) => {
2776
3257
  const declaration = symbol?.declarations?.[0];
2777
3258
  const file = declaration?.getSourceFile().fileName ?? sourceFile.fileName;
2778
3259
  const exportedName = symbol?.name ?? expression.getText(sourceFile);
2779
- const source = posixPath(relative5(projectRoot, file));
3260
+ const source = posixPath(relative7(projectRoot, file));
2780
3261
  return `${source}#${exportedName}`;
2781
3262
  };
2782
3263
  var resolveAlias = (symbol, checker) => {
@@ -2883,7 +3364,7 @@ var analyzeProgram = (program, projectRoot) => {
2883
3364
  const checker = program.getTypeChecker();
2884
3365
  const analyzed = new Map;
2885
3366
  for (const sourceFile of program.getSourceFiles()) {
2886
- const resolvedFile = resolve9(sourceFile.fileName);
3367
+ const resolvedFile = resolve11(sourceFile.fileName);
2887
3368
  if (!isProjectSource(sourceFile, resolvedFile, projectRoot))
2888
3369
  continue;
2889
3370
  const analysis = analyzeSourceFile(sourceFile, checker, projectRoot);
@@ -2966,14 +3447,14 @@ var transformFile = (source, fileName, analysis) => {
2966
3447
  };
2967
3448
  var ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL = ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION;
2968
3449
  var createAbsoluteMobileRouteMetadataPlugin = (options) => {
2969
- const projectRoot = resolve9(options.projectRoot ?? process.cwd());
2970
- const entry = resolve9(options.entry);
3450
+ const projectRoot = resolve11(options.projectRoot ?? process.cwd());
3451
+ const entry = resolve11(options.entry);
2971
3452
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
2972
3453
  return {
2973
3454
  name: "absolute-mobile-route-metadata",
2974
3455
  setup(build) {
2975
3456
  build.onLoad({ filter: SOURCE_FILTER }, async ({ path }) => {
2976
- const analysis = analyzed.get(resolve9(path));
3457
+ const analysis = analyzed.get(resolve11(path));
2977
3458
  if (!analysis)
2978
3459
  return;
2979
3460
  const source = await Bun.file(path).text();
@@ -2986,11 +3467,11 @@ var createAbsoluteMobileRouteMetadataPlugin = (options) => {
2986
3467
  };
2987
3468
  };
2988
3469
  var inspectAbsoluteMobileRouteMetadata = (options) => {
2989
- const projectRoot = resolve9(options.projectRoot ?? process.cwd());
2990
- const entry = resolve9(options.entry);
3470
+ const projectRoot = resolve11(options.projectRoot ?? process.cwd());
3471
+ const entry = resolve11(options.entry);
2991
3472
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
2992
3473
  return [...analyzed.entries()].flatMap(([file, analysis]) => [...analysis.byRouteCall.values()].map(({ metadata }) => ({
2993
- file: posixPath(relative5(projectRoot, file)),
3474
+ file: posixPath(relative7(projectRoot, file)),
2994
3475
  metadata
2995
3476
  })));
2996
3477
  };
@@ -3004,6 +3485,10 @@ export {
3004
3485
  resolveAbsoluteMobileDeepLink,
3005
3486
  resolveAbsoluteMobileCompatibilityRelease,
3006
3487
  readAbsoluteMobileMaterializedReleases,
3488
+ publishAbsoluteIosRelease,
3489
+ publishAbsoluteAndroidRelease,
3490
+ prepareAbsoluteIosRelease,
3491
+ prepareAbsoluteAndroidRelease,
3007
3492
  parseAbsoluteMobilePageRequest,
3008
3493
  parseAbsoluteMobilePageEnvelope,
3009
3494
  parseAbsoluteMobileCompatibilityArtifact,
@@ -3014,10 +3499,12 @@ export {
3014
3499
  materializeAbsoluteMobileAssociationFiles,
3015
3500
  materializeAbsoluteCapacitorWebBundle,
3016
3501
  matchesAbsoluteMobileRoutePattern,
3502
+ loadAbsoluteNativeReleasePublisher,
3017
3503
  loadAbsoluteMobileMaterializedBundle,
3018
3504
  inspectAbsoluteMobileRouteMetadata,
3019
3505
  hashAbsoluteMobilePropsSchema,
3020
3506
  getCurrentAbsoluteMobileProducerContext,
3507
+ fingerprintAbsoluteIosNativeProject,
3021
3508
  finalizeAbsoluteMobilePage,
3022
3509
  finalizeAbsoluteMobileCompatibilityBuild,
3023
3510
  fetchAbsoluteMobilePage,
@@ -3035,6 +3522,7 @@ export {
3035
3522
  carryForwardAbsoluteMobileCompatibilityReleases,
3036
3523
  captureAbsoluteMobileRouteGraph,
3037
3524
  buildAbsoluteMobileCompatibilityRelease,
3525
+ buildAbsoluteIosRelease,
3038
3526
  buildAbsoluteAndroidRelease,
3039
3527
  applyAbsoluteNativeDeepLinks,
3040
3528
  activateAbsoluteMobilePage,
@@ -3051,8 +3539,9 @@ export {
3051
3539
  ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT,
3052
3540
  ABSOLUTE_MOBILE_COMPATIBILITY_FORMAT,
3053
3541
  ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
3542
+ ABSOLUTE_IOS_RELEASE_FORMAT,
3054
3543
  ABSOLUTE_ANDROID_RELEASE_FORMAT
3055
3544
  };
3056
3545
 
3057
- //# debugId=EC6A042BA6D550B864756E2164756E21
3546
+ //# debugId=8F6E00755BF98A2E64756E2164756E21
3058
3547
  //# sourceMappingURL=index.js.map