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

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.
@@ -20032,7 +20032,7 @@ __export(exports_mobileUpdate, {
20032
20032
  parseMobileUpdateManifest: () => parseMobileUpdateManifest
20033
20033
  });
20034
20034
  import {
20035
- createHash as createHash18,
20035
+ createHash as createHash19,
20036
20036
  createHmac,
20037
20037
  createPrivateKey,
20038
20038
  createPublicKey as createPublicKey2,
@@ -20042,7 +20042,7 @@ import {
20042
20042
  verify as verify2,
20043
20043
  X509Certificate as X509Certificate3
20044
20044
  } from "crypto";
20045
- import { readFile as readFile27, stat as stat7 } from "fs/promises";
20045
+ import { readFile as readFile28, stat as stat7 } from "fs/promises";
20046
20046
  import path from "path";
20047
20047
  var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updates", MAX_FILE_BYTES, MAX_TOTAL_BYTES, DAY_MS, DEFAULT_MIN_AGE_MS, DEFAULT_GRACE_PERIOD_MS, DEFAULT_RETAIN_RECENT = 5, HASH, RELEASE, APP_ID, NAME, EXPO_DESCRIPTOR = "_absolute/expo-update.json", EXPO_CODE_SIGNING_ALGORITHM = "rsa-v1_5-sha256", HEALTH_TOKEN_VERSION = 1, HEALTH_KINDS, FAILURE_HEALTH_KINDS, MobileUpdateRegistryError, object6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), text2 = (value, field2) => {
20048
20048
  if (typeof value !== "string" || value.length === 0)
@@ -20192,7 +20192,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
20192
20192
  if (!prefix || prefix.split("/").some((segment) => segment === "." || segment === ".."))
20193
20193
  throw new MobileUpdateRegistryError("Mobile update prefix is invalid");
20194
20194
  return prefix;
20195
- }, appHash = (appId) => createHash18("sha256").update(appId).digest("hex"), digest = (bytes) => createHash18("sha256").update(bytes).digest("hex"), json = (value) => new TextEncoder().encode(`${JSON.stringify(value, null, 2)}
20195
+ }, appHash = (appId) => createHash19("sha256").update(appId).digest("hex"), digest = (bytes) => createHash19("sha256").update(bytes).digest("hex"), json = (value) => new TextEncoder().encode(`${JSON.stringify(value, null, 2)}
20196
20196
  `), decode2 = (value) => JSON.parse(new TextDecoder().decode(value)), fileDigest = async (file) => {
20197
20197
  const hasher = new Bun.CryptoHasher("sha256");
20198
20198
  for await (const chunk of file.stream())
@@ -20203,7 +20203,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
20203
20203
  return false;
20204
20204
  if (input.rollout === 1)
20205
20205
  return true;
20206
- const value = createHash18("sha256").update(`${input.appId}\x00${input.channel}\x00${input.releaseId}\x00${input.installationId}`).digest().readUInt32BE(0);
20206
+ const value = createHash19("sha256").update(`${input.appId}\x00${input.channel}\x00${input.releaseId}\x00${input.installationId}`).digest().readUInt32BE(0);
20207
20207
  return value / 4294967296 < input.rollout;
20208
20208
  }, base64Url = (value) => Buffer.from(value).toString("base64url"), healthPromotionId = (channel) => channel.promotionId ?? digest(new TextEncoder().encode(`${channel.appId}\x00${channel.channel}\x00${channel.releaseId ?? "embedded"}\x00${channel.promotedAt}`)), finiteMetric = (value, field2) => {
20209
20209
  if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
@@ -20243,7 +20243,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
20243
20243
  const releaseRoot = (manifest) => `${root(manifest.appId)}/releases/${manifest.releaseId}`;
20244
20244
  const manifestKey = (manifest) => `${releaseRoot(manifest)}/update.json`;
20245
20245
  const fileKey = (manifest, file) => `${releaseRoot(manifest)}/files/${file.path}`;
20246
- const contentBlobKey = (appId, sha2565) => `${root(appId)}/blobs/${sha2565}`;
20246
+ const contentBlobKey = (appId, sha2566) => `${root(appId)}/blobs/${sha2566}`;
20247
20247
  const tombstoneKey = (appId, releaseId) => `${root(appId)}/gc/${releaseId}.json`;
20248
20248
  const healthRoot = (appId, promotionId, releaseId) => `${root(appId)}/health/${promotionId}/${releaseId}`;
20249
20249
  const pauseKey = (appId, promotionId, releaseId) => `${healthRoot(appId, promotionId, releaseId)}/paused.json`;
@@ -20864,10 +20864,10 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
20864
20864
  const objectBytesByDigest = new Map;
20865
20865
  for (const blobObject of objects) {
20866
20866
  if (blobObject.key.startsWith(blobPrefix)) {
20867
- const sha2565 = blobObject.key.slice(blobPrefix.length);
20868
- if (HASH.test(sha2565)) {
20869
- objectKeysByDigest.set(sha2565, blobObject.key);
20870
- objectBytesByDigest.set(sha2565, blobObject.size);
20867
+ const sha2566 = blobObject.key.slice(blobPrefix.length);
20868
+ if (HASH.test(sha2566)) {
20869
+ objectKeysByDigest.set(sha2566, blobObject.key);
20870
+ objectBytesByDigest.set(sha2566, blobObject.size);
20871
20871
  }
20872
20872
  }
20873
20873
  if (!blobObject.key.startsWith(releasePrefix))
@@ -20950,7 +20950,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
20950
20950
  }).toSorted((left, right) => right.createdAt.localeCompare(left.createdAt));
20951
20951
  const protectedReleaseIds = new Set(releases.filter((release) => release.protectedBy.length > 0).map((release) => release.releaseId));
20952
20952
  const protectedDigests = new Set(manifests.filter((manifest) => protectedReleaseIds.has(manifest.releaseId)).flatMap((manifest) => manifest.files.map((file) => file.sha256)));
20953
- const reclaimableContentBytes = [...objectBytesByDigest].filter(([sha2565]) => !protectedDigests.has(sha2565)).reduce((total, [, bytes]) => total + bytes, 0);
20953
+ const reclaimableContentBytes = [...objectBytesByDigest].filter(([sha2566]) => !protectedDigests.has(sha2566)).reduce((total, [, bytes]) => total + bytes, 0);
20954
20954
  const contentBlobBytes = [...objectBytesByDigest.values()].reduce((total, bytes) => total + bytes, 0);
20955
20955
  const releaseBytes = releases.reduce((total, release) => total + release.bytes, 0);
20956
20956
  const totalBytes = objects.reduce((total, object22) => total + object22.size, 0);
@@ -21047,13 +21047,13 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
21047
21047
  for (const file of manifest?.manifest.files ?? [])
21048
21048
  referencedDigests.add(file.sha256);
21049
21049
  }
21050
- for (const [sha2565, key] of afterReleaseSweep.objectKeysByDigest) {
21051
- if (referencedDigests.has(sha2565))
21050
+ for (const [sha2566, key] of afterReleaseSweep.objectKeysByDigest) {
21051
+ if (referencedDigests.has(sha2566))
21052
21052
  continue;
21053
21053
  const head = await options.store.head(key);
21054
21054
  await remove(key);
21055
21055
  result.reclaimedBytes += head?.size ?? 0;
21056
- result.sweptContentBlobs.push(sha2565);
21056
+ result.sweptContentBlobs.push(sha2566);
21057
21057
  }
21058
21058
  return result;
21059
21059
  };
@@ -21075,7 +21075,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
21075
21075
  verifyManifestSignature(manifest, options.publicKeys);
21076
21076
  await assertNotMarked(manifest.appId, manifest.releaseId);
21077
21077
  const localRoot = path.resolve(input.releaseDirectory);
21078
- const localManifest = parseMobileUpdateManifest(JSON.parse(await readFile27(path.join(localRoot, "update.json"), "utf8")));
21078
+ const localManifest = parseMobileUpdateManifest(JSON.parse(await readFile28(path.join(localRoot, "update.json"), "utf8")));
21079
21079
  if (JSON.stringify(localManifest) !== JSON.stringify(manifest))
21080
21080
  throw new MobileUpdateRegistryError("Local mobile update manifest changed");
21081
21081
  const existing = await readManifest(manifest.appId, manifest.releaseId);
@@ -21208,7 +21208,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
21208
21208
  }
21209
21209
  };
21210
21210
  }, expoUpdateId = (releaseId) => {
21211
- const hash = RELEASE.test(releaseId) ? releaseId.slice("amu_".length, "amu_".length + 32) : createHash18("sha256").update(releaseId).digest("hex").slice(0, 32);
21211
+ const hash = RELEASE.test(releaseId) ? releaseId.slice("amu_".length, "amu_".length + 32) : createHash19("sha256").update(releaseId).digest("hex").slice(0, 32);
21212
21212
  return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20)}`;
21213
21213
  }, expoContentType = (extension2, launch2) => {
21214
21214
  if (launch2)
@@ -21361,8 +21361,8 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
21361
21361
  });
21362
21362
  }
21363
21363
  const pathname = new URL(request.url).pathname.replace(/^\/+/, "");
21364
- const relative20 = pathname.startsWith(`${route}/`) ? pathname.slice(route.length + 1) : "";
21365
- if (request.method === "POST" && relative20 === "health") {
21364
+ const relative21 = pathname.startsWith(`${route}/`) ? pathname.slice(route.length + 1) : "";
21365
+ if (request.method === "POST" && relative21 === "health") {
21366
21366
  if (!options.registry.recordUpdateHealth)
21367
21367
  return new Response(null, { status: 404 });
21368
21368
  const appId = request.headers.get("x-absolute-mobile-app");
@@ -21405,7 +21405,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
21405
21405
  }
21406
21406
  if (request.method !== "GET")
21407
21407
  return new Response(null, { status: 405 });
21408
- if (relative20 === "update.json") {
21408
+ if (relative21 === "update.json") {
21409
21409
  const expoProtocolVersion = request.headers.get("expo-protocol-version");
21410
21410
  const expoProtocol = expoProtocolVersion !== null;
21411
21411
  if (expoProtocol && expoProtocolVersion !== "1")
@@ -21523,7 +21523,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
21523
21523
  }
21524
21524
  });
21525
21525
  }
21526
- const match = /^(amu_[a-f0-9]{64})\/files\/(.+)$/.exec(relative20);
21526
+ const match = /^(amu_[a-f0-9]{64})\/files\/(.+)$/.exec(relative21);
21527
21527
  if (!match?.[1] || !match[2])
21528
21528
  return new Response(null, { status: 404 });
21529
21529
  const file = await options.registry.readUpdateFile({
@@ -21616,15 +21616,15 @@ import {
21616
21616
  createPublicKey as createPublicKey3,
21617
21617
  X509Certificate as X509Certificate4
21618
21618
  } from "crypto";
21619
- import { access as access19, mkdir as mkdir21 } from "fs/promises";
21620
- import { dirname as dirname22, isAbsolute as isAbsolute11, relative as relative20, resolve as resolve22, sep as sep13 } from "path";
21619
+ import { access as access20, mkdir as mkdir22 } from "fs/promises";
21620
+ import { dirname as dirname22, isAbsolute as isAbsolute12, relative as relative21, resolve as resolve23, sep as sep14 } from "path";
21621
21621
  import { pathToFileURL as pathToFileURL5 } from "url";
21622
21622
  import { Elysia as Elysia4 } from "elysia";
21623
21623
  var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE = "mobile.update.ts", object7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), projectPath3 = (projectRoot, requested) => {
21624
- const root = resolve22(projectRoot);
21625
- const path2 = resolve22(root, requested);
21626
- const projectRelative = relative20(root, path2);
21627
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep13}`) || isAbsolute11(projectRelative))
21624
+ const root = resolve23(projectRoot);
21625
+ const path2 = resolve23(root, requested);
21626
+ const projectRelative = relative21(root, path2);
21627
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep14}`) || isAbsolute12(projectRelative))
21628
21628
  throw new TypeError("mobile.updates.server.registry must remain inside the project.");
21629
21629
  return path2;
21630
21630
  }, isRegistry4 = (value) => object7(value) && [
@@ -21647,7 +21647,7 @@ var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MOD
21647
21647
  return metadata;
21648
21648
  }, loadAbsoluteMobileUpdateServerModule = async (projectRoot, requestedModulePath = DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE) => {
21649
21649
  const modulePath = projectPath3(projectRoot, requestedModulePath);
21650
- await access19(modulePath).catch(() => {
21650
+ await access20(modulePath).catch(() => {
21651
21651
  throw new TypeError(`Mobile update server registry does not exist: ${modulePath}. Run \`absolute mobile update provision\`.`);
21652
21652
  });
21653
21653
  const loaded = await import(pathToFileURL5(modulePath).href);
@@ -21836,13 +21836,13 @@ ${generated}`);
21836
21836
  }, writeAbsoluteMobileUpdateRegistry = async (options) => {
21837
21837
  const path2 = projectPath3(options.projectRoot, options.modulePath ?? DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE);
21838
21838
  if (!options.force) {
21839
- await access19(path2).then(() => {
21839
+ await access20(path2).then(() => {
21840
21840
  throw new TypeError(`Mobile update registry already exists: ${path2}. Pass --force to replace it.`);
21841
21841
  }, () => {
21842
21842
  return;
21843
21843
  });
21844
21844
  }
21845
- await mkdir21(dirname22(path2), { recursive: true });
21845
+ await mkdir22(dirname22(path2), { recursive: true });
21846
21846
  await Bun.write(path2, renderAbsoluteMobileUpdateRegistry({
21847
21847
  ...options.health ? { health: options.health } : {},
21848
21848
  ...options.rollout ? { rollout: options.rollout } : {},
@@ -25838,6 +25838,7 @@ var defaultRun3 = async (command, options = {}) => {
25838
25838
  const process2 = Bun.spawn(command, {
25839
25839
  cwd: options.cwd,
25840
25840
  env: options.env,
25841
+ signal: options.signal,
25841
25842
  stderr: "pipe",
25842
25843
  stdin: "ignore",
25843
25844
  stdout: "pipe"
@@ -25925,6 +25926,7 @@ var releaseMarker = (release) => {
25925
25926
  var waitForSimulatorMarker = async (options) => {
25926
25927
  const startedAt = performance.now();
25927
25928
  const poll = async () => {
25929
+ options.signal?.throwIfAborted();
25928
25930
  if (performance.now() - startedAt >= READY_TIMEOUT_MS)
25929
25931
  throw new Error("The installed iOS Release app launched but its embedded content did not become ready.");
25930
25932
  const logs = await options.run([
@@ -25963,7 +25965,12 @@ var launchSimulator = async (options) => {
25963
25965
  return Math.round(performance.now() - startedAt);
25964
25966
  };
25965
25967
  var runAbsoluteIosSimulatorReleaseAcceptance = async (options) => {
25966
- const run = options.run ?? defaultRun3;
25968
+ const baseRun = options.run ?? defaultRun3;
25969
+ const run = (command, commandOptions = {}) => baseRun(command, {
25970
+ ...commandOptions,
25971
+ ...options.signal ? { signal: options.signal } : {}
25972
+ });
25973
+ options.signal?.throwIfAborted();
25967
25974
  const xcodebuild = options.xcodebuild ?? "/usr/bin/xcodebuild";
25968
25975
  const xcrun = options.xcrun ?? "/usr/bin/xcrun";
25969
25976
  const startedAt = performance.now();
@@ -26061,7 +26068,12 @@ var observeDeviceLaunch = async (options) => {
26061
26068
  "--device",
26062
26069
  options.device,
26063
26070
  options.appId
26064
- ], { stderr: "pipe", stdin: "ignore", stdout: "pipe" });
26071
+ ], {
26072
+ signal: options.signal,
26073
+ stderr: "pipe",
26074
+ stdin: "ignore",
26075
+ stdout: "pipe"
26076
+ });
26065
26077
  const stdoutReader = process2.stdout.getReader();
26066
26078
  const stderrReader = process2.stderr.getReader();
26067
26079
  const requireMarker = async (reader) => {
@@ -26148,7 +26160,12 @@ var inspectTestFlightInstallation = async (options) => {
26148
26160
  var runAbsoluteIosDeviceReleaseAcceptance = async (options) => {
26149
26161
  if (!options.networkUnavailableConfirmed)
26150
26162
  throw new TypeError("Physical iOS offline acceptance requires explicit confirmation that Airplane Mode is enabled and Wi-Fi is disabled in Settings.");
26151
- const run = options.run ?? defaultRun3;
26163
+ const baseRun = options.run ?? defaultRun3;
26164
+ const run = (command, commandOptions = {}) => baseRun(command, {
26165
+ ...commandOptions,
26166
+ ...options.signal ? { signal: options.signal } : {}
26167
+ });
26168
+ options.signal?.throwIfAborted();
26152
26169
  const xcrun = options.xcrun ?? "/usr/bin/xcrun";
26153
26170
  const startedAt = performance.now();
26154
26171
  await requireSuccess3(run, [
@@ -26167,7 +26184,11 @@ var runAbsoluteIosDeviceReleaseAcceptance = async (options) => {
26167
26184
  xcrun
26168
26185
  };
26169
26186
  const installMs = options.distribution === "registered-device" ? await installRegisteredDeviceRelease(installation) : await inspectTestFlightInstallation(installation);
26170
- const observe = options.observeLaunch ?? ((launch2) => observeDeviceLaunch({ ...launch2, xcrun }));
26187
+ const observe = options.observeLaunch ?? ((launch2) => observeDeviceLaunch({
26188
+ ...launch2,
26189
+ ...options.signal ? { signal: options.signal } : {},
26190
+ xcrun
26191
+ }));
26171
26192
  const marker = releaseMarker(options.release);
26172
26193
  const launchMs = await observe({
26173
26194
  appId: options.release.metadata.appId,
@@ -27324,7 +27345,7 @@ import {
27324
27345
 
27325
27346
  // src/mobile/remoteMacWire.ts
27326
27347
  var ABSOLUTE_REMOTE_MAC_EVENT_PREFIX = "ABSOLUTE_REMOTE_MAC\t";
27327
- var ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION = 3;
27348
+ var ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION = 4;
27328
27349
 
27329
27350
  // src/mobile/remoteMacProtocol.ts
27330
27351
  var PROFILE_FORMAT = 1;
@@ -27868,6 +27889,45 @@ var absoluteRemoteReleaseInputSyncCommands = (project) => {
27868
27889
  tar: ["tar", "-cf", "-", "-C", project.config.bundleDirectory, "."]
27869
27890
  };
27870
27891
  };
27892
+ var syncAbsoluteRemoteMacAcceptanceRelease = async (project, release, options = {}) => {
27893
+ options.signal?.throwIfAborted();
27894
+ const destination = posix.join(project.remoteProjectRoot, ".absolutejs", "mobile", "acceptance", "ios", release.metadata.releaseId);
27895
+ const staging = `${destination}.incoming-${randomUUID4()}`;
27896
+ const script = [
27897
+ "set -eu",
27898
+ "umask 077",
27899
+ `rm -rf ${shellQuote(staging)}`,
27900
+ `mkdir -p ${shellQuote(staging)}`,
27901
+ `tar -xf - -C ${shellQuote(staging)}`,
27902
+ `test -f ${shellQuote(posix.join(staging, "release.json"))}`,
27903
+ `rm -rf ${shellQuote(destination)}`,
27904
+ `mkdir -p ${shellQuote(posix.dirname(destination))}`,
27905
+ `mv ${shellQuote(staging)} ${shellQuote(destination)}`
27906
+ ].join("; ");
27907
+ const archive = Bun.spawn(["tar", "-cf", "-", "-C", release.releaseRoot, "."], {
27908
+ signal: options.signal,
27909
+ stderr: "pipe",
27910
+ stdout: "pipe"
27911
+ });
27912
+ const upload = Bun.spawn([
27913
+ ...absoluteRemoteMacSshBase(project.profile),
27914
+ "/bin/sh -lc",
27915
+ shellQuote(script)
27916
+ ], {
27917
+ signal: options.signal,
27918
+ stderr: "pipe",
27919
+ stdin: archive.stdout,
27920
+ stdout: "pipe"
27921
+ });
27922
+ const [archiveExit, uploadExit, archiveError, uploadError] = await Promise.all([
27923
+ archive.exited,
27924
+ upload.exited,
27925
+ new Response(archive.stderr).text(),
27926
+ new Response(upload.stderr).text()
27927
+ ]);
27928
+ if (archiveExit !== 0 || uploadExit !== 0)
27929
+ throw new Error(`Remote iOS acceptance artifact synchronization failed: ${(archiveError || uploadError).trim()}`);
27930
+ };
27871
27931
  var syncAbsoluteRemoteMacReleaseInputs = async (project, options = {}) => {
27872
27932
  options.signal?.throwIfAborted();
27873
27933
  const commands = absoluteRemoteReleaseInputSyncCommands(project);
@@ -27938,11 +27998,28 @@ var retrieveAbsoluteRemoteIosRelease = async (project, metadata, outputDirectory
27938
27998
  ]);
27939
27999
  if (downloadExit !== 0)
27940
28000
  throw new Error(`Remote iOS release retrieval failed: ${downloadError.trim()}`);
28001
+ let registeredArtifactPath;
28002
+ if (metadata.registeredDevice) {
28003
+ registeredArtifactPath = join10(staging, "App.registered-device.ipa");
28004
+ const registeredDownload = Bun.spawn([
28005
+ ...absoluteRemoteMacSshBase(project.profile),
28006
+ "/bin/sh -lc",
28007
+ shellQuote(`cat ${shellQuote(posix.join(releaseRoot, metadata.registeredDevice.artifact))}`)
28008
+ ], { stderr: "pipe", stdout: "pipe" });
28009
+ const [registeredExit, registeredError] = await Promise.all([
28010
+ registeredDownload.exited,
28011
+ new Response(registeredDownload.stderr).text(),
28012
+ writeRemoteArtifact(registeredArtifactPath, registeredDownload.stdout)
28013
+ ]);
28014
+ if (registeredExit !== 0)
28015
+ throw new Error(`Remote iOS registered-device artifact retrieval failed: ${registeredError.trim()}`);
28016
+ }
27941
28017
  return await installAbsoluteIosRelease({
27942
28018
  artifactPath,
27943
28019
  metadata,
27944
28020
  outputDirectory,
27945
- projectRoot: project.projectRoot
28021
+ projectRoot: project.projectRoot,
28022
+ ...registeredArtifactPath ? { registeredArtifactPath } : {}
27946
28023
  });
27947
28024
  } finally {
27948
28025
  await rm8(staging, { force: true, recursive: true }).catch(() => {
@@ -28040,6 +28117,7 @@ var buildAbsoluteRemoteIosRelease = async (options) => {
28040
28117
  shellQuote(encodedConfig),
28041
28118
  ...options.allowUnsigned ? ["--unsigned"] : [],
28042
28119
  ...options.prepareBuildNumber ? ["--request-build-number"] : [],
28120
+ ...options.registeredDeviceArtifact ? ["--registered-device-artifact"] : [],
28043
28121
  ...options.developmentTeam ? ["--development-team", shellQuote(options.developmentTeam)] : []
28044
28122
  ].join(" ");
28045
28123
  const process2 = (options.transport?.spawn ?? defaultTransport.spawn)([
@@ -28144,6 +28222,178 @@ var buildAbsoluteRemoteIosRelease = async (options) => {
28144
28222
  }
28145
28223
  }
28146
28224
  };
28225
+ var requireRemoteIosReleaseAcceptance = (value, release, distribution) => {
28226
+ if (typeof value !== "object" || value === null)
28227
+ throw new TypeError("Remote Mac returned invalid iOS release evidence.");
28228
+ const result = value;
28229
+ let expectedExactness = "archive-equivalent";
28230
+ if (distribution === "simulator-release")
28231
+ expectedExactness = "source-equivalent";
28232
+ if (distribution === "testflight")
28233
+ expectedExactness = "store-delivered";
28234
+ const expectedDistribution = distribution === "testflight" ? "apple-processed" : distribution;
28235
+ const expectedTarget = distribution === "simulator-release" ? "simulator" : "device";
28236
+ if (result.status !== "pass" || result.embeddedLocal !== true || result.releaseId !== release.metadata.releaseId || result.engine !== release.metadata.engine || result.marketingVersion !== release.metadata.marketingVersion || result.artifactSha256 !== release.metadata.sha256 || result.artifactBytes !== release.metadata.bytes || result.signed !== release.metadata.signed || result.artifactExactness !== expectedExactness || result.distribution !== expectedDistribution || result.target !== expectedTarget || (distribution === "simulator-release" ? result.networkUnavailable !== "not-proven" : result.networkUnavailable !== "user-confirmed") || (release.metadata.buildNumber === undefined ? result.buildNumber !== undefined : result.buildNumber !== release.metadata.buildNumber) || !["durationMs", "installMs", "launchMs", "relaunchMs"].every((field2) => typeof result[field2] === "number" && Number.isFinite(result[field2]) && result[field2] >= 0))
28237
+ throw new TypeError("Remote Mac iOS release evidence does not match the immutable release or requested lane.");
28238
+ return value;
28239
+ };
28240
+ var runAbsoluteRemoteIosReleaseAcceptance = async (options) => {
28241
+ options.signal?.throwIfAborted();
28242
+ if (options.distribution !== "simulator-release" && !options.deviceIdentifier)
28243
+ throw new TypeError("Remote physical iOS release acceptance requires a device identifier.");
28244
+ if (options.distribution !== "simulator-release" && !options.networkUnavailableConfirmed)
28245
+ throw new TypeError("Remote physical iOS release acceptance requires explicit network-unavailable confirmation.");
28246
+ const operation = new AbortController;
28247
+ const abort = () => operation.abort(options.signal?.reason ?? new Error("Remote iOS release acceptance cancelled."));
28248
+ options.signal?.addEventListener("abort", abort, { once: true });
28249
+ const startedAt = performance.now();
28250
+ const leaseStartedAt = performance.now();
28251
+ let lease;
28252
+ try {
28253
+ lease = await (options.acquireLease ?? ((project, signal) => acquireAbsoluteRemoteMacReleaseLease(project, {
28254
+ signal,
28255
+ transport: options.transport
28256
+ })))(options.project, operation.signal);
28257
+ } catch (error) {
28258
+ options.signal?.removeEventListener("abort", abort);
28259
+ throw error;
28260
+ }
28261
+ const leaseDuration = performance.now() - leaseStartedAt;
28262
+ options.onPhaseTiming?.({
28263
+ durationMs: leaseDuration,
28264
+ phase: "remote-release-acceptance-lease"
28265
+ });
28266
+ let heartbeat;
28267
+ const heartbeatTimer = setInterval(() => {
28268
+ if (heartbeat)
28269
+ return;
28270
+ heartbeat = lease.heartbeat().catch((error) => operation.abort(error)).finally(() => {
28271
+ heartbeat = undefined;
28272
+ });
28273
+ }, REMOTE_RELEASE_LEASE_HEARTBEAT_MS);
28274
+ unrefTimer(heartbeatTimer);
28275
+ try {
28276
+ const syncStartedAt = performance.now();
28277
+ if (options.syncProject)
28278
+ await options.syncProject(options.project);
28279
+ else
28280
+ await syncAbsoluteRemoteMacProject(options.project, {
28281
+ signal: operation.signal
28282
+ });
28283
+ operation.signal.throwIfAborted();
28284
+ if (options.syncReleaseInputs)
28285
+ await options.syncReleaseInputs(options.project);
28286
+ else
28287
+ await syncAbsoluteRemoteMacReleaseInputs(options.project, {
28288
+ signal: operation.signal
28289
+ });
28290
+ operation.signal.throwIfAborted();
28291
+ if (options.syncRelease)
28292
+ await options.syncRelease(options.project, options.release);
28293
+ else
28294
+ await syncAbsoluteRemoteMacAcceptanceRelease(options.project, options.release, { signal: operation.signal });
28295
+ operation.signal.throwIfAborted();
28296
+ const syncDuration = performance.now() - syncStartedAt;
28297
+ options.onPhaseTiming?.({
28298
+ durationMs: syncDuration,
28299
+ phase: "remote-release-acceptance-sync"
28300
+ });
28301
+ const agent = await (options.installAgent ?? installAbsoluteRemoteMacAgent)(options.project);
28302
+ const encodedConfig = Buffer.from(JSON.stringify(portableMobileConfig(options.project))).toString("base64url");
28303
+ const remoteCommand = [
28304
+ `cd ${shellQuote(options.project.remoteProjectRoot)}`,
28305
+ "&&",
28306
+ "exec",
28307
+ shellQuote(options.project.profile.bunPath),
28308
+ shellQuote(agent.remotePath),
28309
+ "--test-ios-release",
28310
+ "--mobile-config",
28311
+ shellQuote(encodedConfig),
28312
+ "--release-id",
28313
+ shellQuote(options.release.metadata.releaseId),
28314
+ "--distribution",
28315
+ shellQuote(options.distribution),
28316
+ ...options.deviceIdentifier ? ["--device", shellQuote(options.deviceIdentifier)] : [],
28317
+ ...options.simulatorUdid ? ["--simulator-udid", shellQuote(options.simulatorUdid)] : [],
28318
+ ...options.networkUnavailableConfirmed ? ["--network-unavailable-confirmed"] : []
28319
+ ].join(" ");
28320
+ const process2 = (options.transport?.spawn ?? defaultTransport.spawn)([
28321
+ ...absoluteRemoteMacSshBase(options.project.profile),
28322
+ "/bin/sh -lc",
28323
+ shellQuote(remoteCommand)
28324
+ ], { signal: operation.signal });
28325
+ const killOnAbort = () => process2.kill();
28326
+ operation.signal.addEventListener("abort", killOnAbort, { once: true });
28327
+ let failure;
28328
+ let accepted;
28329
+ const stdoutDone = consumeLines2(process2.stdout, (line) => {
28330
+ if (!line.startsWith(ABSOLUTE_REMOTE_MAC_EVENT_PREFIX))
28331
+ return;
28332
+ try {
28333
+ const event = JSON.parse(line.slice(ABSOLUTE_REMOTE_MAC_EVENT_PREFIX.length));
28334
+ if (event.v !== ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION)
28335
+ throw new Error("Remote Mac protocol version mismatch.");
28336
+ if (event.type === "log")
28337
+ options.log?.(event.message);
28338
+ if (event.type === "timing") {
28339
+ const durationMs = Reflect.get(event, "durationMs");
28340
+ const phase = Reflect.get(event, "phase");
28341
+ if (typeof durationMs === "number" && typeof phase === "string")
28342
+ options.onPhaseTiming?.({ durationMs, phase });
28343
+ }
28344
+ if (event.type === "fatal")
28345
+ failure = new Error(event.error);
28346
+ if (event.type === "release-acceptance") {
28347
+ if (!event.targetId || typeof event.targetId !== "string")
28348
+ throw new TypeError("Remote Mac returned an invalid iOS acceptance target.");
28349
+ accepted = {
28350
+ result: requireRemoteIosReleaseAcceptance(event.result, options.release, options.distribution),
28351
+ targetId: event.targetId
28352
+ };
28353
+ }
28354
+ } catch (error) {
28355
+ failure = error instanceof Error ? error : new Error("Remote Mac emitted invalid iOS release evidence.");
28356
+ process2.kill();
28357
+ }
28358
+ });
28359
+ const stderrDone = consumeLines2(process2.stderr, (line) => options.log?.(`[remote] ${line}`));
28360
+ const exitCode = await process2.exited;
28361
+ operation.signal.removeEventListener("abort", killOnAbort);
28362
+ try {
28363
+ process2.stdin.end();
28364
+ } catch {}
28365
+ await Promise.all([stdoutDone, stderrDone]);
28366
+ operation.signal.throwIfAborted();
28367
+ if (failure)
28368
+ throw failure;
28369
+ if (exitCode !== 0)
28370
+ throw new Error(`Remote iOS release acceptance agent exited with status ${exitCode}.`);
28371
+ if (!accepted)
28372
+ throw new Error("Remote iOS release acceptance agent returned no evidence.");
28373
+ const total = performance.now() - startedAt;
28374
+ options.log?.(`Remote Mac ${options.project.profile.name} completed ${options.distribution} acceptance for ${options.release.metadata.releaseId} in ${total.toFixed(2)}ms (agent ${agent.uploaded ? "uploaded" : "cache hit"}).`);
28375
+ return {
28376
+ ...accepted,
28377
+ timings: {
28378
+ "remote-release-acceptance-lease": leaseDuration,
28379
+ "remote-release-acceptance-sync": syncDuration,
28380
+ total
28381
+ }
28382
+ };
28383
+ } finally {
28384
+ clearInterval(heartbeatTimer);
28385
+ await heartbeat?.catch(() => {
28386
+ return;
28387
+ });
28388
+ options.signal?.removeEventListener("abort", abort);
28389
+ try {
28390
+ await lease.release();
28391
+ options.log?.("Released Remote Mac release acceptance lease.");
28392
+ } catch (error) {
28393
+ options.log?.(`Remote Mac acceptance lease cleanup failed and will recover after expiry: ${error instanceof Error ? error.message : String(error)}`);
28394
+ }
28395
+ }
28396
+ };
28147
28397
  var startAbsoluteRemoteExpoIosDevSession = async (options) => {
28148
28398
  let session = await startAbsoluteRemoteDevSession({
28149
28399
  certificateAuthorityPath: options.certificateAuthorityPath,
@@ -34997,9 +35247,412 @@ init_pageProtocol();
34997
35247
  init_producerContext();
34998
35248
  init_releaseArtifact();
34999
35249
 
35250
+ // src/mobile/releaseCertification.ts
35251
+ import { createHash as createHash16 } from "crypto";
35252
+ import {
35253
+ access as access17,
35254
+ mkdir as mkdir20,
35255
+ mkdtemp as mkdtemp10,
35256
+ readFile as readFile26,
35257
+ rename as rename18,
35258
+ rm as rm15,
35259
+ writeFile as writeFile22
35260
+ } from "fs/promises";
35261
+ import { basename as basename8, isAbsolute as isAbsolute9, join as join30, relative as relative16, resolve as resolve18, sep as sep11 } from "path";
35262
+ var ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT = 1;
35263
+ var CERTIFICATION_PREFIX = "amobile_cert_";
35264
+ var SHA256 = /^[a-f0-9]{64}$/u;
35265
+ var STRENGTH_RANK = {
35266
+ device: 1,
35267
+ installed: 0,
35268
+ simulator: 0,
35269
+ store: 2
35270
+ };
35271
+ var isRequirement = (value) => value === "installed" || value === "simulator" || value === "device" || value === "store";
35272
+ var isStrength = (value) => value === "installed" || value === "simulator" || value === "device" || value === "store";
35273
+ var isArtifactExactness = (value) => value === "archive-equivalent" || value === "source-equivalent" || value === "store-delivered";
35274
+ var isDistribution = (value) => value === "apple-processed" || value === "registered-device" || value === "simulator-release";
35275
+ var isRecord14 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
35276
+ var inside4 = (root, candidate) => {
35277
+ const path = relative16(resolve18(root), resolve18(candidate));
35278
+ return path === "" || !isAbsolute9(path) && path !== ".." && !path.startsWith(`..${sep11}`);
35279
+ };
35280
+ var exists9 = async (path) => {
35281
+ try {
35282
+ await access17(path);
35283
+ return true;
35284
+ } catch {
35285
+ return false;
35286
+ }
35287
+ };
35288
+ var sha2564 = (bytes) => createHash16("sha256").update(bytes).digest("hex");
35289
+ var requireString2 = (value, name) => {
35290
+ if (typeof value !== "string" || !value)
35291
+ throw new TypeError(`Mobile certification ${name} is invalid.`);
35292
+ return value;
35293
+ };
35294
+ var requirePositiveInteger = (value, name) => {
35295
+ if (!Number.isSafeInteger(value) || Number(value) < 1)
35296
+ throw new TypeError(`Mobile certification ${name} is invalid.`);
35297
+ return Number(value);
35298
+ };
35299
+ var resolveEvidencePath = async (projectRoot, requested) => {
35300
+ const selected = resolve18(projectRoot, requested);
35301
+ if (!inside4(projectRoot, selected))
35302
+ throw new TypeError("Mobile certification evidence must remain inside the project.");
35303
+ if (basename8(selected) === "report.json")
35304
+ return selected;
35305
+ const report = join30(selected, "report.json");
35306
+ if (!await exists9(report))
35307
+ throw new TypeError(`Mobile certification evidence does not contain report.json: ${requested}`);
35308
+ return report;
35309
+ };
35310
+ var releaseIdentity = (release) => {
35311
+ const { metadata } = release;
35312
+ if (!metadata.signed)
35313
+ throw new TypeError("Mobile release certification requires a signed native release.");
35314
+ const common = {
35315
+ appBuild: metadata.appBuild,
35316
+ appId: metadata.appId,
35317
+ artifactBytes: metadata.bytes,
35318
+ artifactSha256: metadata.sha256,
35319
+ engine: metadata.engine,
35320
+ platform: metadata.platform,
35321
+ releaseId: metadata.releaseId,
35322
+ runtime: metadata.runtime,
35323
+ signed: true
35324
+ };
35325
+ if (metadata.platform === "android")
35326
+ return {
35327
+ ...common,
35328
+ ...metadata.versionCode === undefined ? {} : { versionCode: metadata.versionCode }
35329
+ };
35330
+ return {
35331
+ ...common,
35332
+ ...metadata.buildNumber === undefined ? {} : { buildNumber: metadata.buildNumber },
35333
+ marketingVersion: metadata.marketingVersion
35334
+ };
35335
+ };
35336
+ var requireCheck = (report, id, accepted = ["PASS"]) => {
35337
+ const checks = report.automatedChecks;
35338
+ if (!Array.isArray(checks))
35339
+ throw new TypeError("Mobile certification evidence has invalid automated checks.");
35340
+ const check = checks.find((candidate) => isRecord14(candidate) && candidate.id === id);
35341
+ if (!isRecord14(check) || !accepted.includes(String(check.result)))
35342
+ throw new TypeError(`Mobile certification evidence does not satisfy ${id}.`);
35343
+ };
35344
+ var validateCommonReport = (report, release) => {
35345
+ if (report.reportVersion !== 1 || report.platform !== release.platform || !isRecord14(report.metadata) || report.metadata.provider !== release.engine || !isRecord14(report.run) || report.run.status !== "pass" || report.run.appId !== release.appId)
35346
+ throw new TypeError("Mobile certification evidence does not match the release platform, provider, application, or successful-run contract.");
35347
+ requireCheck(report, "AUTO-DEV-01");
35348
+ return {
35349
+ generatedAt: requireString2(report.generatedAt, "evidence generatedAt"),
35350
+ run: report.run
35351
+ };
35352
+ };
35353
+ var validateReleaseFields = (value, release) => {
35354
+ if (value.releaseId !== release.releaseId || value.artifactSha256 !== release.artifactSha256 || value.artifactBytes !== release.artifactBytes || value.engine !== release.engine || value.signed !== true)
35355
+ throw new TypeError("Mobile certification evidence does not match the immutable release identity.");
35356
+ };
35357
+ var validateAndroidEvidence = (report, run, release, reportSha256, generatedAt) => {
35358
+ if (!isRecord14(run.release))
35359
+ throw new TypeError("Android certification evidence has no installed release result.");
35360
+ validateReleaseFields(run.release, release);
35361
+ if (run.release.embeddedOffline !== true)
35362
+ throw new TypeError("Android certification evidence did not prove embedded offline relaunch.");
35363
+ requireCheck(report, "AUTO-RELEASE-01");
35364
+ requireCheck(report, "AUTO-RELEASE-OFFLINE-01");
35365
+ return {
35366
+ generatedAt,
35367
+ networkUnavailable: "proven",
35368
+ remote: false,
35369
+ reportSha256,
35370
+ strength: "installed"
35371
+ };
35372
+ };
35373
+ var validateIosEvidence = (report, run, release, reportSha256, generatedAt) => {
35374
+ if (!isRecord14(run.iosRelease))
35375
+ throw new TypeError("iOS certification evidence has no installed release result.");
35376
+ const ios = run.iosRelease;
35377
+ validateReleaseFields(ios, release);
35378
+ if (ios.embeddedLocal !== true)
35379
+ throw new TypeError("iOS certification evidence did not prove embedded local rendering.");
35380
+ requireCheck(report, "AUTO-IOS-RELEASE-01");
35381
+ let strength;
35382
+ if (ios.distribution === "simulator-release" && ios.artifactExactness === "source-equivalent" && run.targetKind === "simulator" && ios.networkUnavailable === "not-proven")
35383
+ strength = "simulator";
35384
+ else if (ios.distribution === "registered-device" && ios.artifactExactness === "archive-equivalent" && run.targetKind === "device" && ios.networkUnavailable === "user-confirmed")
35385
+ strength = "device";
35386
+ else if (ios.distribution === "apple-processed" && ios.artifactExactness === "store-delivered" && run.targetKind === "device" && ios.networkUnavailable === "user-confirmed")
35387
+ strength = "store";
35388
+ else
35389
+ throw new TypeError("iOS certification evidence contains an invalid target, distribution, exactness, or offline-strength combination.");
35390
+ if (strength === "simulator")
35391
+ requireCheck(report, "AUTO-IOS-RELEASE-OFFLINE-01", ["SKIPPED"]);
35392
+ else
35393
+ requireCheck(report, "AUTO-IOS-RELEASE-OFFLINE-01");
35394
+ return {
35395
+ artifactExactness: ios.artifactExactness,
35396
+ distribution: ios.distribution,
35397
+ generatedAt,
35398
+ networkUnavailable: ios.networkUnavailable === "user-confirmed" ? "proven" : "not-proven",
35399
+ remote: ios.remote === true,
35400
+ reportSha256,
35401
+ strength
35402
+ };
35403
+ };
35404
+ var validateEvidence = async (projectRoot, requested, release) => {
35405
+ const path = await resolveEvidencePath(projectRoot, requested);
35406
+ const bytes = await readFile26(path);
35407
+ let value;
35408
+ try {
35409
+ value = JSON.parse(bytes.toString("utf8"));
35410
+ } catch {
35411
+ throw new TypeError("Mobile certification evidence report is not valid JSON.");
35412
+ }
35413
+ if (!isRecord14(value))
35414
+ throw new TypeError("Mobile certification evidence report must contain an object.");
35415
+ const { generatedAt, run } = validateCommonReport(value, release);
35416
+ const digest = sha2564(bytes);
35417
+ return release.platform === "android" ? validateAndroidEvidence(value, run, release, digest, generatedAt) : validateIosEvidence(value, run, release, digest, generatedAt);
35418
+ };
35419
+ var defaultRequirement = (platform4) => platform4 === "android" ? "installed" : "simulator";
35420
+ var validateRequirement = (platform4, requirement) => {
35421
+ if (platform4 === "android" && requirement !== "installed")
35422
+ throw new TypeError("Android certification currently uses its fixed installed/offline requirement; --require simulator, device, and store are iOS policies.");
35423
+ if (platform4 === "ios" && requirement === "installed")
35424
+ throw new TypeError("iOS certification requires simulator, device, or store evidence.");
35425
+ };
35426
+ var satisfiesRequirement = (strength, requirement) => {
35427
+ if (requirement === "installed")
35428
+ return strength === "installed";
35429
+ if (strength === "installed")
35430
+ return false;
35431
+ return STRENGTH_RANK[strength] >= STRENGTH_RANK[requirement];
35432
+ };
35433
+ var validateCertifiedEvidence = (platform4, evidence) => {
35434
+ if (platform4 === "android") {
35435
+ if (evidence.strength !== "installed" || evidence.networkUnavailable !== "proven" || evidence.artifactExactness !== undefined || evidence.distribution !== undefined || evidence.remote)
35436
+ throw new TypeError("Android release certification contains invalid evidence strength.");
35437
+ return;
35438
+ }
35439
+ const simulator = evidence.strength === "simulator" && evidence.artifactExactness === "source-equivalent" && evidence.distribution === "simulator-release" && evidence.networkUnavailable === "not-proven";
35440
+ const device = evidence.strength === "device" && evidence.artifactExactness === "archive-equivalent" && evidence.distribution === "registered-device" && evidence.networkUnavailable === "proven";
35441
+ const store = evidence.strength === "store" && evidence.artifactExactness === "store-delivered" && evidence.distribution === "apple-processed" && evidence.networkUnavailable === "proven";
35442
+ if (!simulator && !device && !store)
35443
+ throw new TypeError("iOS release certification contains invalid evidence strength.");
35444
+ };
35445
+ var canonicalJsonValue = (value) => {
35446
+ if (Array.isArray(value))
35447
+ return value.map(canonicalJsonValue);
35448
+ if (!isRecord14(value))
35449
+ return value;
35450
+ const sorted = {};
35451
+ for (const key of Object.keys(value).sort())
35452
+ sorted[key] = canonicalJsonValue(value[key]);
35453
+ return sorted;
35454
+ };
35455
+ var certificationBody = (certification) => JSON.stringify(canonicalJsonValue(certification));
35456
+ var createAbsoluteMobileReleaseCertification = async (options) => {
35457
+ if (options.evidencePaths.length === 0)
35458
+ throw new TypeError("Mobile release certification requires at least one --evidence report.");
35459
+ const release = releaseIdentity(options.release);
35460
+ const requirement = options.requirement ?? defaultRequirement(release.platform);
35461
+ validateRequirement(release.platform, requirement);
35462
+ const evidence = await Promise.all(options.evidencePaths.map((path) => validateEvidence(options.projectRoot, path, release)));
35463
+ evidence.sort((left, right) => left.reportSha256.localeCompare(right.reportSha256));
35464
+ const [firstEvidence] = evidence;
35465
+ if (!firstEvidence)
35466
+ throw new TypeError("Mobile release certification requires valid evidence.");
35467
+ const strength = evidence.reduce((strongest, candidate) => STRENGTH_RANK[candidate.strength] > STRENGTH_RANK[strongest] ? candidate.strength : strongest, firstEvidence.strength);
35468
+ if (!satisfiesRequirement(strength, requirement))
35469
+ throw new TypeError(`Mobile release evidence strength ${strength} does not satisfy required ${requirement} certification.`);
35470
+ const body = {
35471
+ evidence,
35472
+ format: ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT,
35473
+ generatedAt: options.generatedAt ?? evidence.reduce((latest, candidate) => candidate.generatedAt > latest ? candidate.generatedAt : latest, firstEvidence.generatedAt),
35474
+ release,
35475
+ requirement,
35476
+ status: "certified",
35477
+ strength
35478
+ };
35479
+ return {
35480
+ certificationId: `${CERTIFICATION_PREFIX}${sha2564(certificationBody(body))}`,
35481
+ ...body
35482
+ };
35483
+ };
35484
+ var parseCertification = (value) => {
35485
+ if (!isRecord14(value) || value.format !== ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT || value.status !== "certified" || !isRecord14(value.release) || !Array.isArray(value.evidence))
35486
+ throw new TypeError("Mobile release certification contract is invalid.");
35487
+ const { release } = value;
35488
+ const { platform: platform4 } = release;
35489
+ const { engine } = release;
35490
+ if (platform4 !== "android" && platform4 !== "ios" || engine !== "capacitor" && engine !== "expo" || release.signed !== true)
35491
+ throw new TypeError("Mobile release certification identity is invalid.");
35492
+ const { requirement } = value;
35493
+ const { strength } = value;
35494
+ if (!isRequirement(requirement) || !isStrength(strength))
35495
+ throw new TypeError("Mobile release certification policy is invalid.");
35496
+ validateRequirement(platform4, requirement);
35497
+ if (!satisfiesRequirement(strength, requirement))
35498
+ throw new TypeError("Mobile release certification does not satisfy its declared policy.");
35499
+ const artifactBytes = requirePositiveInteger(release.artifactBytes, "release artifactBytes");
35500
+ const artifactSha256 = requireString2(release.artifactSha256, "release artifactSha256");
35501
+ if (!SHA256.test(artifactSha256))
35502
+ throw new TypeError("Mobile release certification artifact digest is invalid.");
35503
+ const parsedRelease = {
35504
+ appBuild: requireString2(release.appBuild, "release appBuild"),
35505
+ appId: requireString2(release.appId, "release appId"),
35506
+ artifactBytes,
35507
+ artifactSha256,
35508
+ engine,
35509
+ platform: platform4,
35510
+ releaseId: requireString2(release.releaseId, "release releaseId"),
35511
+ runtime: requireString2(release.runtime, "release runtime"),
35512
+ signed: true,
35513
+ ...release.buildNumber === undefined ? {} : {
35514
+ buildNumber: requirePositiveInteger(release.buildNumber, "release buildNumber")
35515
+ },
35516
+ ...release.marketingVersion === undefined ? {} : {
35517
+ marketingVersion: requireString2(release.marketingVersion, "release marketingVersion")
35518
+ },
35519
+ ...release.versionCode === undefined ? {} : {
35520
+ versionCode: requirePositiveInteger(release.versionCode, "release versionCode")
35521
+ }
35522
+ };
35523
+ const evidence = value.evidence.map((candidate) => {
35524
+ if (!isRecord14(candidate) || !SHA256.test(String(candidate.reportSha256)) || !isStrength(candidate.strength) || candidate.networkUnavailable !== "not-proven" && candidate.networkUnavailable !== "proven" || typeof candidate.remote !== "boolean" || typeof candidate.generatedAt !== "string" || candidate.artifactExactness !== undefined && !isArtifactExactness(candidate.artifactExactness) || candidate.distribution !== undefined && !isDistribution(candidate.distribution))
35525
+ throw new TypeError("Mobile release certification evidence is invalid.");
35526
+ const parsedEvidence = {
35527
+ generatedAt: candidate.generatedAt,
35528
+ networkUnavailable: candidate.networkUnavailable,
35529
+ remote: candidate.remote,
35530
+ reportSha256: String(candidate.reportSha256),
35531
+ strength: candidate.strength,
35532
+ ...candidate.artifactExactness === undefined ? {} : {
35533
+ artifactExactness: candidate.artifactExactness
35534
+ },
35535
+ ...candidate.distribution === undefined ? {} : { distribution: candidate.distribution }
35536
+ };
35537
+ validateCertifiedEvidence(platform4, parsedEvidence);
35538
+ return parsedEvidence;
35539
+ });
35540
+ const strongestEvidence = evidence.reduce((strongest, candidate) => STRENGTH_RANK[candidate.strength] > STRENGTH_RANK[strongest] ? candidate.strength : strongest, evidence[0]?.strength ?? "installed");
35541
+ if (evidence.length === 0 || strongestEvidence !== strength)
35542
+ throw new TypeError("Mobile release certification evidence does not support its declared strength.");
35543
+ const parsed = {
35544
+ certificationId: requireString2(value.certificationId, "certificationId"),
35545
+ evidence,
35546
+ format: ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT,
35547
+ generatedAt: requireString2(value.generatedAt, "generatedAt"),
35548
+ release: parsedRelease,
35549
+ requirement,
35550
+ status: "certified",
35551
+ strength
35552
+ };
35553
+ const { certificationId, ...body } = parsed;
35554
+ if (certificationId !== `${CERTIFICATION_PREFIX}${sha2564(certificationBody(body))}`)
35555
+ throw new TypeError("Mobile release certification content digest is invalid.");
35556
+ return parsed;
35557
+ };
35558
+ var readAbsoluteMobileReleaseCertification = async (projectRoot, requested) => {
35559
+ const selected = resolve18(projectRoot, requested);
35560
+ if (!inside4(projectRoot, selected))
35561
+ throw new TypeError("Mobile release certification must remain inside the project.");
35562
+ const path = basename8(selected) === "certification.json" ? selected : join30(selected, "certification.json");
35563
+ const value = JSON.parse(await readFile26(path, "utf8"));
35564
+ return { certification: parseCertification(value), path };
35565
+ };
35566
+ var renderAbsoluteMobileReleaseCertification = (certification) => {
35567
+ const { release } = certification;
35568
+ const version = release.platform === "ios" ? `${release.marketingVersion ?? "unknown"} (${release.buildNumber ?? "unallocated"})` : String(release.versionCode ?? "unallocated");
35569
+ const evidence = certification.evidence.map((item) => `| ${item.strength} | ${item.networkUnavailable} | ${item.remote ? "paired Mac" : "local host"} | ${item.reportSha256} |`).join(`
35570
+ `);
35571
+ return `# AbsoluteJS mobile release certification
35572
+
35573
+ - Status: CERTIFIED
35574
+ - Certification: ${certification.certificationId}
35575
+ - Generated: ${certification.generatedAt}
35576
+ - Platform: ${release.platform}
35577
+ - Engine: ${release.engine}
35578
+ - Requirement: ${certification.requirement}
35579
+ - Evidence strength: ${certification.strength}
35580
+ - Release: ${release.releaseId}
35581
+ - Artifact SHA-256: ${release.artifactSha256}
35582
+ - Runtime fingerprint: ${release.runtime}
35583
+ - Embedded app build: ${release.appBuild}
35584
+ - Version/build: ${version}
35585
+
35586
+ This certification is local and contains no signing credentials, device identifiers, SSH destinations, report paths, application data, or page contents. Re-run verification against the immutable release before using it as a CI or promotion gate.
35587
+
35588
+ | Strength | Network unavailable | Execution | Report SHA-256 |
35589
+ | --- | --- | --- | --- |
35590
+ ${evidence}
35591
+ `;
35592
+ };
35593
+ var verifyAbsoluteMobileReleaseCertification = (certification, release, requirement) => {
35594
+ const parsed = parseCertification(certification);
35595
+ const expected = releaseIdentity(release);
35596
+ if (JSON.stringify(parsed.release) !== JSON.stringify(expected))
35597
+ throw new TypeError("Mobile release certification was invalidated by a release identity, artifact, runtime, signing, version, build, or embedded-bundle change.");
35598
+ if (requirement) {
35599
+ validateRequirement(expected.platform, requirement);
35600
+ if (!satisfiesRequirement(parsed.strength, requirement))
35601
+ throw new TypeError(`Mobile release certification strength ${parsed.strength} does not satisfy required ${requirement} policy.`);
35602
+ }
35603
+ return parsed;
35604
+ };
35605
+ var writeAbsoluteMobileReleaseCertification = async (projectRoot, certification, outputDirectory) => {
35606
+ const root = resolve18(projectRoot);
35607
+ const parent = resolve18(root, outputDirectory ?? join30(".absolutejs", "mobile", "certifications", certification.release.platform, certification.release.releaseId));
35608
+ if (!inside4(root, parent))
35609
+ throw new TypeError("Mobile certification output must remain inside the project.");
35610
+ await mkdir20(parent, { recursive: true });
35611
+ const destination = join30(parent, certification.certificationId);
35612
+ const paths = {
35613
+ directory: destination,
35614
+ jsonPath: join30(destination, "certification.json"),
35615
+ markdownPath: join30(destination, "certification.md")
35616
+ };
35617
+ const json = `${JSON.stringify(certification, null, 2)}
35618
+ `;
35619
+ const markdown = renderAbsoluteMobileReleaseCertification(certification);
35620
+ const verifyExisting = async () => {
35621
+ const [existingJson, existingMarkdown] = await Promise.all([
35622
+ readFile26(paths.jsonPath, "utf8"),
35623
+ readFile26(paths.markdownPath, "utf8")
35624
+ ]);
35625
+ if (existingJson !== json || existingMarkdown !== markdown)
35626
+ throw new TypeError(`Immutable mobile certification ${certification.certificationId} already exists with different content.`);
35627
+ };
35628
+ if (await exists9(destination)) {
35629
+ await verifyExisting();
35630
+ return paths;
35631
+ }
35632
+ const staging = await mkdtemp10(join30(parent, ".certification-"));
35633
+ try {
35634
+ await Promise.all([
35635
+ writeFile22(join30(staging, "certification.json"), json, {
35636
+ flag: "wx"
35637
+ }),
35638
+ writeFile22(join30(staging, "certification.md"), markdown, {
35639
+ flag: "wx"
35640
+ })
35641
+ ]);
35642
+ await rename18(staging, destination).catch(async (error) => {
35643
+ if (await exists9(destination))
35644
+ return;
35645
+ throw error;
35646
+ });
35647
+ } finally {
35648
+ await rm15(staging, { force: true, recursive: true });
35649
+ }
35650
+ await verifyExisting();
35651
+ return paths;
35652
+ };
35000
35653
  // src/mobile/releasePublisher.ts
35001
- import { access as access17 } from "fs/promises";
35002
- import { isAbsolute as isAbsolute9, relative as relative16, resolve as resolve18, sep as sep11 } from "path";
35654
+ import { access as access18 } from "fs/promises";
35655
+ import { isAbsolute as isAbsolute10, relative as relative17, resolve as resolve19, sep as sep12 } from "path";
35003
35656
  import { pathToFileURL as pathToFileURL3 } from "url";
35004
35657
  var prepareAbsoluteIosRelease = async (publisher, options) => {
35005
35658
  if (typeof publisher.prepareIosRelease !== "function") {
@@ -35022,24 +35675,24 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
35022
35675
  }
35023
35676
  return versionCode;
35024
35677
  };
35025
- var isRecord14 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
35026
- var isPublisher = (value) => isRecord14(value) && typeof value.publish === "function";
35678
+ var isRecord15 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
35679
+ var isPublisher = (value) => isRecord15(value) && typeof value.publish === "function";
35027
35680
  var publisherModulePath = (projectRoot, requested) => {
35028
- const root = resolve18(projectRoot);
35029
- const path = resolve18(root, requested);
35030
- const projectRelative = relative16(root, path);
35031
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep11}`) || isAbsolute9(projectRelative)) {
35681
+ const root = resolve19(projectRoot);
35682
+ const path = resolve19(root, requested);
35683
+ const projectRelative = relative17(root, path);
35684
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep12}`) || isAbsolute10(projectRelative)) {
35032
35685
  throw new TypeError("mobile publish --registry must remain inside the project.");
35033
35686
  }
35034
35687
  return path;
35035
35688
  };
35036
35689
  var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
35037
35690
  const modulePath = publisherModulePath(projectRoot, requestedModulePath);
35038
- await access17(modulePath).catch(() => {
35691
+ await access18(modulePath).catch(() => {
35039
35692
  throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
35040
35693
  });
35041
35694
  const loaded = await import(pathToFileURL3(modulePath).href);
35042
- const publisher = isRecord14(loaded) ? loaded.default ?? loaded.registry : undefined;
35695
+ const publisher = isRecord15(loaded) ? loaded.default ?? loaded.registry : undefined;
35043
35696
  if (!isPublisher(publisher)) {
35044
35697
  throw new TypeError("Native release registry module must default-export a registry with publish(options).");
35045
35698
  }
@@ -35099,7 +35752,7 @@ var publishAbsoluteIosRelease = async (options) => {
35099
35752
  init_pageProtocol();
35100
35753
  init_releaseArtifact();
35101
35754
  import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
35102
- import { dirname as dirname20, extname as extname6, relative as relative17, resolve as resolve19 } from "path";
35755
+ import { dirname as dirname20, extname as extname6, relative as relative18, resolve as resolve20 } from "path";
35103
35756
  import ts2 from "typescript";
35104
35757
  var ROUTE_METHODS = new Set(["get", "head"]);
35105
35758
  var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
@@ -35268,7 +35921,7 @@ var resolvePageIdentity = (expression, sourceFile, checker, projectRoot) => {
35268
35921
  const declaration = symbol?.declarations?.[0];
35269
35922
  const file = declaration?.getSourceFile().fileName ?? sourceFile.fileName;
35270
35923
  const exportedName = symbol?.name ?? expression.getText(sourceFile);
35271
- const source = posixPath(relative17(projectRoot, file));
35924
+ const source = posixPath(relative18(projectRoot, file));
35272
35925
  return `${source}#${exportedName}`;
35273
35926
  };
35274
35927
  var resolveAlias = (symbol, checker) => {
@@ -35507,7 +36160,7 @@ var analyzeProgram = (program, projectRoot) => {
35507
36160
  const checker = program.getTypeChecker();
35508
36161
  const analyzed = new Map;
35509
36162
  for (const sourceFile of program.getSourceFiles()) {
35510
- const resolvedFile = resolve19(sourceFile.fileName);
36163
+ const resolvedFile = resolve20(sourceFile.fileName);
35511
36164
  if (!isProjectSource(sourceFile, resolvedFile, projectRoot))
35512
36165
  continue;
35513
36166
  const analysis = analyzeSourceFile(sourceFile, checker, projectRoot);
@@ -35600,14 +36253,14 @@ var transformFile = (source, fileName, analysis) => {
35600
36253
  };
35601
36254
  var ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL = ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION;
35602
36255
  var createAbsoluteMobileRouteMetadataPlugin = (options) => {
35603
- const projectRoot = resolve19(options.projectRoot ?? process.cwd());
35604
- const entry = resolve19(options.entry);
36256
+ const projectRoot = resolve20(options.projectRoot ?? process.cwd());
36257
+ const entry = resolve20(options.entry);
35605
36258
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
35606
36259
  return {
35607
36260
  name: "absolute-mobile-route-metadata",
35608
36261
  setup(build) {
35609
36262
  build.onLoad({ filter: SOURCE_FILTER }, async ({ path }) => {
35610
- const analysis = analyzed.get(resolve19(path));
36263
+ const analysis = analyzed.get(resolve20(path));
35611
36264
  if (!analysis)
35612
36265
  return;
35613
36266
  const source = await Bun.file(path).text();
@@ -35620,11 +36273,11 @@ var createAbsoluteMobileRouteMetadataPlugin = (options) => {
35620
36273
  };
35621
36274
  };
35622
36275
  var inspectAbsoluteMobileRouteMetadata = (options) => {
35623
- const projectRoot = resolve19(options.projectRoot ?? process.cwd());
35624
- const entry = resolve19(options.entry);
36276
+ const projectRoot = resolve20(options.projectRoot ?? process.cwd());
36277
+ const entry = resolve20(options.entry);
35625
36278
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
35626
36279
  return [...analyzed.entries()].flatMap(([file, analysis]) => [...analysis.byRouteCall.values()].map(({ metadata }) => ({
35627
- file: posixPath(relative17(projectRoot, file)),
36280
+ file: posixPath(relative18(projectRoot, file)),
35628
36281
  metadata
35629
36282
  })));
35630
36283
  };
@@ -35958,7 +36611,7 @@ var createCapacitor = (win) => {
35958
36611
  callbackId
35959
36612
  }, callback);
35960
36613
  };
35961
- const p = new Promise((resolve20) => call.then(() => resolve20({ remove })));
36614
+ const p = new Promise((resolve21) => call.then(() => resolve21({ remove })));
35962
36615
  p.remove = async () => {
35963
36616
  console.warn(`Using addListener() without 'await' is deprecated.`);
35964
36617
  await remove();
@@ -36163,11 +36816,11 @@ class CapacitorCookiesPluginWeb extends WebPlugin {
36163
36816
  var CapacitorCookies = registerPlugin("CapacitorCookies", {
36164
36817
  web: () => new CapacitorCookiesPluginWeb
36165
36818
  });
36166
- var readBlobAsBase64 = async (blob) => new Promise((resolve20, reject) => {
36819
+ var readBlobAsBase64 = async (blob) => new Promise((resolve21, reject) => {
36167
36820
  const reader = new FileReader;
36168
36821
  reader.onload = () => {
36169
36822
  const base64String = reader.result;
36170
- resolve20(base64String.indexOf(",") >= 0 ? base64String.split(",")[1] : base64String);
36823
+ resolve21(base64String.indexOf(",") >= 0 ? base64String.split(",")[1] : base64String);
36171
36824
  };
36172
36825
  reader.onerror = (error) => reject(error);
36173
36826
  reader.readAsDataURL(blob);
@@ -36343,7 +36996,7 @@ var REPORT_KINDS = new Set([
36343
36996
  "signal"
36344
36997
  ]);
36345
36998
  var nativeObservability = registerPlugin("AbsoluteMobileObservability");
36346
- var isRecord15 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
36999
+ var isRecord16 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
36347
37000
  var redactText = (value) => value.slice(0, MAX_STRING_LENGTH).replace(/\b(Bearer)\s+[A-Za-z0-9._~+/-]+=*/giu, "$1 [REDACTED]").replace(/([?&](?:access_token|api_?key|authorization|code|id_token|password|refresh_token|secret|token)=)[^&#\s]*/giu, "$1[REDACTED]").replace(/\b((?:access_?token|api_?key|authorization|client_?secret|cookie|id_?token|password|passwd|refresh_?token|secret|token)\s*[:=]\s*)[^,\s;]+/giu, "$1[REDACTED]").replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[REDACTED]");
36348
37001
  var redactValue = (value, depth = 0) => {
36349
37002
  if (depth >= MAX_REDACTION_DEPTH)
@@ -36354,7 +37007,7 @@ var redactValue = (value, depth = 0) => {
36354
37007
  return value;
36355
37008
  if (Array.isArray(value))
36356
37009
  return value.slice(0, 64).map((entry) => redactValue(entry, depth + 1));
36357
- if (!isRecord15(value))
37010
+ if (!isRecord16(value))
36358
37011
  return String(value).slice(0, MAX_STRING_LENGTH);
36359
37012
  return Object.fromEntries(Object.entries(value).slice(0, 128).map(([key, entry]) => [
36360
37013
  key,
@@ -36362,7 +37015,7 @@ var redactValue = (value, depth = 0) => {
36362
37015
  ]));
36363
37016
  };
36364
37017
  var parseReport = (value) => {
36365
- if (!isRecord15(value) || !isRecord15(value.details))
37018
+ if (!isRecord16(value) || !isRecord16(value.details))
36366
37019
  return;
36367
37020
  if (typeof value.id !== "string" || !REPORT_ID.test(value.id))
36368
37021
  return;
@@ -36373,7 +37026,7 @@ var parseReport = (value) => {
36373
37026
  if (value.platform !== "android" && value.platform !== "ios")
36374
37027
  return;
36375
37028
  const details = redactValue(value.details);
36376
- if (!isRecord15(details))
37029
+ if (!isRecord16(details))
36377
37030
  return;
36378
37031
  if (new TextEncoder().encode(JSON.stringify(details)).byteLength > MAX_DETAIL_BYTES)
36379
37032
  return;
@@ -40827,8 +41480,8 @@ var createBeacon = (options) => {
40827
41480
  if (await fontAvailable(element))
40828
41481
  return;
40829
41482
  if (confirmationMs > 0) {
40830
- await new Promise((resolve20) => {
40831
- const timer2 = window.setTimeout(resolve20, confirmationMs);
41483
+ await new Promise((resolve21) => {
41484
+ const timer2 = window.setTimeout(resolve21, confirmationMs);
40832
41485
  cleanups.push(() => window.clearTimeout(timer2));
40833
41486
  });
40834
41487
  if (document.visibilityState === "hidden")
@@ -41816,51 +42469,51 @@ var createAbsoluteMobileUpdateClient = (options) => {
41816
42469
  };
41817
42470
  };
41818
42471
  // src/mobile/updatePublisher.ts
41819
- import { access as access18 } from "fs/promises";
41820
- import { isAbsolute as isAbsolute10, relative as relative19, resolve as resolve21, sep as sep12 } from "path";
42472
+ import { access as access19 } from "fs/promises";
42473
+ import { isAbsolute as isAbsolute11, relative as relative20, resolve as resolve22, sep as sep13 } from "path";
41821
42474
  import { pathToFileURL as pathToFileURL4 } from "url";
41822
42475
 
41823
42476
  // src/mobile/updateSigning.ts
41824
- import { createHash as createHash16, sign, verify } from "crypto";
42477
+ import { createHash as createHash17, sign, verify } from "crypto";
41825
42478
  import {
41826
42479
  cp as cp2,
41827
- mkdir as mkdir20,
41828
- mkdtemp as mkdtemp10,
42480
+ mkdir as mkdir21,
42481
+ mkdtemp as mkdtemp11,
41829
42482
  readdir as readdir10,
41830
- readFile as readFile26,
41831
- rename as rename18,
41832
- rm as rm15,
42483
+ readFile as readFile27,
42484
+ rename as rename19,
42485
+ rm as rm16,
41833
42486
  stat as stat6,
41834
- writeFile as writeFile22
42487
+ writeFile as writeFile23
41835
42488
  } from "fs/promises";
41836
- import { dirname as dirname21, join as join30, relative as relative18, resolve as resolve20 } from "path";
42489
+ import { dirname as dirname21, join as join31, relative as relative19, resolve as resolve21 } from "path";
41837
42490
  var UPDATE_MANIFEST_FILE = "update.json";
41838
42491
  var UPDATE_FILES_DIRECTORY = "files";
41839
- var sha2564 = (value) => createHash16("sha256").update(value).digest("hex");
42492
+ var sha2565 = (value) => createHash17("sha256").update(value).digest("hex");
41840
42493
  var listFiles = async (root, directory = root) => {
41841
42494
  const entries = await readdir10(directory, { withFileTypes: true });
41842
42495
  const paths = await Promise.all(entries.map(async (entry) => {
41843
- const path = join30(directory, entry.name);
42496
+ const path = join31(directory, entry.name);
41844
42497
  if (entry.isDirectory())
41845
42498
  return listFiles(root, path);
41846
42499
  if (!entry.isFile())
41847
42500
  throw new TypeError("Mobile updates cannot contain links or special files.");
41848
- return [relative18(root, path).replaceAll("\\", "/")];
42501
+ return [relative19(root, path).replaceAll("\\", "/")];
41849
42502
  }));
41850
42503
  return paths.flat().sort((left, right) => left.localeCompare(right));
41851
42504
  };
41852
42505
  var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
41853
- const source = join30(root, path);
42506
+ const source = join31(root, path);
41854
42507
  const [metadata, contents] = await Promise.all([
41855
42508
  stat6(source),
41856
- readFile26(source)
42509
+ readFile27(source)
41857
42510
  ]);
41858
- return { bytes: metadata.size, path, sha256: sha2564(contents) };
42511
+ return { bytes: metadata.size, path, sha256: sha2565(contents) };
41859
42512
  }));
41860
- var releaseIdFor = (value) => `amu_${sha2564(canonicalizeAbsoluteMobileUpdate(value))}`;
42513
+ var releaseIdFor = (value) => `amu_${sha2565(canonicalizeAbsoluteMobileUpdate(value))}`;
41861
42514
  var buildAbsoluteMobileUpdate = async (options) => {
41862
- const bundleDirectory = resolve20(options.bundleDirectory);
41863
- const outputRoot = resolve20(options.outputDirectory);
42515
+ const bundleDirectory = resolve21(options.bundleDirectory);
42516
+ const outputRoot = resolve21(options.outputDirectory);
41864
42517
  if (outputRoot === bundleDirectory || outputRoot.startsWith(`${bundleDirectory}/`))
41865
42518
  throw new TypeError("Mobile update output must be outside the embedded bundle.");
41866
42519
  const paths = await listFiles(bundleDirectory);
@@ -41888,38 +42541,38 @@ var buildAbsoluteMobileUpdate = async (options) => {
41888
42541
  value: signature.toString("base64")
41889
42542
  }
41890
42543
  });
41891
- await mkdir20(outputRoot, { recursive: true });
41892
- const outputDirectory = join30(outputRoot, manifest.releaseId);
41893
- const staging = await mkdtemp10(join30(outputRoot, ".stage-"));
42544
+ await mkdir21(outputRoot, { recursive: true });
42545
+ const outputDirectory = join31(outputRoot, manifest.releaseId);
42546
+ const staging = await mkdtemp11(join31(outputRoot, ".stage-"));
41894
42547
  try {
41895
- await cp2(bundleDirectory, join30(staging, UPDATE_FILES_DIRECTORY), {
42548
+ await cp2(bundleDirectory, join31(staging, UPDATE_FILES_DIRECTORY), {
41896
42549
  force: true,
41897
42550
  recursive: true
41898
42551
  });
41899
- await writeFile22(join30(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
42552
+ await writeFile23(join31(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
41900
42553
  `);
41901
- await rename18(staging, outputDirectory);
42554
+ await rename19(staging, outputDirectory);
41902
42555
  } catch (error) {
41903
- await rm15(staging, { force: true, recursive: true });
42556
+ await rm16(staging, { force: true, recursive: true });
41904
42557
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
41905
42558
  throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
41906
42559
  throw error;
41907
42560
  }
41908
42561
  return {
41909
42562
  manifest,
41910
- manifestPath: join30(outputDirectory, UPDATE_MANIFEST_FILE),
42563
+ manifestPath: join31(outputDirectory, UPDATE_MANIFEST_FILE),
41911
42564
  outputDirectory
41912
42565
  };
41913
42566
  };
41914
42567
  var copyAbsoluteMobileUpdateFile = async (updateDirectory, path, destination) => {
41915
- const source = resolve20(updateDirectory, UPDATE_FILES_DIRECTORY, path);
41916
- const root = resolve20(updateDirectory, UPDATE_FILES_DIRECTORY);
42568
+ const source = resolve21(updateDirectory, UPDATE_FILES_DIRECTORY, path);
42569
+ const root = resolve21(updateDirectory, UPDATE_FILES_DIRECTORY);
41917
42570
  if (!source.startsWith(`${root}/`))
41918
42571
  throw new TypeError("Update file escaped its release.");
41919
- await mkdir20(dirname21(destination), { recursive: true });
42572
+ await mkdir21(dirname21(destination), { recursive: true });
41920
42573
  await cp2(source, destination, { force: true });
41921
42574
  };
41922
- var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile26(join30(resolve20(directory), UPDATE_MANIFEST_FILE), "utf8")));
42575
+ var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile27(join31(resolve21(directory), UPDATE_MANIFEST_FILE), "utf8")));
41923
42576
  var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
41924
42577
  const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
41925
42578
  const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
@@ -42036,16 +42689,16 @@ var pruneAbsoluteMobileUpdates = async (options) => {
42036
42689
  var object5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
42037
42690
  var isPublisher2 = (value) => object5(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
42038
42691
  var projectPath2 = (projectRoot, requested, label) => {
42039
- const root = resolve21(projectRoot);
42040
- const path = resolve21(root, requested);
42041
- const projectRelative = relative19(root, path);
42042
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep12}`) || isAbsolute10(projectRelative))
42692
+ const root = resolve22(projectRoot);
42693
+ const path = resolve22(root, requested);
42694
+ const projectRelative = relative20(root, path);
42695
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep13}`) || isAbsolute11(projectRelative))
42043
42696
  throw new TypeError(`${label} must remain inside the project.`);
42044
42697
  return path;
42045
42698
  };
42046
42699
  var loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
42047
42700
  const modulePath = projectPath2(projectRoot, requestedModulePath, "mobile update registry");
42048
- await access18(modulePath).catch(() => {
42701
+ await access19(modulePath).catch(() => {
42049
42702
  throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
42050
42703
  });
42051
42704
  const loaded = await import(pathToFileURL4(modulePath).href);
@@ -42096,7 +42749,7 @@ var rollbackAbsoluteMobileUpdate = async (options) => {
42096
42749
  return result;
42097
42750
  };
42098
42751
  // src/mobile/updateRollout.ts
42099
- import { createHash as createHash17 } from "crypto";
42752
+ import { createHash as createHash18 } from "crypto";
42100
42753
  var UUID_PATTERN = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-8][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/u;
42101
42754
  var requireHeader = (headers, name) => {
42102
42755
  const value = headers.get(name);
@@ -42111,7 +42764,7 @@ var isAbsoluteMobileUpdateRolloutMember = (options) => {
42111
42764
  return false;
42112
42765
  if (options.rollout === 1)
42113
42766
  return true;
42114
- const digest = createHash17("sha256").update(`${options.appId}\x00${options.channel}\x00${options.releaseId}\x00${options.installationId}`).digest();
42767
+ const digest = createHash18("sha256").update(`${options.appId}\x00${options.channel}\x00${options.releaseId}\x00${options.installationId}`).digest();
42115
42768
  const bucket = digest.readUInt32BE(0) / 4294967296;
42116
42769
  return bucket < options.rollout;
42117
42770
  };
@@ -42158,6 +42811,7 @@ export {
42158
42811
  ABSOLUTE_MOBILE_PAGE_MEDIA_TYPE,
42159
42812
  ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION,
42160
42813
  ABSOLUTE_MOBILE_PREVIEW_PATH,
42814
+ ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT,
42161
42815
  ABSOLUTE_MOBILE_RETAINED_GENERATIONS,
42162
42816
  ABSOLUTE_MOBILE_ROUTE_DETAIL,
42163
42817
  ABSOLUTE_MOBILE_SHELL_ABI,
@@ -42243,6 +42897,7 @@ export {
42243
42897
  createAbsoluteMobilePageErrorResponse,
42244
42898
  createAbsoluteMobilePageRequest,
42245
42899
  createAbsoluteMobilePreviewPlugin,
42900
+ createAbsoluteMobileReleaseCertification,
42246
42901
  createAbsoluteMobileRouteMetadataPlugin,
42247
42902
  createAbsoluteMobileUpdateClient,
42248
42903
  createAbsoluteMobileUpdateRuntimeDescriptor,
@@ -42352,10 +43007,12 @@ export {
42352
43007
  readAbsoluteIosRelease,
42353
43008
  readAbsoluteMobileLinkIntent,
42354
43009
  readAbsoluteMobileMaterializedReleases,
43010
+ readAbsoluteMobileReleaseCertification,
42355
43011
  readAbsoluteMobileUpdate,
42356
43012
  reconcileAbsoluteMobileUpdateRollout,
42357
43013
  redactAbsoluteIosLog,
42358
43014
  removeAbsoluteRemoteMacProfile,
43015
+ renderAbsoluteMobileReleaseCertification,
42359
43016
  renderAbsoluteMobileUpdateRegistry,
42360
43017
  repairAbsoluteIosDevSession,
42361
43018
  reportAbsoluteMobileUpdateHealth,
@@ -42376,6 +43033,7 @@ export {
42376
43033
  runAbsoluteAndroidUpgradeConformance,
42377
43034
  runAbsoluteIosDeviceReleaseAcceptance,
42378
43035
  runAbsoluteIosSimulatorReleaseAcceptance,
43036
+ runAbsoluteRemoteIosReleaseAcceptance,
42379
43037
  runWithAbsoluteMobileProducer,
42380
43038
  serializeAbsoluteMobileAuthEnvironment,
42381
43039
  startAbsoluteExpoDevSession,
@@ -42385,6 +43043,7 @@ export {
42385
43043
  startAbsoluteRemoteExpoIosDevSession,
42386
43044
  startAbsoluteRemoteIosDevSession,
42387
43045
  syncAbsoluteExpoWebAssets,
43046
+ syncAbsoluteRemoteMacAcceptanceRelease,
42388
43047
  syncAbsoluteRemoteMacProject,
42389
43048
  syncAbsoluteRemoteMacReleaseInputs,
42390
43049
  testAbsoluteIosPhysicalDevice,
@@ -42393,13 +43052,15 @@ export {
42393
43052
  validateAbsoluteSshDestination,
42394
43053
  verifyAbsoluteMobileAssociationFiles,
42395
43054
  verifyAbsoluteMobileCompatibilityProducer,
43055
+ verifyAbsoluteMobileReleaseCertification,
42396
43056
  verifyAbsoluteMobileUpdateSignature,
42397
43057
  waitForAbsoluteIosHmrLog,
42398
43058
  writeAbsoluteCapacitorConfig,
42399
43059
  writeAbsoluteExpoProject,
42400
43060
  writeAbsoluteMobileGithubWorkflow,
43061
+ writeAbsoluteMobileReleaseCertification,
42401
43062
  writeAbsoluteMobileUpdateRegistry
42402
43063
  };
42403
43064
 
42404
- //# debugId=E34FAEF09492010764756E2164756E21
43065
+ //# debugId=DB53CC7C8DF0442B64756E2164756E21
42405
43066
  //# sourceMappingURL=index.js.map