@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.
@@ -29,8 +29,9 @@ import {
29
29
  inspectAbsoluteRemoteMacWorkspace,
30
30
  listAbsoluteRemoteMacProfiles,
31
31
  pairAbsoluteRemoteMac,
32
- removeAbsoluteRemoteMacProfile
33
- } from "./index-7fxrgx5q.js";
32
+ removeAbsoluteRemoteMacProfile,
33
+ runAbsoluteRemoteIosReleaseAcceptance
34
+ } from "./index-jf1f6npt.js";
34
35
  import {
35
36
  ABSOLUTE_IOS_SIMULATOR_NAME,
36
37
  parseIosSimulators,
@@ -4414,9 +4415,9 @@ var require_sha256 = __commonJS(function(exports, module) {
4414
4415
  var forge = require_forge();
4415
4416
  require_md();
4416
4417
  require_util();
4417
- var sha2564 = module.exports = forge.sha256 = forge.sha256 || {};
4418
- forge.md.sha256 = forge.md.algorithms.sha256 = sha2564;
4419
- sha2564.create = function() {
4418
+ var sha2565 = module.exports = forge.sha256 = forge.sha256 || {};
4419
+ forge.md.sha256 = forge.md.algorithms.sha256 = sha2565;
4420
+ sha2565.create = function() {
4420
4421
  if (!_initialized) {
4421
4422
  _init();
4422
4423
  }
@@ -16778,15 +16779,15 @@ var require_main = __commonJS(function(exports) {
16778
16779
 
16779
16780
  // src/cli/scripts/mobile.ts
16780
16781
  import {
16781
- access as access14,
16782
- mkdir as mkdir15,
16783
- mkdtemp as mkdtemp5,
16784
- readFile as readFile19,
16785
- rm as rm8,
16786
- writeFile as writeFile16
16782
+ access as access15,
16783
+ mkdir as mkdir16,
16784
+ mkdtemp as mkdtemp6,
16785
+ readFile as readFile20,
16786
+ rm as rm9,
16787
+ writeFile as writeFile17
16787
16788
  } from "fs/promises";
16788
16789
  import { createPublicKey as createPublicKey2 } from "crypto";
16789
- import { join as join16, relative as relative13, resolve as resolve14 } from "path";
16790
+ import { join as join17, relative as relative14, resolve as resolve15 } from "path";
16790
16791
  import { createInterface } from "readline/promises";
16791
16792
 
16792
16793
  // src/mobile/nativeDeepLinks.ts
@@ -21241,6 +21242,7 @@ var defaultRun2 = async (command, options = {}) => {
21241
21242
  const process2 = Bun.spawn(command, {
21242
21243
  cwd: options.cwd,
21243
21244
  env: options.env,
21245
+ signal: options.signal,
21244
21246
  stderr: "pipe",
21245
21247
  stdin: "ignore",
21246
21248
  stdout: "pipe"
@@ -21328,6 +21330,7 @@ var releaseMarker = (release) => {
21328
21330
  var waitForSimulatorMarker = async (options) => {
21329
21331
  const startedAt = performance.now();
21330
21332
  const poll = async () => {
21333
+ options.signal?.throwIfAborted();
21331
21334
  if (performance.now() - startedAt >= READY_TIMEOUT_MS)
21332
21335
  throw new Error("The installed iOS Release app launched but its embedded content did not become ready.");
21333
21336
  const logs = await options.run([
@@ -21366,7 +21369,12 @@ var launchSimulator = async (options) => {
21366
21369
  return Math.round(performance.now() - startedAt);
21367
21370
  };
21368
21371
  var runAbsoluteIosSimulatorReleaseAcceptance = async (options) => {
21369
- const run = options.run ?? defaultRun2;
21372
+ const baseRun = options.run ?? defaultRun2;
21373
+ const run = (command, commandOptions = {}) => baseRun(command, {
21374
+ ...commandOptions,
21375
+ ...options.signal ? { signal: options.signal } : {}
21376
+ });
21377
+ options.signal?.throwIfAborted();
21370
21378
  const xcodebuild = options.xcodebuild ?? "/usr/bin/xcodebuild";
21371
21379
  const xcrun = options.xcrun ?? "/usr/bin/xcrun";
21372
21380
  const startedAt = performance.now();
@@ -21464,7 +21472,12 @@ var observeDeviceLaunch = async (options) => {
21464
21472
  "--device",
21465
21473
  options.device,
21466
21474
  options.appId
21467
- ], { stderr: "pipe", stdin: "ignore", stdout: "pipe" });
21475
+ ], {
21476
+ signal: options.signal,
21477
+ stderr: "pipe",
21478
+ stdin: "ignore",
21479
+ stdout: "pipe"
21480
+ });
21468
21481
  const stdoutReader = process2.stdout.getReader();
21469
21482
  const stderrReader = process2.stderr.getReader();
21470
21483
  const requireMarker = async (reader) => {
@@ -21551,7 +21564,12 @@ var inspectTestFlightInstallation = async (options) => {
21551
21564
  var runAbsoluteIosDeviceReleaseAcceptance = async (options) => {
21552
21565
  if (!options.networkUnavailableConfirmed)
21553
21566
  throw new TypeError("Physical iOS offline acceptance requires explicit confirmation that Airplane Mode is enabled and Wi-Fi is disabled in Settings.");
21554
- const run = options.run ?? defaultRun2;
21567
+ const baseRun = options.run ?? defaultRun2;
21568
+ const run = (command, commandOptions = {}) => baseRun(command, {
21569
+ ...commandOptions,
21570
+ ...options.signal ? { signal: options.signal } : {}
21571
+ });
21572
+ options.signal?.throwIfAborted();
21555
21573
  const xcrun = options.xcrun ?? "/usr/bin/xcrun";
21556
21574
  const startedAt = performance.now();
21557
21575
  await requireSuccess2(run, [
@@ -21570,7 +21588,11 @@ var runAbsoluteIosDeviceReleaseAcceptance = async (options) => {
21570
21588
  xcrun
21571
21589
  };
21572
21590
  const installMs = options.distribution === "registered-device" ? await installRegisteredDeviceRelease(installation) : await inspectTestFlightInstallation(installation);
21573
- const observe = options.observeLaunch ?? ((launch2) => observeDeviceLaunch({ ...launch2, xcrun }));
21591
+ const observe = options.observeLaunch ?? ((launch2) => observeDeviceLaunch({
21592
+ ...launch2,
21593
+ ...options.signal ? { signal: options.signal } : {},
21594
+ xcrun
21595
+ }));
21574
21596
  const marker = releaseMarker(options.release);
21575
21597
  const launchMs = await observe({
21576
21598
  appId: options.release.metadata.appId,
@@ -21776,7 +21798,7 @@ var createAbsoluteNativeAutomatedChecks = (run) => {
21776
21798
  if (run.iosRelease) {
21777
21799
  const release = run.iosRelease;
21778
21800
  checks.push({
21779
- details: `Validated immutable release ${release.releaseId} (${release.artifactBytes} bytes, SHA-256 ${release.artifactSha256}); ${release.distribution} evidence is ${release.artifactExactness}, installed in ${release.installMs}ms.`,
21801
+ details: `Validated immutable release ${release.releaseId} (${release.artifactBytes} bytes, SHA-256 ${release.artifactSha256}); ${release.distribution} evidence is ${release.artifactExactness}, executed on the ${release.remote ? "paired Remote Mac" : "local Mac"}, and installed in ${release.installMs}ms.`,
21780
21802
  id: "AUTO-IOS-RELEASE-01",
21781
21803
  result: run.status === "pass" ? "PASS" : "FAIL"
21782
21804
  }, {
@@ -21784,6 +21806,12 @@ var createAbsoluteNativeAutomatedChecks = (run) => {
21784
21806
  id: "AUTO-IOS-RELEASE-OFFLINE-01",
21785
21807
  result: release.networkUnavailable === "user-confirmed" ? "PASS" : "SKIPPED"
21786
21808
  });
21809
+ if (release.remote)
21810
+ checks.push({
21811
+ details: "The installed-release lane executed through the paired Remote Mac protocol; immutable inputs and returned evidence were verified on the initiating host.",
21812
+ id: "AUTO-IOS-REMOTE-01",
21813
+ result: run.status === "pass" ? "PASS" : "FAIL"
21814
+ });
21787
21815
  }
21788
21816
  if (run.upgrade) {
21789
21817
  const { upgrade } = run;
@@ -22142,9 +22170,413 @@ var createAbsoluteAndroidTestReport = (options) => {
22142
22170
  });
22143
22171
  };
22144
22172
 
22173
+ // src/mobile/releaseCertification.ts
22174
+ import { createHash as createHash5 } from "crypto";
22175
+ import {
22176
+ access as access8,
22177
+ mkdir as mkdir11,
22178
+ mkdtemp as mkdtemp4,
22179
+ readFile as readFile15,
22180
+ rename as rename10,
22181
+ rm as rm7,
22182
+ writeFile as writeFile12
22183
+ } from "fs/promises";
22184
+ import { basename as basename3, isAbsolute as isAbsolute5, join as join13, relative as relative7, resolve as resolve8, sep as sep5 } from "path";
22185
+ var ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT = 1;
22186
+ var CERTIFICATION_PREFIX = "amobile_cert_";
22187
+ var SHA256 = /^[a-f0-9]{64}$/u;
22188
+ var STRENGTH_RANK = {
22189
+ device: 1,
22190
+ installed: 0,
22191
+ simulator: 0,
22192
+ store: 2
22193
+ };
22194
+ var isRequirement = (value) => value === "installed" || value === "simulator" || value === "device" || value === "store";
22195
+ var isStrength = (value) => value === "installed" || value === "simulator" || value === "device" || value === "store";
22196
+ var isArtifactExactness = (value) => value === "archive-equivalent" || value === "source-equivalent" || value === "store-delivered";
22197
+ var isDistribution = (value) => value === "apple-processed" || value === "registered-device" || value === "simulator-release";
22198
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
22199
+ var inside3 = (root, candidate) => {
22200
+ const path = relative7(resolve8(root), resolve8(candidate));
22201
+ return path === "" || !isAbsolute5(path) && path !== ".." && !path.startsWith(`..${sep5}`);
22202
+ };
22203
+ var exists4 = async (path) => {
22204
+ try {
22205
+ await access8(path);
22206
+ return true;
22207
+ } catch {
22208
+ return false;
22209
+ }
22210
+ };
22211
+ var sha2563 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
22212
+ var requireString3 = (value, name) => {
22213
+ if (typeof value !== "string" || !value)
22214
+ throw new TypeError(`Mobile certification ${name} is invalid.`);
22215
+ return value;
22216
+ };
22217
+ var requirePositiveInteger = (value, name) => {
22218
+ if (!Number.isSafeInteger(value) || Number(value) < 1)
22219
+ throw new TypeError(`Mobile certification ${name} is invalid.`);
22220
+ return Number(value);
22221
+ };
22222
+ var resolveEvidencePath = async (projectRoot, requested) => {
22223
+ const selected = resolve8(projectRoot, requested);
22224
+ if (!inside3(projectRoot, selected))
22225
+ throw new TypeError("Mobile certification evidence must remain inside the project.");
22226
+ if (basename3(selected) === "report.json")
22227
+ return selected;
22228
+ const report = join13(selected, "report.json");
22229
+ if (!await exists4(report))
22230
+ throw new TypeError(`Mobile certification evidence does not contain report.json: ${requested}`);
22231
+ return report;
22232
+ };
22233
+ var releaseIdentity = (release) => {
22234
+ const { metadata } = release;
22235
+ if (!metadata.signed)
22236
+ throw new TypeError("Mobile release certification requires a signed native release.");
22237
+ const common = {
22238
+ appBuild: metadata.appBuild,
22239
+ appId: metadata.appId,
22240
+ artifactBytes: metadata.bytes,
22241
+ artifactSha256: metadata.sha256,
22242
+ engine: metadata.engine,
22243
+ platform: metadata.platform,
22244
+ releaseId: metadata.releaseId,
22245
+ runtime: metadata.runtime,
22246
+ signed: true
22247
+ };
22248
+ if (metadata.platform === "android")
22249
+ return {
22250
+ ...common,
22251
+ ...metadata.versionCode === undefined ? {} : { versionCode: metadata.versionCode }
22252
+ };
22253
+ return {
22254
+ ...common,
22255
+ ...metadata.buildNumber === undefined ? {} : { buildNumber: metadata.buildNumber },
22256
+ marketingVersion: metadata.marketingVersion
22257
+ };
22258
+ };
22259
+ var requireCheck = (report, id, accepted = ["PASS"]) => {
22260
+ const checks = report.automatedChecks;
22261
+ if (!Array.isArray(checks))
22262
+ throw new TypeError("Mobile certification evidence has invalid automated checks.");
22263
+ const check = checks.find((candidate) => isRecord6(candidate) && candidate.id === id);
22264
+ if (!isRecord6(check) || !accepted.includes(String(check.result)))
22265
+ throw new TypeError(`Mobile certification evidence does not satisfy ${id}.`);
22266
+ };
22267
+ var validateCommonReport = (report, release) => {
22268
+ if (report.reportVersion !== 1 || report.platform !== release.platform || !isRecord6(report.metadata) || report.metadata.provider !== release.engine || !isRecord6(report.run) || report.run.status !== "pass" || report.run.appId !== release.appId)
22269
+ throw new TypeError("Mobile certification evidence does not match the release platform, provider, application, or successful-run contract.");
22270
+ requireCheck(report, "AUTO-DEV-01");
22271
+ return {
22272
+ generatedAt: requireString3(report.generatedAt, "evidence generatedAt"),
22273
+ run: report.run
22274
+ };
22275
+ };
22276
+ var validateReleaseFields = (value, release) => {
22277
+ if (value.releaseId !== release.releaseId || value.artifactSha256 !== release.artifactSha256 || value.artifactBytes !== release.artifactBytes || value.engine !== release.engine || value.signed !== true)
22278
+ throw new TypeError("Mobile certification evidence does not match the immutable release identity.");
22279
+ };
22280
+ var validateAndroidEvidence = (report, run, release, reportSha256, generatedAt) => {
22281
+ if (!isRecord6(run.release))
22282
+ throw new TypeError("Android certification evidence has no installed release result.");
22283
+ validateReleaseFields(run.release, release);
22284
+ if (run.release.embeddedOffline !== true)
22285
+ throw new TypeError("Android certification evidence did not prove embedded offline relaunch.");
22286
+ requireCheck(report, "AUTO-RELEASE-01");
22287
+ requireCheck(report, "AUTO-RELEASE-OFFLINE-01");
22288
+ return {
22289
+ generatedAt,
22290
+ networkUnavailable: "proven",
22291
+ remote: false,
22292
+ reportSha256,
22293
+ strength: "installed"
22294
+ };
22295
+ };
22296
+ var validateIosEvidence = (report, run, release, reportSha256, generatedAt) => {
22297
+ if (!isRecord6(run.iosRelease))
22298
+ throw new TypeError("iOS certification evidence has no installed release result.");
22299
+ const ios = run.iosRelease;
22300
+ validateReleaseFields(ios, release);
22301
+ if (ios.embeddedLocal !== true)
22302
+ throw new TypeError("iOS certification evidence did not prove embedded local rendering.");
22303
+ requireCheck(report, "AUTO-IOS-RELEASE-01");
22304
+ let strength;
22305
+ if (ios.distribution === "simulator-release" && ios.artifactExactness === "source-equivalent" && run.targetKind === "simulator" && ios.networkUnavailable === "not-proven")
22306
+ strength = "simulator";
22307
+ else if (ios.distribution === "registered-device" && ios.artifactExactness === "archive-equivalent" && run.targetKind === "device" && ios.networkUnavailable === "user-confirmed")
22308
+ strength = "device";
22309
+ else if (ios.distribution === "apple-processed" && ios.artifactExactness === "store-delivered" && run.targetKind === "device" && ios.networkUnavailable === "user-confirmed")
22310
+ strength = "store";
22311
+ else
22312
+ throw new TypeError("iOS certification evidence contains an invalid target, distribution, exactness, or offline-strength combination.");
22313
+ if (strength === "simulator")
22314
+ requireCheck(report, "AUTO-IOS-RELEASE-OFFLINE-01", ["SKIPPED"]);
22315
+ else
22316
+ requireCheck(report, "AUTO-IOS-RELEASE-OFFLINE-01");
22317
+ return {
22318
+ artifactExactness: ios.artifactExactness,
22319
+ distribution: ios.distribution,
22320
+ generatedAt,
22321
+ networkUnavailable: ios.networkUnavailable === "user-confirmed" ? "proven" : "not-proven",
22322
+ remote: ios.remote === true,
22323
+ reportSha256,
22324
+ strength
22325
+ };
22326
+ };
22327
+ var validateEvidence = async (projectRoot, requested, release) => {
22328
+ const path = await resolveEvidencePath(projectRoot, requested);
22329
+ const bytes = await readFile15(path);
22330
+ let value;
22331
+ try {
22332
+ value = JSON.parse(bytes.toString("utf8"));
22333
+ } catch {
22334
+ throw new TypeError("Mobile certification evidence report is not valid JSON.");
22335
+ }
22336
+ if (!isRecord6(value))
22337
+ throw new TypeError("Mobile certification evidence report must contain an object.");
22338
+ const { generatedAt, run } = validateCommonReport(value, release);
22339
+ const digest = sha2563(bytes);
22340
+ return release.platform === "android" ? validateAndroidEvidence(value, run, release, digest, generatedAt) : validateIosEvidence(value, run, release, digest, generatedAt);
22341
+ };
22342
+ var defaultRequirement = (platform) => platform === "android" ? "installed" : "simulator";
22343
+ var validateRequirement = (platform, requirement) => {
22344
+ if (platform === "android" && requirement !== "installed")
22345
+ throw new TypeError("Android certification currently uses its fixed installed/offline requirement; --require simulator, device, and store are iOS policies.");
22346
+ if (platform === "ios" && requirement === "installed")
22347
+ throw new TypeError("iOS certification requires simulator, device, or store evidence.");
22348
+ };
22349
+ var satisfiesRequirement = (strength, requirement) => {
22350
+ if (requirement === "installed")
22351
+ return strength === "installed";
22352
+ if (strength === "installed")
22353
+ return false;
22354
+ return STRENGTH_RANK[strength] >= STRENGTH_RANK[requirement];
22355
+ };
22356
+ var validateCertifiedEvidence = (platform, evidence) => {
22357
+ if (platform === "android") {
22358
+ if (evidence.strength !== "installed" || evidence.networkUnavailable !== "proven" || evidence.artifactExactness !== undefined || evidence.distribution !== undefined || evidence.remote)
22359
+ throw new TypeError("Android release certification contains invalid evidence strength.");
22360
+ return;
22361
+ }
22362
+ const simulator = evidence.strength === "simulator" && evidence.artifactExactness === "source-equivalent" && evidence.distribution === "simulator-release" && evidence.networkUnavailable === "not-proven";
22363
+ const device = evidence.strength === "device" && evidence.artifactExactness === "archive-equivalent" && evidence.distribution === "registered-device" && evidence.networkUnavailable === "proven";
22364
+ const store = evidence.strength === "store" && evidence.artifactExactness === "store-delivered" && evidence.distribution === "apple-processed" && evidence.networkUnavailable === "proven";
22365
+ if (!simulator && !device && !store)
22366
+ throw new TypeError("iOS release certification contains invalid evidence strength.");
22367
+ };
22368
+ var canonicalJsonValue = (value) => {
22369
+ if (Array.isArray(value))
22370
+ return value.map(canonicalJsonValue);
22371
+ if (!isRecord6(value))
22372
+ return value;
22373
+ const sorted = {};
22374
+ for (const key of Object.keys(value).sort())
22375
+ sorted[key] = canonicalJsonValue(value[key]);
22376
+ return sorted;
22377
+ };
22378
+ var certificationBody = (certification) => JSON.stringify(canonicalJsonValue(certification));
22379
+ var createAbsoluteMobileReleaseCertification = async (options) => {
22380
+ if (options.evidencePaths.length === 0)
22381
+ throw new TypeError("Mobile release certification requires at least one --evidence report.");
22382
+ const release = releaseIdentity(options.release);
22383
+ const requirement = options.requirement ?? defaultRequirement(release.platform);
22384
+ validateRequirement(release.platform, requirement);
22385
+ const evidence = await Promise.all(options.evidencePaths.map((path) => validateEvidence(options.projectRoot, path, release)));
22386
+ evidence.sort((left, right) => left.reportSha256.localeCompare(right.reportSha256));
22387
+ const [firstEvidence] = evidence;
22388
+ if (!firstEvidence)
22389
+ throw new TypeError("Mobile release certification requires valid evidence.");
22390
+ const strength = evidence.reduce((strongest, candidate) => STRENGTH_RANK[candidate.strength] > STRENGTH_RANK[strongest] ? candidate.strength : strongest, firstEvidence.strength);
22391
+ if (!satisfiesRequirement(strength, requirement))
22392
+ throw new TypeError(`Mobile release evidence strength ${strength} does not satisfy required ${requirement} certification.`);
22393
+ const body = {
22394
+ evidence,
22395
+ format: ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT,
22396
+ generatedAt: options.generatedAt ?? evidence.reduce((latest, candidate) => candidate.generatedAt > latest ? candidate.generatedAt : latest, firstEvidence.generatedAt),
22397
+ release,
22398
+ requirement,
22399
+ status: "certified",
22400
+ strength
22401
+ };
22402
+ return {
22403
+ certificationId: `${CERTIFICATION_PREFIX}${sha2563(certificationBody(body))}`,
22404
+ ...body
22405
+ };
22406
+ };
22407
+ var parseCertification = (value) => {
22408
+ if (!isRecord6(value) || value.format !== ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT || value.status !== "certified" || !isRecord6(value.release) || !Array.isArray(value.evidence))
22409
+ throw new TypeError("Mobile release certification contract is invalid.");
22410
+ const { release } = value;
22411
+ const { platform } = release;
22412
+ const { engine } = release;
22413
+ if (platform !== "android" && platform !== "ios" || engine !== "capacitor" && engine !== "expo" || release.signed !== true)
22414
+ throw new TypeError("Mobile release certification identity is invalid.");
22415
+ const { requirement } = value;
22416
+ const { strength } = value;
22417
+ if (!isRequirement(requirement) || !isStrength(strength))
22418
+ throw new TypeError("Mobile release certification policy is invalid.");
22419
+ validateRequirement(platform, requirement);
22420
+ if (!satisfiesRequirement(strength, requirement))
22421
+ throw new TypeError("Mobile release certification does not satisfy its declared policy.");
22422
+ const artifactBytes = requirePositiveInteger(release.artifactBytes, "release artifactBytes");
22423
+ const artifactSha256 = requireString3(release.artifactSha256, "release artifactSha256");
22424
+ if (!SHA256.test(artifactSha256))
22425
+ throw new TypeError("Mobile release certification artifact digest is invalid.");
22426
+ const parsedRelease = {
22427
+ appBuild: requireString3(release.appBuild, "release appBuild"),
22428
+ appId: requireString3(release.appId, "release appId"),
22429
+ artifactBytes,
22430
+ artifactSha256,
22431
+ engine,
22432
+ platform,
22433
+ releaseId: requireString3(release.releaseId, "release releaseId"),
22434
+ runtime: requireString3(release.runtime, "release runtime"),
22435
+ signed: true,
22436
+ ...release.buildNumber === undefined ? {} : {
22437
+ buildNumber: requirePositiveInteger(release.buildNumber, "release buildNumber")
22438
+ },
22439
+ ...release.marketingVersion === undefined ? {} : {
22440
+ marketingVersion: requireString3(release.marketingVersion, "release marketingVersion")
22441
+ },
22442
+ ...release.versionCode === undefined ? {} : {
22443
+ versionCode: requirePositiveInteger(release.versionCode, "release versionCode")
22444
+ }
22445
+ };
22446
+ const evidence = value.evidence.map((candidate) => {
22447
+ if (!isRecord6(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))
22448
+ throw new TypeError("Mobile release certification evidence is invalid.");
22449
+ const parsedEvidence = {
22450
+ generatedAt: candidate.generatedAt,
22451
+ networkUnavailable: candidate.networkUnavailable,
22452
+ remote: candidate.remote,
22453
+ reportSha256: String(candidate.reportSha256),
22454
+ strength: candidate.strength,
22455
+ ...candidate.artifactExactness === undefined ? {} : {
22456
+ artifactExactness: candidate.artifactExactness
22457
+ },
22458
+ ...candidate.distribution === undefined ? {} : { distribution: candidate.distribution }
22459
+ };
22460
+ validateCertifiedEvidence(platform, parsedEvidence);
22461
+ return parsedEvidence;
22462
+ });
22463
+ const strongestEvidence = evidence.reduce((strongest, candidate) => STRENGTH_RANK[candidate.strength] > STRENGTH_RANK[strongest] ? candidate.strength : strongest, evidence[0]?.strength ?? "installed");
22464
+ if (evidence.length === 0 || strongestEvidence !== strength)
22465
+ throw new TypeError("Mobile release certification evidence does not support its declared strength.");
22466
+ const parsed = {
22467
+ certificationId: requireString3(value.certificationId, "certificationId"),
22468
+ evidence,
22469
+ format: ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT,
22470
+ generatedAt: requireString3(value.generatedAt, "generatedAt"),
22471
+ release: parsedRelease,
22472
+ requirement,
22473
+ status: "certified",
22474
+ strength
22475
+ };
22476
+ const { certificationId, ...body } = parsed;
22477
+ if (certificationId !== `${CERTIFICATION_PREFIX}${sha2563(certificationBody(body))}`)
22478
+ throw new TypeError("Mobile release certification content digest is invalid.");
22479
+ return parsed;
22480
+ };
22481
+ var readAbsoluteMobileReleaseCertification = async (projectRoot, requested) => {
22482
+ const selected = resolve8(projectRoot, requested);
22483
+ if (!inside3(projectRoot, selected))
22484
+ throw new TypeError("Mobile release certification must remain inside the project.");
22485
+ const path = basename3(selected) === "certification.json" ? selected : join13(selected, "certification.json");
22486
+ const value = JSON.parse(await readFile15(path, "utf8"));
22487
+ return { certification: parseCertification(value), path };
22488
+ };
22489
+ var renderAbsoluteMobileReleaseCertification = (certification) => {
22490
+ const { release } = certification;
22491
+ const version = release.platform === "ios" ? `${release.marketingVersion ?? "unknown"} (${release.buildNumber ?? "unallocated"})` : String(release.versionCode ?? "unallocated");
22492
+ const evidence = certification.evidence.map((item) => `| ${item.strength} | ${item.networkUnavailable} | ${item.remote ? "paired Mac" : "local host"} | ${item.reportSha256} |`).join(`
22493
+ `);
22494
+ return `# AbsoluteJS mobile release certification
22495
+
22496
+ - Status: CERTIFIED
22497
+ - Certification: ${certification.certificationId}
22498
+ - Generated: ${certification.generatedAt}
22499
+ - Platform: ${release.platform}
22500
+ - Engine: ${release.engine}
22501
+ - Requirement: ${certification.requirement}
22502
+ - Evidence strength: ${certification.strength}
22503
+ - Release: ${release.releaseId}
22504
+ - Artifact SHA-256: ${release.artifactSha256}
22505
+ - Runtime fingerprint: ${release.runtime}
22506
+ - Embedded app build: ${release.appBuild}
22507
+ - Version/build: ${version}
22508
+
22509
+ 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.
22510
+
22511
+ | Strength | Network unavailable | Execution | Report SHA-256 |
22512
+ | --- | --- | --- | --- |
22513
+ ${evidence}
22514
+ `;
22515
+ };
22516
+ var verifyAbsoluteMobileReleaseCertification = (certification, release, requirement) => {
22517
+ const parsed = parseCertification(certification);
22518
+ const expected = releaseIdentity(release);
22519
+ if (JSON.stringify(parsed.release) !== JSON.stringify(expected))
22520
+ throw new TypeError("Mobile release certification was invalidated by a release identity, artifact, runtime, signing, version, build, or embedded-bundle change.");
22521
+ if (requirement) {
22522
+ validateRequirement(expected.platform, requirement);
22523
+ if (!satisfiesRequirement(parsed.strength, requirement))
22524
+ throw new TypeError(`Mobile release certification strength ${parsed.strength} does not satisfy required ${requirement} policy.`);
22525
+ }
22526
+ return parsed;
22527
+ };
22528
+ var writeAbsoluteMobileReleaseCertification = async (projectRoot, certification, outputDirectory) => {
22529
+ const root = resolve8(projectRoot);
22530
+ const parent = resolve8(root, outputDirectory ?? join13(".absolutejs", "mobile", "certifications", certification.release.platform, certification.release.releaseId));
22531
+ if (!inside3(root, parent))
22532
+ throw new TypeError("Mobile certification output must remain inside the project.");
22533
+ await mkdir11(parent, { recursive: true });
22534
+ const destination = join13(parent, certification.certificationId);
22535
+ const paths = {
22536
+ directory: destination,
22537
+ jsonPath: join13(destination, "certification.json"),
22538
+ markdownPath: join13(destination, "certification.md")
22539
+ };
22540
+ const json = `${JSON.stringify(certification, null, 2)}
22541
+ `;
22542
+ const markdown = renderAbsoluteMobileReleaseCertification(certification);
22543
+ const verifyExisting = async () => {
22544
+ const [existingJson, existingMarkdown] = await Promise.all([
22545
+ readFile15(paths.jsonPath, "utf8"),
22546
+ readFile15(paths.markdownPath, "utf8")
22547
+ ]);
22548
+ if (existingJson !== json || existingMarkdown !== markdown)
22549
+ throw new TypeError(`Immutable mobile certification ${certification.certificationId} already exists with different content.`);
22550
+ };
22551
+ if (await exists4(destination)) {
22552
+ await verifyExisting();
22553
+ return paths;
22554
+ }
22555
+ const staging = await mkdtemp4(join13(parent, ".certification-"));
22556
+ try {
22557
+ await Promise.all([
22558
+ writeFile12(join13(staging, "certification.json"), json, {
22559
+ flag: "wx"
22560
+ }),
22561
+ writeFile12(join13(staging, "certification.md"), markdown, {
22562
+ flag: "wx"
22563
+ })
22564
+ ]);
22565
+ await rename10(staging, destination).catch(async (error) => {
22566
+ if (await exists4(destination))
22567
+ return;
22568
+ throw error;
22569
+ });
22570
+ } finally {
22571
+ await rm7(staging, { force: true, recursive: true });
22572
+ }
22573
+ await verifyExisting();
22574
+ return paths;
22575
+ };
22576
+
22145
22577
  // src/mobile/releasePublisher.ts
22146
- import { access as access8 } from "fs/promises";
22147
- import { isAbsolute as isAbsolute5, relative as relative7, resolve as resolve8, sep as sep5 } from "path";
22578
+ import { access as access9 } from "fs/promises";
22579
+ import { isAbsolute as isAbsolute6, relative as relative8, resolve as resolve9, sep as sep6 } from "path";
22148
22580
  import { pathToFileURL as pathToFileURL2 } from "url";
22149
22581
  var prepareAbsoluteIosRelease = async (publisher, options) => {
22150
22582
  if (typeof publisher.prepareIosRelease !== "function") {
@@ -22167,24 +22599,24 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
22167
22599
  }
22168
22600
  return versionCode;
22169
22601
  };
22170
- var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
22171
- var isPublisher = (value) => isRecord6(value) && typeof value.publish === "function";
22602
+ var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
22603
+ var isPublisher = (value) => isRecord7(value) && typeof value.publish === "function";
22172
22604
  var publisherModulePath = (projectRoot, requested) => {
22173
- const root = resolve8(projectRoot);
22174
- const path = resolve8(root, requested);
22175
- const projectRelative = relative7(root, path);
22176
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep5}`) || isAbsolute5(projectRelative)) {
22605
+ const root = resolve9(projectRoot);
22606
+ const path = resolve9(root, requested);
22607
+ const projectRelative = relative8(root, path);
22608
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep6}`) || isAbsolute6(projectRelative)) {
22177
22609
  throw new TypeError("mobile publish --registry must remain inside the project.");
22178
22610
  }
22179
22611
  return path;
22180
22612
  };
22181
22613
  var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
22182
22614
  const modulePath = publisherModulePath(projectRoot, requestedModulePath);
22183
- await access8(modulePath).catch(() => {
22615
+ await access9(modulePath).catch(() => {
22184
22616
  throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
22185
22617
  });
22186
22618
  const loaded = await import(pathToFileURL2(modulePath).href);
22187
- const publisher = isRecord6(loaded) ? loaded.default ?? loaded.registry : undefined;
22619
+ const publisher = isRecord7(loaded) ? loaded.default ?? loaded.registry : undefined;
22188
22620
  if (!isPublisher(publisher)) {
22189
22621
  throw new TypeError("Native release registry module must default-export a registry with publish(options).");
22190
22622
  }
@@ -22242,8 +22674,8 @@ var publishAbsoluteIosRelease = async (options) => {
22242
22674
  };
22243
22675
 
22244
22676
  // src/mobile/mobileInspect.ts
22245
- import { access as access9, readFile as readFile15 } from "fs/promises";
22246
- import { join as join13, relative as relative8, resolve as resolve9 } from "path";
22677
+ import { access as access10, readFile as readFile16 } from "fs/promises";
22678
+ import { join as join14, relative as relative9, resolve as resolve10 } from "path";
22247
22679
  var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1;
22248
22680
  var MOBILE_PACKAGE_NAMES = new Set([
22249
22681
  "@absolutejs/absolute",
@@ -22258,19 +22690,19 @@ var MOBILE_PACKAGE_NAMES = new Set([
22258
22690
  ]);
22259
22691
  var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
22260
22692
  var portablePath2 = (projectRoot, path) => {
22261
- const value = relative8(resolve9(projectRoot), resolve9(path)).replaceAll("\\", "/");
22693
+ const value = relative9(resolve10(projectRoot), resolve10(path)).replaceAll("\\", "/");
22262
22694
  return value || ".";
22263
22695
  };
22264
22696
  var pathExists4 = async (path) => {
22265
22697
  try {
22266
- await access9(path);
22698
+ await access10(path);
22267
22699
  return true;
22268
22700
  } catch {
22269
22701
  return false;
22270
22702
  }
22271
22703
  };
22272
22704
  var readObject2 = async (path) => {
22273
- const value = JSON.parse(await readFile15(path, "utf8"));
22705
+ const value = JSON.parse(await readFile16(path, "utf8"));
22274
22706
  if (!isObject2(value))
22275
22707
  throw new TypeError("JSON root must be an object.");
22276
22708
  return value;
@@ -22282,13 +22714,13 @@ var addPackageDeclarations = (declarations, value) => {
22282
22714
  declarations.set(name, declared);
22283
22715
  };
22284
22716
  var packageInspections = async (projectRoot, additionalNames) => {
22285
- const project = await readObject2(join13(projectRoot, "package.json"));
22717
+ const project = await readObject2(join14(projectRoot, "package.json"));
22286
22718
  const declarations = new Map;
22287
22719
  for (const field2 of ["dependencies", "devDependencies"])
22288
22720
  addPackageDeclarations(declarations, project[field2]);
22289
22721
  const names = [...new Set([...declarations.keys(), ...additionalNames])].filter((name) => MOBILE_PACKAGE_NAMES.has(name) || name.startsWith("@capacitor/") || additionalNames.includes(name)).sort();
22290
22722
  return Promise.all(names.map(async (name) => {
22291
- const installedManifest = await readObject2(join13(projectRoot, "node_modules", name, "package.json")).catch(() => {
22723
+ const installedManifest = await readObject2(join14(projectRoot, "node_modules", name, "package.json")).catch(() => {
22292
22724
  return;
22293
22725
  });
22294
22726
  const installed = installedManifest?.version;
@@ -22347,7 +22779,7 @@ var inspectAbsoluteMobileProject = async (config, projectRoot, options = {}) =>
22347
22779
  },
22348
22780
  format: ABSOLUTE_MOBILE_INSPECTION_FORMAT,
22349
22781
  nativeProjects: await Promise.all(config.platforms.map(async (platform) => {
22350
- const path = join13(config.nativeProjectDirectory, platform);
22782
+ const path = join14(config.nativeProjectDirectory, platform);
22351
22783
  return {
22352
22784
  initialized: await pathExists4(path),
22353
22785
  path: portablePath2(projectRoot, path),
@@ -22404,8 +22836,8 @@ var renderAbsoluteMobileProjectInspection = (report) => {
22404
22836
 
22405
22837
  // src/mobile/ciWorkflow.ts
22406
22838
  import { existsSync } from "fs";
22407
- import { access as access10, mkdir as mkdir11, readFile as readFile16, writeFile as writeFile12 } from "fs/promises";
22408
- import { dirname as dirname10, extname as extname2, relative as relative9, resolve as resolve10, sep as sep6 } from "path";
22839
+ import { access as access11, mkdir as mkdir12, readFile as readFile17, writeFile as writeFile13 } from "fs/promises";
22840
+ import { dirname as dirname10, extname as extname2, relative as relative10, resolve as resolve11, sep as sep7 } from "path";
22409
22841
  var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 1;
22410
22842
  var SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
22411
22843
  var CI_ENV_INDENTATION = 6;
@@ -22424,9 +22856,9 @@ var RESERVED_SECRET_NAMES = new Set([
22424
22856
  "APP_STORE_CONNECT_KEY_ID",
22425
22857
  "APP_STORE_CONNECT_PRIVATE_KEY_BASE64"
22426
22858
  ]);
22427
- var exists4 = async (path) => {
22859
+ var exists5 = async (path) => {
22428
22860
  try {
22429
- await access10(path);
22861
+ await access11(path);
22430
22862
  return true;
22431
22863
  } catch {
22432
22864
  return false;
@@ -22434,10 +22866,10 @@ var exists4 = async (path) => {
22434
22866
  };
22435
22867
  var yamlString = (value) => `'${value.replaceAll("'", "''")}'`;
22436
22868
  var projectPath2 = (projectRoot, value, field2, options = {}) => {
22437
- const root = resolve10(projectRoot);
22438
- const path = resolve10(root, value);
22439
- const portable = relative9(root, path).replaceAll("\\", "/");
22440
- if (portable === ".." || portable.startsWith(`..${sep6}`) || portable.startsWith("../") || portable === "") {
22869
+ const root = resolve11(projectRoot);
22870
+ const path = resolve11(root, value);
22871
+ const portable = relative10(root, path).replaceAll("\\", "/");
22872
+ if (portable === ".." || portable.startsWith(`..${sep7}`) || portable.startsWith("../") || portable === "") {
22441
22873
  throw new TypeError(`${field2} must remain inside the project root.`);
22442
22874
  }
22443
22875
  if (/\r|\n/u.test(portable) || portable.startsWith("-"))
@@ -22447,11 +22879,11 @@ var projectPath2 = (projectRoot, value, field2, options = {}) => {
22447
22879
  return portable;
22448
22880
  };
22449
22881
  var workflowOutputPath = (projectRoot, value) => {
22450
- const root = resolve10(projectRoot);
22451
- const workflows = resolve10(root, ".github/workflows");
22452
- const path = resolve10(root, value ?? ".github/workflows/absolute-mobile.yml");
22453
- const portable = relative9(workflows, path);
22454
- if (portable === ".." || portable.startsWith(`..${sep6}`) || extname2(path) !== ".yml" && extname2(path) !== ".yaml") {
22882
+ const root = resolve11(projectRoot);
22883
+ const workflows = resolve11(root, ".github/workflows");
22884
+ const path = resolve11(root, value ?? ".github/workflows/absolute-mobile.yml");
22885
+ const portable = relative10(workflows, path);
22886
+ if (portable === ".." || portable.startsWith(`..${sep7}`) || extname2(path) !== ".yml" && extname2(path) !== ".yaml") {
22455
22887
  throw new TypeError("mobile ci github --output must be a .yml or .yaml file inside .github/workflows.");
22456
22888
  }
22457
22889
  return path;
@@ -22880,12 +23312,12 @@ ${bundleAuditSteps}${platforms.includes("android") ? androidJob({ customSecrets,
22880
23312
  var writeAbsoluteMobileGithubWorkflow = async (options) => {
22881
23313
  const path = workflowOutputPath(options.projectRoot, options.outputPath);
22882
23314
  const generated = createAbsoluteMobileGithubWorkflow(options);
22883
- const previous = await exists4(path) ? await readFile16(path, "utf8") : undefined;
23315
+ const previous = await exists5(path) ? await readFile17(path, "utf8") : undefined;
22884
23316
  if (previous !== undefined && previous !== generated.workflow && !options.force)
22885
- throw new TypeError(`${relative9(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
23317
+ throw new TypeError(`${relative10(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
22886
23318
  if (previous !== generated.workflow) {
22887
- await mkdir11(dirname10(path), { recursive: true });
22888
- await writeFile12(path, generated.workflow);
23319
+ await mkdir12(dirname10(path), { recursive: true });
23320
+ await writeFile13(path, generated.workflow);
22889
23321
  }
22890
23322
  return {
22891
23323
  changed: previous !== generated.workflow,
@@ -22898,46 +23330,46 @@ var writeAbsoluteMobileGithubWorkflow = async (options) => {
22898
23330
  };
22899
23331
 
22900
23332
  // src/mobile/updateSigning.ts
22901
- import { createHash as createHash5, sign, verify } from "crypto";
23333
+ import { createHash as createHash6, sign, verify } from "crypto";
22902
23334
  import {
22903
23335
  cp,
22904
- mkdir as mkdir12,
22905
- mkdtemp as mkdtemp4,
23336
+ mkdir as mkdir13,
23337
+ mkdtemp as mkdtemp5,
22906
23338
  readdir as readdir6,
22907
- readFile as readFile17,
22908
- rename as rename10,
22909
- rm as rm7,
23339
+ readFile as readFile18,
23340
+ rename as rename11,
23341
+ rm as rm8,
22910
23342
  stat as stat6,
22911
- writeFile as writeFile13
23343
+ writeFile as writeFile14
22912
23344
  } from "fs/promises";
22913
- import { dirname as dirname11, join as join14, relative as relative10, resolve as resolve11 } from "path";
23345
+ import { dirname as dirname11, join as join15, relative as relative11, resolve as resolve12 } from "path";
22914
23346
  var UPDATE_MANIFEST_FILE = "update.json";
22915
23347
  var UPDATE_FILES_DIRECTORY = "files";
22916
- var sha2563 = (value) => createHash5("sha256").update(value).digest("hex");
23348
+ var sha2564 = (value) => createHash6("sha256").update(value).digest("hex");
22917
23349
  var listFiles = async (root, directory = root) => {
22918
23350
  const entries = await readdir6(directory, { withFileTypes: true });
22919
23351
  const paths = await Promise.all(entries.map(async (entry) => {
22920
- const path = join14(directory, entry.name);
23352
+ const path = join15(directory, entry.name);
22921
23353
  if (entry.isDirectory())
22922
23354
  return listFiles(root, path);
22923
23355
  if (!entry.isFile())
22924
23356
  throw new TypeError("Mobile updates cannot contain links or special files.");
22925
- return [relative10(root, path).replaceAll("\\", "/")];
23357
+ return [relative11(root, path).replaceAll("\\", "/")];
22926
23358
  }));
22927
23359
  return paths.flat().sort((left, right) => left.localeCompare(right));
22928
23360
  };
22929
23361
  var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
22930
- const source = join14(root, path);
23362
+ const source = join15(root, path);
22931
23363
  const [metadata, contents] = await Promise.all([
22932
23364
  stat6(source),
22933
- readFile17(source)
23365
+ readFile18(source)
22934
23366
  ]);
22935
- return { bytes: metadata.size, path, sha256: sha2563(contents) };
23367
+ return { bytes: metadata.size, path, sha256: sha2564(contents) };
22936
23368
  }));
22937
- var releaseIdFor = (value) => `amu_${sha2563(canonicalizeAbsoluteMobileUpdate(value))}`;
23369
+ var releaseIdFor = (value) => `amu_${sha2564(canonicalizeAbsoluteMobileUpdate(value))}`;
22938
23370
  var buildAbsoluteMobileUpdate = async (options) => {
22939
- const bundleDirectory = resolve11(options.bundleDirectory);
22940
- const outputRoot = resolve11(options.outputDirectory);
23371
+ const bundleDirectory = resolve12(options.bundleDirectory);
23372
+ const outputRoot = resolve12(options.outputDirectory);
22941
23373
  if (outputRoot === bundleDirectory || outputRoot.startsWith(`${bundleDirectory}/`))
22942
23374
  throw new TypeError("Mobile update output must be outside the embedded bundle.");
22943
23375
  const paths = await listFiles(bundleDirectory);
@@ -22965,30 +23397,30 @@ var buildAbsoluteMobileUpdate = async (options) => {
22965
23397
  value: signature.toString("base64")
22966
23398
  }
22967
23399
  });
22968
- await mkdir12(outputRoot, { recursive: true });
22969
- const outputDirectory = join14(outputRoot, manifest.releaseId);
22970
- const staging = await mkdtemp4(join14(outputRoot, ".stage-"));
23400
+ await mkdir13(outputRoot, { recursive: true });
23401
+ const outputDirectory = join15(outputRoot, manifest.releaseId);
23402
+ const staging = await mkdtemp5(join15(outputRoot, ".stage-"));
22971
23403
  try {
22972
- await cp(bundleDirectory, join14(staging, UPDATE_FILES_DIRECTORY), {
23404
+ await cp(bundleDirectory, join15(staging, UPDATE_FILES_DIRECTORY), {
22973
23405
  force: true,
22974
23406
  recursive: true
22975
23407
  });
22976
- await writeFile13(join14(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
23408
+ await writeFile14(join15(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
22977
23409
  `);
22978
- await rename10(staging, outputDirectory);
23410
+ await rename11(staging, outputDirectory);
22979
23411
  } catch (error) {
22980
- await rm7(staging, { force: true, recursive: true });
23412
+ await rm8(staging, { force: true, recursive: true });
22981
23413
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
22982
23414
  throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
22983
23415
  throw error;
22984
23416
  }
22985
23417
  return {
22986
23418
  manifest,
22987
- manifestPath: join14(outputDirectory, UPDATE_MANIFEST_FILE),
23419
+ manifestPath: join15(outputDirectory, UPDATE_MANIFEST_FILE),
22988
23420
  outputDirectory
22989
23421
  };
22990
23422
  };
22991
- var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile17(join14(resolve11(directory), UPDATE_MANIFEST_FILE), "utf8")));
23423
+ var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile18(join15(resolve12(directory), UPDATE_MANIFEST_FILE), "utf8")));
22992
23424
  var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
22993
23425
  const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
22994
23426
  const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
@@ -22998,8 +23430,8 @@ var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
22998
23430
  };
22999
23431
 
23000
23432
  // src/mobile/expoUpdate.ts
23001
- import { access as access11, mkdir as mkdir13, readFile as readFile18, writeFile as writeFile14 } from "fs/promises";
23002
- import { dirname as dirname12, extname as extname3, join as join15 } from "path";
23433
+ import { access as access12, mkdir as mkdir14, readFile as readFile19, writeFile as writeFile15 } from "fs/promises";
23434
+ import { dirname as dirname12, extname as extname3, join as join16 } from "path";
23003
23435
  var ABSOLUTE_EXPO_UPDATE_DESCRIPTOR = "_absolute/expo-update.json";
23004
23436
  var ABSOLUTE_EXPO_UPDATE_FORMAT = 1;
23005
23437
  var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
@@ -23065,7 +23497,7 @@ var parseAbsoluteExpoUpdateDescriptor = (value) => {
23065
23497
  };
23066
23498
  };
23067
23499
  var finalizeAbsoluteExpoUpdateExport = async (options) => {
23068
- const metadata = JSON.parse(await readFile18(join15(options.exportDirectory, "metadata.json"), "utf8"));
23500
+ const metadata = JSON.parse(await readFile19(join16(options.exportDirectory, "metadata.json"), "utf8"));
23069
23501
  if (!object2(metadata) || !object2(metadata.fileMetadata))
23070
23502
  throw new TypeError("Expo export metadata.json is invalid.");
23071
23503
  const descriptor = parseAbsoluteExpoUpdateDescriptor({
@@ -23076,33 +23508,33 @@ var finalizeAbsoluteExpoUpdateExport = async (options) => {
23076
23508
  runtimeVersion: options.runtimeVersion
23077
23509
  });
23078
23510
  const referenced = Object.values(descriptor.platforms).flatMap((entry) => entry ? [entry.launchAsset, ...entry.assets] : []);
23079
- await Promise.all(referenced.map(({ path }) => access11(join15(options.exportDirectory, path)).catch(() => {
23511
+ await Promise.all(referenced.map(({ path }) => access12(join16(options.exportDirectory, path)).catch(() => {
23080
23512
  throw new TypeError(`Expo export metadata references missing asset ${path}.`);
23081
23513
  })));
23082
- const destination = join15(options.exportDirectory, ABSOLUTE_EXPO_UPDATE_DESCRIPTOR);
23083
- await mkdir13(dirname12(destination), { recursive: true });
23084
- await writeFile14(destination, `${JSON.stringify(descriptor, null, "\t")}
23514
+ const destination = join16(options.exportDirectory, ABSOLUTE_EXPO_UPDATE_DESCRIPTOR);
23515
+ await mkdir14(dirname12(destination), { recursive: true });
23516
+ await writeFile15(destination, `${JSON.stringify(descriptor, null, "\t")}
23085
23517
  `);
23086
23518
  return { descriptor, path: destination };
23087
23519
  };
23088
23520
 
23089
23521
  // src/mobile/expoCodeSigning.ts
23090
23522
  var import_code_signing_certificates = __toESM(require_main(), 1);
23091
- import { access as access12, mkdir as mkdir14, writeFile as writeFile15 } from "fs/promises";
23092
- import { dirname as dirname13, isAbsolute as isAbsolute6, relative as relative11, resolve as resolve12, sep as sep7 } from "path";
23093
- var exists5 = async (path) => access12(path).then(() => true).catch(() => false);
23094
- var inside3 = (root, path) => {
23095
- const location = relative11(root, path);
23096
- return location === "" || location !== ".." && !location.startsWith(`..${sep7}`) && !isAbsolute6(location);
23523
+ import { access as access13, mkdir as mkdir15, writeFile as writeFile16 } from "fs/promises";
23524
+ import { dirname as dirname13, isAbsolute as isAbsolute7, relative as relative12, resolve as resolve13, sep as sep8 } from "path";
23525
+ var exists6 = async (path) => access13(path).then(() => true).catch(() => false);
23526
+ var inside4 = (root, path) => {
23527
+ const location = relative12(root, path);
23528
+ return location === "" || location !== ".." && !location.startsWith(`..${sep8}`) && !isAbsolute7(location);
23097
23529
  };
23098
23530
  var generateAbsoluteExpoCodeSigning = async (options) => {
23099
- const root = resolve12(options.projectRoot);
23100
- const certificatePath = resolve12(root, options.certificatePath);
23101
- const privateKeyPath = resolve12(root, options.privateKeyPath);
23102
- const publicKeyPath = resolve12(root, options.publicKeyPath ?? resolve12(dirname13(privateKeyPath), "public-key.pem"));
23103
- if (!inside3(root, certificatePath))
23531
+ const root = resolve13(options.projectRoot);
23532
+ const certificatePath = resolve13(root, options.certificatePath);
23533
+ const privateKeyPath = resolve13(root, options.privateKeyPath);
23534
+ const publicKeyPath = resolve13(root, options.publicKeyPath ?? resolve13(dirname13(privateKeyPath), "public-key.pem"));
23535
+ if (!inside4(root, certificatePath))
23104
23536
  throw new TypeError("Expo code-signing certificate must be written inside the project so store builds can embed it.");
23105
- if (inside3(root, privateKeyPath) || inside3(root, publicKeyPath))
23537
+ if (inside4(root, privateKeyPath) || inside4(root, publicKeyPath))
23106
23538
  throw new TypeError("Expo code-signing private/public key output must remain outside the project and source control.");
23107
23539
  const commonName = options.commonName.trim();
23108
23540
  if (!commonName || commonName.length > 64)
@@ -23111,7 +23543,7 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
23111
23543
  if (!Number.isSafeInteger(validityYears) || validityYears < 1 || validityYears > 20)
23112
23544
  throw new TypeError("Expo code-signing validity must be an integer from 1 through 20 years.");
23113
23545
  const collisions = (await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map(async (path) => ({
23114
- exists: await exists5(path),
23546
+ exists: await exists6(path),
23115
23547
  path
23116
23548
  })))).filter((entry) => entry.exists);
23117
23549
  if (collisions.length > 0)
@@ -23129,11 +23561,11 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
23129
23561
  import_code_signing_certificates.validateSelfSignedCertificate(certificate, keyPair);
23130
23562
  const { privateKeyPEM, publicKeyPEM } = import_code_signing_certificates.convertKeyPairToPEM(keyPair);
23131
23563
  const certificatePem = import_code_signing_certificates.convertCertificateToCertificatePEM(certificate);
23132
- await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) => mkdir14(dirname13(path), { recursive: true })));
23564
+ await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) => mkdir15(dirname13(path), { recursive: true })));
23133
23565
  await Promise.all([
23134
- writeFile15(certificatePath, certificatePem, { flag: "wx", mode: 420 }),
23135
- writeFile15(privateKeyPath, privateKeyPEM, { flag: "wx", mode: 384 }),
23136
- writeFile15(publicKeyPath, publicKeyPEM, { flag: "wx", mode: 420 })
23566
+ writeFile16(certificatePath, certificatePem, { flag: "wx", mode: 420 }),
23567
+ writeFile16(privateKeyPath, privateKeyPEM, { flag: "wx", mode: 384 }),
23568
+ writeFile16(publicKeyPath, publicKeyPEM, { flag: "wx", mode: 420 })
23137
23569
  ]);
23138
23570
  return {
23139
23571
  certificatePath,
@@ -23144,8 +23576,8 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
23144
23576
  };
23145
23577
 
23146
23578
  // src/mobile/updatePublisher.ts
23147
- import { access as access13 } from "fs/promises";
23148
- import { isAbsolute as isAbsolute7, relative as relative12, resolve as resolve13, sep as sep8 } from "path";
23579
+ import { access as access14 } from "fs/promises";
23580
+ import { isAbsolute as isAbsolute8, relative as relative13, resolve as resolve14, sep as sep9 } from "path";
23149
23581
  import { pathToFileURL as pathToFileURL3 } from "url";
23150
23582
  var lifecycleMethod = (publisher, name) => {
23151
23583
  const method = publisher[name];
@@ -23254,16 +23686,16 @@ var pruneAbsoluteMobileUpdates = async (options) => {
23254
23686
  var object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
23255
23687
  var isPublisher2 = (value) => object3(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
23256
23688
  var projectPath3 = (projectRoot, requested, label) => {
23257
- const root = resolve13(projectRoot);
23258
- const path = resolve13(root, requested);
23259
- const projectRelative = relative12(root, path);
23260
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep8}`) || isAbsolute7(projectRelative))
23689
+ const root = resolve14(projectRoot);
23690
+ const path = resolve14(root, requested);
23691
+ const projectRelative = relative13(root, path);
23692
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep9}`) || isAbsolute8(projectRelative))
23261
23693
  throw new TypeError(`${label} must remain inside the project.`);
23262
23694
  return path;
23263
23695
  };
23264
23696
  var loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
23265
23697
  const modulePath = projectPath3(projectRoot, requestedModulePath, "mobile update registry");
23266
- await access13(modulePath).catch(() => {
23698
+ await access14(modulePath).catch(() => {
23267
23699
  throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
23268
23700
  });
23269
23701
  const loaded = await import(pathToFileURL3(modulePath).href);
@@ -23318,10 +23750,10 @@ var rollbackAbsoluteMobileUpdate = async (options) => {
23318
23750
  var NOT_FOUND4 = -1;
23319
23751
  var ANDROID_EMULATOR_BOOT_POLL_MS = 500;
23320
23752
  var ANDROID_EMULATOR_BOOT_TIMEOUT_MS = 180000;
23321
- var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
23753
+ var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
23322
23754
  var expoPublicConfig = (config) => {
23323
23755
  const result = { ...config };
23324
- if (!isRecord7(result.updates))
23756
+ if (!isRecord8(result.updates))
23325
23757
  return result;
23326
23758
  const {
23327
23759
  codeSigningCertificate: _codeSigningCertificate,
@@ -23361,13 +23793,13 @@ var CAPACITOR_SYNC_PACKAGE_SPECS = [
23361
23793
  ];
23362
23794
  var packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@"));
23363
23795
  var directProjectPackages = async (projectRoot) => {
23364
- const manifest = JSON.parse(await readFile19(join16(projectRoot, "package.json"), "utf8"));
23365
- if (!isRecord7(manifest))
23796
+ const manifest = JSON.parse(await readFile20(join17(projectRoot, "package.json"), "utf8"));
23797
+ if (!isRecord8(manifest))
23366
23798
  throw new TypeError("Application package.json must contain an object.");
23367
23799
  const names = new Set;
23368
23800
  for (const field2 of ["dependencies", "devDependencies"]) {
23369
23801
  const dependencies = Reflect.get(manifest, field2);
23370
- if (isRecord7(dependencies))
23802
+ if (isRecord8(dependencies))
23371
23803
  for (const name of Object.keys(dependencies))
23372
23804
  names.add(name);
23373
23805
  }
@@ -23375,8 +23807,8 @@ var directProjectPackages = async (projectRoot) => {
23375
23807
  };
23376
23808
  var resolvedPackageVersion = async (projectRoot, packageName) => {
23377
23809
  try {
23378
- const manifest = JSON.parse(await readFile19(join16(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
23379
- return isRecord7(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
23810
+ const manifest = JSON.parse(await readFile20(join17(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
23811
+ return isRecord8(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
23380
23812
  } catch {
23381
23813
  return;
23382
23814
  }
@@ -23439,9 +23871,9 @@ var runCapacitor = async (projectRoot, args) => {
23439
23871
  };
23440
23872
  var runCapacitorForPlatforms = (projectRoot, command, platforms) => platforms.reduce((pending, platform2) => pending.then(() => runCapacitor(projectRoot, [command, platform2])), Promise.resolve());
23441
23873
  var expoExecutable = async (project) => {
23442
- const executable = join16(project, "node_modules", ".bin", "expo");
23874
+ const executable = join17(project, "node_modules", ".bin", "expo");
23443
23875
  try {
23444
- await access14(executable);
23876
+ await access15(executable);
23445
23877
  return executable;
23446
23878
  } catch {
23447
23879
  throw new TypeError("Expo dependencies are not installed in the generated shell. Run `absolute mobile init --yes`.");
@@ -23473,14 +23905,14 @@ var runExpo = async (project, args, options = {}) => {
23473
23905
  };
23474
23906
  var ensureExpoPackages = async (project, args) => {
23475
23907
  try {
23476
- const manifest = JSON.parse(await readFile19(join16(project, "package.json"), "utf8"));
23908
+ const manifest = JSON.parse(await readFile20(join17(project, "package.json"), "utf8"));
23477
23909
  const dependencies = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "dependencies") : undefined;
23478
23910
  if (typeof dependencies !== "object" || dependencies === null || Array.isArray(dependencies))
23479
23911
  throw new TypeError("Generated Expo dependencies are invalid.");
23480
23912
  await Promise.all(Object.entries(dependencies).map(async ([name, expected]) => {
23481
23913
  if (typeof expected !== "string")
23482
23914
  throw new TypeError("Generated Expo dependency version is invalid.");
23483
- const installed = JSON.parse(await readFile19(join16(project, "node_modules", name, "package.json"), "utf8"));
23915
+ const installed = JSON.parse(await readFile20(join17(project, "node_modules", name, "package.json"), "utf8"));
23484
23916
  const actual = typeof installed === "object" && installed !== null ? Reflect.get(installed, "version") : undefined;
23485
23917
  if (typeof actual !== "string")
23486
23918
  throw new TypeError("Installed Expo dependency version is invalid.");
@@ -23678,7 +24110,7 @@ var sync = async (args) => {
23678
24110
  };
23679
24111
  var associations = async (args) => {
23680
24112
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
23681
- const outputDirectory = resolve14(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
24113
+ const outputDirectory = resolve15(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
23682
24114
  if (args.includes("--verify")) {
23683
24115
  const result2 = await verifyAbsoluteMobileAssociationFiles(mobile);
23684
24116
  console.log(`Verified ${result2.results.length} hosted association files`);
@@ -23730,7 +24162,7 @@ var generateGithubCi = async (args) => {
23730
24162
  const publicResult = {
23731
24163
  changed: result.changed,
23732
24164
  format: result.format,
23733
- path: relative13(projectRoot, result.path).replaceAll("\\", "/"),
24165
+ path: relative14(projectRoot, result.path).replaceAll("\\", "/"),
23734
24166
  platforms: result.platforms,
23735
24167
  publishing: result.publishing,
23736
24168
  requiredSecrets: result.requiredSecrets
@@ -23841,9 +24273,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
23841
24273
  temporaryDirectory: undefined
23842
24274
  };
23843
24275
  await ensureExpoPackages(mobile.nativeProjectDirectory, options.args);
23844
- const temporaryParent = join16(options.projectRoot, ".absolutejs", "mobile", "expo-update-exports");
23845
- await mkdir15(temporaryParent, { recursive: true });
23846
- const temporaryDirectory = await mkdtemp5(join16(temporaryParent, ".stage-"));
24276
+ const temporaryParent = join17(options.projectRoot, ".absolutejs", "mobile", "expo-update-exports");
24277
+ await mkdir16(temporaryParent, { recursive: true });
24278
+ const temporaryDirectory = await mkdtemp6(join17(temporaryParent, ".stage-"));
23847
24279
  try {
23848
24280
  await runExpo(mobile.nativeProjectDirectory, [
23849
24281
  "export",
@@ -23855,9 +24287,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
23855
24287
  temporaryDirectory,
23856
24288
  "--clear"
23857
24289
  ], { production: true });
23858
- const generatedApp = JSON.parse(await readFile19(join16(mobile.nativeProjectDirectory, "app.json"), "utf8"));
23859
- const expoConfig = isRecord7(generatedApp) ? generatedApp.expo : undefined;
23860
- if (!isRecord7(expoConfig))
24290
+ const generatedApp = JSON.parse(await readFile20(join17(mobile.nativeProjectDirectory, "app.json"), "utf8"));
24291
+ const expoConfig = isRecord8(generatedApp) ? generatedApp.expo : undefined;
24292
+ if (!isRecord8(expoConfig))
23861
24293
  throw new TypeError("Generated Expo app configuration is invalid.");
23862
24294
  const publicExpoConfig = expoPublicConfig(expoConfig);
23863
24295
  const computedRuntime = resolveAbsoluteMobileUpdateRuntime(mobile, options.projectRoot).fingerprint;
@@ -23873,7 +24305,7 @@ var prepareExpoMobileUpdateExport = async (options) => {
23873
24305
  temporaryDirectory
23874
24306
  };
23875
24307
  } catch (error) {
23876
- await rm8(temporaryDirectory, { force: true, recursive: true });
24308
+ await rm9(temporaryDirectory, { force: true, recursive: true });
23877
24309
  throw error;
23878
24310
  }
23879
24311
  };
@@ -23898,11 +24330,11 @@ var buildMobileUpdate = async (args) => {
23898
24330
  let expoExportDirectory;
23899
24331
  try {
23900
24332
  await start(mobileBuildServerEntry(args), valueAfter(args, "--web-outdir"), configPath, { prepareOnly: true });
23901
- const embedded = JSON.parse(await readFile19(join16(mobile.bundleDirectory, "absolute-mobile-manifest.json"), "utf8"));
23902
- const runtimeFingerprint = isRecord7(embedded) ? embedded.nativeRuntime : undefined;
24333
+ const embedded = JSON.parse(await readFile20(join17(mobile.bundleDirectory, "absolute-mobile-manifest.json"), "utf8"));
24334
+ const runtimeFingerprint = isRecord8(embedded) ? embedded.nativeRuntime : undefined;
23903
24335
  if (typeof runtimeFingerprint !== "string" || !/^[a-f0-9]{64}$/u.test(runtimeFingerprint))
23904
24336
  throw new TypeError("Prepared mobile bundle is missing its native runtime fingerprint.");
23905
- const privateKey = await readFile19(resolve14(projectRoot, signingKeyPath));
24337
+ const privateKey = await readFile20(resolve15(projectRoot, signingKeyPath));
23906
24338
  const configuredPublicKey = Buffer.from(mobile.updates.publicKeys[keyId], "base64");
23907
24339
  const derivedPublicKey = createPublicKey2(privateKey).export({
23908
24340
  format: "der",
@@ -23923,7 +24355,7 @@ var buildMobileUpdate = async (args) => {
23923
24355
  channel: mobile.updates.channel,
23924
24356
  classification,
23925
24357
  keyId,
23926
- outputDirectory: valueAfter(args, "--outdir") ?? join16(projectRoot, ".absolutejs", "mobile", "updates"),
24358
+ outputDirectory: valueAfter(args, "--outdir") ?? join17(projectRoot, ".absolutejs", "mobile", "updates"),
23927
24359
  privateKey,
23928
24360
  runtimeFingerprint
23929
24361
  });
@@ -23935,7 +24367,7 @@ var buildMobileUpdate = async (args) => {
23935
24367
  return result;
23936
24368
  } finally {
23937
24369
  if (expoExportDirectory)
23938
- await rm8(expoExportDirectory, { force: true, recursive: true });
24370
+ await rm9(expoExportDirectory, { force: true, recursive: true });
23939
24371
  sendTelemetryEvent("mobile:update-build", {
23940
24372
  classification: classification ?? "invalid",
23941
24373
  durationMs: Math.round(performance.now() - startedAt),
@@ -23965,7 +24397,7 @@ var generateExpoUpdateSigning = async (args) => {
23965
24397
  ...valueAfter(args, "--public-key") ? { publicKeyPath: valueAfter(args, "--public-key") } : {},
23966
24398
  ...validityYears === undefined ? {} : { validityYears }
23967
24399
  });
23968
- const certificatePath = relative13(projectRoot, result.certificatePath).replaceAll("\\", "/");
24400
+ const certificatePath = relative14(projectRoot, result.certificatePath).replaceAll("\\", "/");
23969
24401
  sendTelemetryEvent("mobile:update-code-signing-generated", {
23970
24402
  engine: "expo",
23971
24403
  validityYears: validityYears ?? 10
@@ -24027,7 +24459,7 @@ var provisionMobileUpdate = async (args) => {
24027
24459
  publicKeys: mobile.updates.publicKeys,
24028
24460
  storage: requestedStorage
24029
24461
  });
24030
- console.log(`Provisioned ${requestedStorage === "local" ? "local development" : "durable S3-compatible"} mobile update storage in ${relative13(projectRoot, path).replaceAll("\\", "/")}.`);
24462
+ console.log(`Provisioned ${requestedStorage === "local" ? "local development" : "durable S3-compatible"} mobile update storage in ${relative14(projectRoot, path).replaceAll("\\", "/")}.`);
24031
24463
  if (requestedStorage === "local")
24032
24464
  console.log("Production release checks will require durable storage. Re-run with --storage s3 --force before deploying.");
24033
24465
  else
@@ -24504,7 +24936,7 @@ var buildAndroid = async (args, prepareVersionCode) => {
24504
24936
  const durationMs = Math.round(performance.now() - startedAt);
24505
24937
  console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} Android App Bundle in ${getDurationString(durationMs)}.`);
24506
24938
  console.log(`Artifact: ${release.artifactPath}`);
24507
- console.log(`Metadata: ${join16(release.releaseRoot, "release.json")}`);
24939
+ console.log(`Metadata: ${join17(release.releaseRoot, "release.json")}`);
24508
24940
  return release;
24509
24941
  } finally {
24510
24942
  sendTelemetryEvent("mobile:android-release-build", {
@@ -24623,8 +25055,6 @@ var buildIos = async (args, prepareBuildNumber) => {
24623
25055
  const remoteProfile = requestedRemote !== undefined || process.platform !== "darwin" ? await getAbsoluteRemoteMacProfile(requestedRemote, remoteProfilePath()) : undefined;
24624
25056
  if (process.platform !== "darwin" && !remoteProfile)
24625
25057
  throw new TypeError("iOS release builds require macOS or a paired Remote Mac. Run `absolute mobile pair mac <name> <user@host>`.");
24626
- if (remoteProfile && args.includes("--registered-device-artifact"))
24627
- throw new TypeError("--registered-device-artifact currently requires a local Mac; paired Remote Mac transfer support is not yet available.");
24628
25058
  let success = false;
24629
25059
  const cancellation = remoteProfile ? new AbortController : undefined;
24630
25060
  let stopListeningForCancellation = () => {
@@ -24652,6 +25082,7 @@ var buildIos = async (args, prepareBuildNumber) => {
24652
25082
  },
24653
25083
  ...prepareBuildNumber === undefined ? {} : { prepareBuildNumber },
24654
25084
  project: createAbsoluteRemoteIosDevProject(mobile, projectRoot, remoteProfile),
25085
+ registeredDeviceArtifact: args.includes("--registered-device-artifact"),
24655
25086
  signal: cancellation?.signal
24656
25087
  }) : await buildLocalIosRelease({
24657
25088
  args,
@@ -24665,7 +25096,7 @@ var buildIos = async (args, prepareBuildNumber) => {
24665
25096
  console.log(`Artifact: ${release.artifactPath}`);
24666
25097
  if (release.registeredArtifactPath)
24667
25098
  console.log(`Registered-device artifact: ${release.registeredArtifactPath}`);
24668
- console.log(`Metadata: ${join16(release.releaseRoot, "release.json")}`);
25099
+ console.log(`Metadata: ${join17(release.releaseRoot, "release.json")}`);
24669
25100
  return release;
24670
25101
  } finally {
24671
25102
  stopListeningForCancellation();
@@ -24777,7 +25208,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
24777
25208
  checks.push({
24778
25209
  id: "sync.storage-schema",
24779
25210
  label: `Offline schema ${schema.components.map((component) => `${component.id}@${component.version}`).join(", ")}`,
24780
- path: join16(projectRoot, "package.json"),
25211
+ path: join17(projectRoot, "package.json"),
24781
25212
  platform: "host",
24782
25213
  status: "pass"
24783
25214
  });
@@ -24785,7 +25216,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
24785
25216
  checks.push({
24786
25217
  id: "sync.storage-schema",
24787
25218
  label: "Offline schema metadata is invalid",
24788
- path: join16(projectRoot, "package.json"),
25219
+ path: join17(projectRoot, "package.json"),
24789
25220
  platform: "host",
24790
25221
  remediation: error instanceof Error ? error.message : String(error),
24791
25222
  status: "fail"
@@ -24873,7 +25304,7 @@ var requireAndroidTestPort = (args, projectRoot) => {
24873
25304
  }
24874
25305
  return { https: args.includes("--https"), port };
24875
25306
  }
24876
- const instances = listLiveInstances().filter((instance2) => resolve14(instance2.cwd) === resolve14(projectRoot) && instance2.source === "dev" && instance2.port !== null);
25307
+ const instances = listLiveInstances().filter((instance2) => resolve15(instance2.cwd) === resolve15(projectRoot) && instance2.source === "dev" && instance2.port !== null);
24877
25308
  if (instances.length !== 1) {
24878
25309
  throw new TypeError(instances.length === 0 ? "No running AbsoluteJS dev server was found for this project. Start `bun dev`, wait for Android to report ready, then run `absolute mobile test android`." : "Multiple dev servers are running for this project. Select one with mobile test android --port <port>.");
24879
25310
  }
@@ -24967,8 +25398,8 @@ var selectOrStartAndroidReleaseEmulator = async (adb2, emulator, explicitSerial,
24967
25398
  return waitForBoot();
24968
25399
  };
24969
25400
  var safeArtifactRoot = (projectRoot, value) => {
24970
- const root = resolve14(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
24971
- if (root !== projectRoot && !root.startsWith(`${resolve14(projectRoot)}/`)) {
25401
+ const root = resolve15(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
25402
+ if (root !== projectRoot && !root.startsWith(`${resolve15(projectRoot)}/`)) {
24972
25403
  throw new TypeError("mobile test --artifacts must remain inside the project.");
24973
25404
  }
24974
25405
  return root;
@@ -24996,12 +25427,12 @@ var waitForRequestedAndroidHmr = async (args, session, checks, timeoutMs) => {
24996
25427
  });
24997
25428
  };
24998
25429
  var writeAndroidFailureArtifacts = async (options) => {
24999
- await mkdir15(options.artifactRoot, { recursive: true });
25000
- const screenshot = options.session ? await options.session.screenshot(join16(options.artifactRoot, "android-failure.png")).catch(() => {
25430
+ await mkdir16(options.artifactRoot, { recursive: true });
25431
+ const screenshot = options.session ? await options.session.screenshot(join17(options.artifactRoot, "android-failure.png")).catch(() => {
25001
25432
  return;
25002
25433
  }) : undefined;
25003
- const diagnosticPath = join16(options.artifactRoot, "android-failure.json");
25004
- await writeFile16(diagnosticPath, `${JSON.stringify({
25434
+ const diagnosticPath = join17(options.artifactRoot, "android-failure.json");
25435
+ await writeFile17(diagnosticPath, `${JSON.stringify({
25005
25436
  diagnostics: options.session?.diagnostics ?? [],
25006
25437
  error: options.error instanceof Error ? options.error.message : String(options.error),
25007
25438
  platform: "android",
@@ -25022,7 +25453,7 @@ var captureAndroidReleaseScreenshot = async (adb2, serial, destination) => {
25022
25453
  ]);
25023
25454
  if (exitCode !== 0)
25024
25455
  throw new Error(`Android release screenshot failed: ${stderr.trim() || `status ${exitCode}`}`);
25025
- await writeFile16(destination, bytes);
25456
+ await writeFile17(destination, bytes);
25026
25457
  return destination;
25027
25458
  };
25028
25459
  var printAndroidReleaseAcceptance = (result, json) => {
@@ -25076,7 +25507,7 @@ var testAndroidRelease = async (args, mobile, projectRoot) => {
25076
25507
  release,
25077
25508
  serial
25078
25509
  });
25079
- const screenshot = reportRoot ? await captureAndroidReleaseScreenshot(adb2, serial, join16(reportRoot, "android-release.png")) : undefined;
25510
+ const screenshot = reportRoot ? await captureAndroidReleaseScreenshot(adb2, serial, join17(reportRoot, "android-release.png")) : undefined;
25080
25511
  sendTelemetryEvent("mobile:android-release-conformance", {
25081
25512
  durationMs: result.durationMs,
25082
25513
  engine: result.engine,
@@ -25123,9 +25554,9 @@ var testAndroidRelease = async (args, mobile, projectRoot) => {
25123
25554
  platform: "android",
25124
25555
  success: false
25125
25556
  });
25126
- await mkdir15(artifactRoot, { recursive: true });
25127
- const diagnosticPath = join16(artifactRoot, "android-release-failure.json");
25128
- await writeFile16(diagnosticPath, `${JSON.stringify({
25557
+ await mkdir16(artifactRoot, { recursive: true });
25558
+ const diagnosticPath = join17(artifactRoot, "android-release-failure.json");
25559
+ await writeFile17(diagnosticPath, `${JSON.stringify({
25129
25560
  error: sanitizeNativeReportText(error instanceof Error ? error.message : String(error)),
25130
25561
  platform: "android",
25131
25562
  provider: mobile.engine,
@@ -25219,7 +25650,7 @@ var testAndroid = async (args) => {
25219
25650
  console.log(JSON.stringify(report, null, 2));
25220
25651
  else
25221
25652
  printAndroidTestReport(report);
25222
- const screenshot = reportRoot ? await session.screenshot(join16(artifactRoot, "android-emulator.png")) : undefined;
25653
+ const screenshot = reportRoot ? await session.screenshot(join17(artifactRoot, "android-emulator.png")) : undefined;
25223
25654
  await writeRequestedAndroidReport({
25224
25655
  adb: adb2,
25225
25656
  args,
@@ -25288,14 +25719,14 @@ var requireIosTestContext = (args, projectRoot) => {
25288
25719
  const port = Number(explicit);
25289
25720
  if (!Number.isInteger(port) || port < 1 || port > 65535)
25290
25721
  throw new TypeError("mobile test --port must be a valid TCP port.");
25291
- const instance2 = listLiveInstances().find((candidate) => resolve14(candidate.cwd) === resolve14(projectRoot) && candidate.source === "dev" && candidate.port === port);
25722
+ const instance2 = listLiveInstances().find((candidate) => resolve15(candidate.cwd) === resolve15(projectRoot) && candidate.source === "dev" && candidate.port === port);
25292
25723
  return {
25293
25724
  https: instance2?.https ?? args.includes("--https"),
25294
25725
  instance: instance2,
25295
25726
  port
25296
25727
  };
25297
25728
  }
25298
- const instances = listLiveInstances().filter((instance2) => resolve14(instance2.cwd) === resolve14(projectRoot) && instance2.source === "dev" && instance2.port !== null);
25729
+ const instances = listLiveInstances().filter((instance2) => resolve15(instance2.cwd) === resolve15(projectRoot) && instance2.source === "dev" && instance2.port !== null);
25299
25730
  if (instances.length !== 1)
25300
25731
  throw new TypeError(instances.length === 0 ? "No running AbsoluteJS dev server was found for this project. Start `bun dev`, wait for iOS to report ready, then run `absolute mobile test ios`." : "Multiple dev servers are running for this project. Select one with mobile test ios --port <port>.");
25301
25732
  const [instance] = instances;
@@ -25313,7 +25744,7 @@ var waitForIosHmrClient = async (options) => {
25313
25744
  return;
25314
25745
  });
25315
25746
  const status = response?.ok ? await response.json().catch(() => null) : null;
25316
- const targets = isRecord7(status) && isRecord7(status.connectedTargets) ? status.connectedTargets : undefined;
25747
+ const targets = isRecord8(status) && isRecord8(status.connectedTargets) ? status.connectedTargets : undefined;
25317
25748
  if (targets && typeof targets["capacitor-ios"] === "number" && targets["capacitor-ios"] > 0)
25318
25749
  return;
25319
25750
  await Bun.sleep(100);
@@ -25439,8 +25870,8 @@ var requireCapturedCommand = (command, label) => {
25439
25870
  return result;
25440
25871
  };
25441
25872
  var writeIosFailureArtifacts = async (options) => {
25442
- await mkdir15(options.artifactRoot, { recursive: true });
25443
- const screenshot = join16(options.artifactRoot, "ios-failure.png");
25873
+ await mkdir16(options.artifactRoot, { recursive: true });
25874
+ const screenshot = join17(options.artifactRoot, "ios-failure.png");
25444
25875
  const screenshotResult = captureCommand4([
25445
25876
  options.xcrun,
25446
25877
  "simctl",
@@ -25449,8 +25880,8 @@ var writeIosFailureArtifacts = async (options) => {
25449
25880
  "screenshot",
25450
25881
  screenshot
25451
25882
  ]);
25452
- const diagnosticPath = join16(options.artifactRoot, "ios-failure.json");
25453
- await writeFile16(diagnosticPath, `${JSON.stringify({
25883
+ const diagnosticPath = join17(options.artifactRoot, "ios-failure.json");
25884
+ await writeFile17(diagnosticPath, `${JSON.stringify({
25454
25885
  appId: options.appId,
25455
25886
  error: options.error instanceof Error ? options.error.message : String(options.error),
25456
25887
  platform: "ios",
@@ -25477,8 +25908,8 @@ var nativeReportRoot = (args, projectRoot, platform2) => {
25477
25908
  var absolutejsVersionForReport = async () => {
25478
25909
  let absolutejsVersion = process.env.ABSOLUTE_VERSION ?? "unknown";
25479
25910
  const versions = await Promise.all([
25480
- resolve14(import.meta.dir, "..", "..", "package.json"),
25481
- resolve14(import.meta.dir, "..", "..", "..", "package.json")
25911
+ resolve15(import.meta.dir, "..", "..", "package.json"),
25912
+ resolve15(import.meta.dir, "..", "..", "..", "package.json")
25482
25913
  ].map((candidate) => readPackageVersionForIosReport(candidate).catch(() => "unknown")));
25483
25914
  for (const version of versions) {
25484
25915
  if (version === "unknown")
@@ -25655,7 +26086,7 @@ var printIosReleaseAcceptance = (result, json) => {
25655
26086
  console.log(`\u2713 Embedded local content launched in ${getDurationString(result.launchMs)} and relaunched in ${getDurationString(result.relaunchMs)}.`);
25656
26087
  console.log(result.networkUnavailable === "user-confirmed" ? "\u2713 Physical-device network-unavailable state was explicitly confirmed." : "\u2139 Simulator evidence does not claim physical-device offline behavior.");
25657
26088
  };
25658
- var iosReleaseRun = (result, appId, targetId) => ({
26089
+ var iosReleaseRun = (result, appId, targetId, remote) => ({
25659
26090
  appId,
25660
26091
  durationMs: result.durationMs,
25661
26092
  hmrConnected: false,
@@ -25671,6 +26102,7 @@ var iosReleaseRun = (result, appId, targetId) => ({
25671
26102
  networkUnavailable: result.networkUnavailable,
25672
26103
  relaunchMs: result.relaunchMs,
25673
26104
  releaseId: result.releaseId,
26105
+ remote,
25674
26106
  signed: result.signed
25675
26107
  },
25676
26108
  status: "pass",
@@ -25704,14 +26136,56 @@ var runIosSimulatorReleaseTarget = async (options) => {
25704
26136
  });
25705
26137
  return { result, targetId: simulator.udid };
25706
26138
  };
26139
+ var runRemoteIosReleaseTarget = async (options) => {
26140
+ const physical = options.requestedDevice !== undefined;
26141
+ const confirmed = !physical || options.args.includes("--yes") || await confirmInstall("On the selected iPhone, enable Airplane Mode and then disable Wi-Fi in Settings. Confirm both are still disabled and continue with two release launches on the Remote Mac?");
26142
+ if (!confirmed)
26143
+ throw new TypeError("Physical iOS offline acceptance was cancelled because network-unavailable state was not confirmed.");
26144
+ let distribution = "simulator-release";
26145
+ if (options.requestedDevice)
26146
+ distribution = "registered-device";
26147
+ if (options.args.includes("--testflight"))
26148
+ distribution = "testflight";
26149
+ const simulatorUdid = valueAfter(options.args, "--udid") ?? valueAfter(options.args, "--serial");
26150
+ const remoteAcceptance = await runAbsoluteRemoteIosReleaseAcceptance({
26151
+ ...options.requestedDevice ? {
26152
+ deviceIdentifier: normalizeAbsoluteIosDeviceIdentifier(options.requestedDevice),
26153
+ networkUnavailableConfirmed: true
26154
+ } : {},
26155
+ distribution,
26156
+ project: createAbsoluteRemoteIosDevProject(options.mobile, options.projectRoot, options.profile),
26157
+ release: options.release,
26158
+ signal: options.cancellation?.signal,
26159
+ log: (message) => console.log(`[remote] ${message}`),
26160
+ onPhaseTiming: ({ durationMs, phase }) => {
26161
+ console.log(`[mobile:ios-release-test] ${phase} ${getDurationString(durationMs)}`);
26162
+ sendTelemetryEvent("mobile:ios-release-test-phase", {
26163
+ durationMs: Math.round(durationMs),
26164
+ engine: options.mobile.engine,
26165
+ phase,
26166
+ platform: "ios",
26167
+ provider: "remote-mac"
26168
+ });
26169
+ },
26170
+ ...simulatorUdid ? { simulatorUdid } : {}
26171
+ });
26172
+ return {
26173
+ result: remoteAcceptance.result,
26174
+ targetId: remoteAcceptance.targetId
26175
+ };
26176
+ };
25707
26177
  var testIosRelease = async (args, mobile, projectRoot) => {
25708
26178
  const requested = valueAfter(args, "--release");
25709
26179
  if (!requested || requested.startsWith("--"))
25710
26180
  throw new TypeError("mobile test ios --release requires a release directory or release.json path.");
25711
26181
  if (!mobile.platforms.includes("ios"))
25712
26182
  throw new TypeError("mobile test ios requires ios in mobile.platforms.");
25713
- if (args.includes("--remote"))
25714
- throw new TypeError("Installed iOS release acceptance currently runs on a local Mac. Remote Mac acceptance will be added to the paired-host protocol.");
26183
+ const requestedRemote = valueAfter(args, "--remote");
26184
+ if (args.includes("--remote") && (!requestedRemote || requestedRemote.startsWith("-")))
26185
+ throw new TypeError("mobile test ios --release requires --remote <name>.");
26186
+ const remoteProfile = requestedRemote !== undefined || process.platform !== "darwin" ? await getAbsoluteRemoteMacProfile(requestedRemote, remoteProfilePath()) : undefined;
26187
+ if (process.platform !== "darwin" && !remoteProfile)
26188
+ throw new TypeError("iOS release acceptance requires macOS or a paired Remote Mac. Run `absolute mobile pair mac <name> <user@host>`.");
25715
26189
  const release = await readAbsoluteIosRelease(projectRoot, requested);
25716
26190
  if (release.metadata.appId !== mobile.appId)
25717
26191
  throw new TypeError(`iOS release app ID ${release.metadata.appId} does not match mobile.appId ${mobile.appId}.`);
@@ -25726,24 +26200,59 @@ var testIosRelease = async (args, mobile, projectRoot) => {
25726
26200
  throw new TypeError("mobile test ios --release --testflight requires --device.");
25727
26201
  if (requestedDevice && (valueAfter(args, "--udid") || valueAfter(args, "--serial")))
25728
26202
  throw new TypeError("mobile test ios --release --device cannot be combined with a simulator selector.");
25729
- const xcrun = await requireIosXcrun();
26203
+ const xcrun = remoteProfile ? undefined : await requireIosXcrun();
25730
26204
  const reportRoot = nativeReportRoot(args, projectRoot, "ios");
25731
26205
  const artifactRoot = reportRoot ?? safeArtifactRoot(projectRoot, valueAfter(args, "--artifacts") ?? `.absolutejs/mobile/test-artifacts/${release.metadata.releaseId}`);
25732
26206
  const startedAt = performance.now();
25733
26207
  let targetId = requestedDevice ?? "ios-simulator";
26208
+ let remoteReportMetadata;
26209
+ if (remoteProfile && reportRoot) {
26210
+ const macos = await captureAbsoluteRemoteMacCommand(remoteProfile, [
26211
+ "/usr/bin/sw_vers",
26212
+ "-productVersion"
26213
+ ]);
26214
+ if (macos.exitCode !== 0)
26215
+ throw new Error("Remote Mac version inspection failed.");
26216
+ remoteReportMetadata = {
26217
+ absolutejsVersion: await absolutejsVersionForReport(),
26218
+ bunVersion: Bun.version,
26219
+ macosVersion: macos.stdout.trim(),
26220
+ xcodeVersion: remoteProfile.xcodeVersion
26221
+ };
26222
+ }
26223
+ const cancellation = remoteProfile ? new AbortController : undefined;
26224
+ const stopListeningForCancellation = remoteProfile ? listenForRemoteReleaseCancellation(cancellation) : () => {
26225
+ return;
26226
+ };
25734
26227
  try {
25735
- const acceptance = requestedDevice ? await runIosPhysicalReleaseTarget({
25736
- args,
25737
- device: requestedDevice,
25738
- release,
25739
- xcrun
25740
- }) : await runIosSimulatorReleaseTarget({
25741
- args,
25742
- artifactRoot,
25743
- mobile,
25744
- release,
25745
- xcrun
25746
- });
26228
+ let acceptance;
26229
+ if (remoteProfile)
26230
+ acceptance = await runRemoteIosReleaseTarget({
26231
+ args,
26232
+ ...cancellation ? { cancellation } : {},
26233
+ mobile,
26234
+ profile: remoteProfile,
26235
+ projectRoot,
26236
+ release,
26237
+ ...requestedDevice ? { requestedDevice } : {}
26238
+ });
26239
+ else if (!xcrun)
26240
+ throw new Error("Local iOS release acceptance requires xcrun.");
26241
+ else if (requestedDevice)
26242
+ acceptance = await runIosPhysicalReleaseTarget({
26243
+ args,
26244
+ device: requestedDevice,
26245
+ release,
26246
+ xcrun
26247
+ });
26248
+ else
26249
+ acceptance = await runIosSimulatorReleaseTarget({
26250
+ args,
26251
+ artifactRoot,
26252
+ mobile,
26253
+ release,
26254
+ xcrun
26255
+ });
25747
26256
  const { result, targetId: acceptedTargetId } = acceptance;
25748
26257
  targetId = acceptedTargetId;
25749
26258
  sendTelemetryEvent("mobile:ios-release-conformance", {
@@ -25755,14 +26264,16 @@ var testIosRelease = async (args, mobile, projectRoot) => {
25755
26264
  launchMs: result.launchMs,
25756
26265
  platform: "ios",
25757
26266
  relaunchMs: result.relaunchMs,
26267
+ remote: remoteProfile !== undefined,
25758
26268
  success: true
25759
26269
  });
25760
26270
  await writeRequestedIosReport({
25761
26271
  args,
26272
+ ...remoteReportMetadata ? { metadata: remoteReportMetadata } : {},
25762
26273
  projectRoot,
25763
26274
  provider: mobile.engine,
25764
- run: iosReleaseRun(result, mobile.appId, targetId),
25765
- xcrun
26275
+ run: iosReleaseRun(result, mobile.appId, targetId, remoteProfile !== undefined),
26276
+ ...xcrun ? { xcrun } : {}
25766
26277
  });
25767
26278
  printIosReleaseAcceptance(result, args.includes("--json"));
25768
26279
  return result;
@@ -25772,11 +26283,12 @@ var testIosRelease = async (args, mobile, projectRoot) => {
25772
26283
  durationMs,
25773
26284
  engine: mobile.engine,
25774
26285
  platform: "ios",
26286
+ remote: remoteProfile !== undefined,
25775
26287
  success: false
25776
26288
  });
25777
- await mkdir15(artifactRoot, { recursive: true });
25778
- const diagnosticPath = join16(artifactRoot, "ios-release-failure.json");
25779
- await writeFile16(diagnosticPath, `${JSON.stringify({
26289
+ await mkdir16(artifactRoot, { recursive: true });
26290
+ const diagnosticPath = join17(artifactRoot, "ios-release-failure.json");
26291
+ await writeFile17(diagnosticPath, `${JSON.stringify({
25780
26292
  error: sanitizeIosReportText(error instanceof Error ? error.message : String(error)),
25781
26293
  platform: "ios",
25782
26294
  provider: mobile.engine,
@@ -25787,6 +26299,7 @@ var testIosRelease = async (args, mobile, projectRoot) => {
25787
26299
  if (reportRoot)
25788
26300
  await writeRequestedIosReport({
25789
26301
  args,
26302
+ ...remoteReportMetadata ? { metadata: remoteReportMetadata } : {},
25790
26303
  projectRoot,
25791
26304
  provider: mobile.engine,
25792
26305
  run: {
@@ -25798,9 +26311,11 @@ var testIosRelease = async (args, mobile, projectRoot) => {
25798
26311
  targetId,
25799
26312
  targetKind: requestedDevice ? "device" : "simulator"
25800
26313
  },
25801
- xcrun
26314
+ ...xcrun ? { xcrun } : {}
25802
26315
  });
25803
26316
  throw new Error(`${error instanceof Error ? error.message : String(error)} Failure diagnostics: ${diagnosticPath}`, { cause: error });
26317
+ } finally {
26318
+ stopListeningForCancellation();
25804
26319
  }
25805
26320
  };
25806
26321
  var testIos = async (args) => {
@@ -25866,8 +26381,8 @@ var testIos = async (args) => {
25866
26381
  mobile.appId
25867
26382
  ], "iOS app launch");
25868
26383
  await waitForIosHmrClient({ https, port, timeoutMs });
25869
- await mkdir15(artifactRoot, { recursive: true });
25870
- const screenshot = join16(artifactRoot, "ios-simulator.png");
26384
+ await mkdir16(artifactRoot, { recursive: true });
26385
+ const screenshot = join17(artifactRoot, "ios-simulator.png");
25871
26386
  requireCapturedCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
25872
26387
  const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
25873
26388
  const report = {
@@ -25951,6 +26466,110 @@ var testIos = async (args) => {
25951
26466
  throw new Error(`${error instanceof Error ? error.message : String(error)} Failure diagnostics: ${diagnosticPath}${screenshot ? `; screenshot: ${screenshot}` : ""}`, { cause: error });
25952
26467
  }
25953
26468
  };
26469
+ var readCertifiableMobileRelease = async (projectRoot, requested) => {
26470
+ let androidError;
26471
+ try {
26472
+ return await readAbsoluteAndroidRelease(projectRoot, requested);
26473
+ } catch (error) {
26474
+ androidError = error;
26475
+ }
26476
+ try {
26477
+ return await readAbsoluteIosRelease(projectRoot, requested);
26478
+ } catch (iosError) {
26479
+ throw new TypeError(`Unable to read an immutable Android or iOS release from ${requested}. Android: ${androidError instanceof Error ? androidError.message : String(androidError)} iOS: ${iosError instanceof Error ? iosError.message : String(iosError)}`, { cause: iosError });
26480
+ }
26481
+ };
26482
+ var mobileCertificationRequirement = (value) => {
26483
+ if (value === undefined)
26484
+ return;
26485
+ if (value !== "installed" && value !== "simulator" && value !== "device" && value !== "store")
26486
+ throw new TypeError("mobile certify --require must be installed, simulator, device, or store.");
26487
+ return value;
26488
+ };
26489
+ var mobileCertificationValue = (args, flag) => {
26490
+ const value = valueAfter(args, flag);
26491
+ if (args.includes(flag) && (!value || value.startsWith("--")))
26492
+ throw new TypeError(`mobile certify ${flag} requires a value.`);
26493
+ return value;
26494
+ };
26495
+ var verifyMobileCertification = async (options) => {
26496
+ if (valuesAfter(options.args, "--evidence").length > 0)
26497
+ throw new TypeError("mobile certify --verify cannot be combined with --evidence.");
26498
+ const loaded = await readAbsoluteMobileReleaseCertification(options.projectRoot, options.verifyPath);
26499
+ const certification = verifyAbsoluteMobileReleaseCertification(loaded.certification, options.release, options.requirement);
26500
+ sendTelemetryEvent("mobile:release-certification", {
26501
+ durationMs: Math.round(performance.now() - options.startedAt),
26502
+ engine: options.release.metadata.engine,
26503
+ mode: "verify",
26504
+ platform: options.release.metadata.platform,
26505
+ requirement: options.requirement ?? certification.requirement,
26506
+ strength: certification.strength,
26507
+ success: true
26508
+ });
26509
+ if (options.args.includes("--json"))
26510
+ console.log(JSON.stringify(certification, null, 2));
26511
+ else
26512
+ console.log(`\u2713 ${certification.strength} certification ${certification.certificationId} still matches ${certification.release.releaseId}.`);
26513
+ return certification;
26514
+ };
26515
+ var createMobileCertification = async (options) => {
26516
+ const evidencePaths = valuesAfter(options.args, "--evidence");
26517
+ const certification = await createAbsoluteMobileReleaseCertification({
26518
+ evidencePaths,
26519
+ projectRoot: options.projectRoot,
26520
+ release: options.release,
26521
+ ...options.requirement ? { requirement: options.requirement } : {}
26522
+ });
26523
+ const output = await writeAbsoluteMobileReleaseCertification(options.projectRoot, certification, mobileCertificationValue(options.args, "--outdir"));
26524
+ sendTelemetryEvent("mobile:release-certification", {
26525
+ durationMs: Math.round(performance.now() - options.startedAt),
26526
+ engine: options.release.metadata.engine,
26527
+ evidenceCount: certification.evidence.length,
26528
+ mode: "create",
26529
+ platform: options.release.metadata.platform,
26530
+ requirement: certification.requirement,
26531
+ strength: certification.strength,
26532
+ success: true
26533
+ });
26534
+ if (options.args.includes("--json"))
26535
+ console.log(JSON.stringify(certification, null, 2));
26536
+ else {
26537
+ console.log(`\u2713 Certified immutable ${certification.release.platform} release ${certification.release.releaseId} at ${certification.strength} strength.`);
26538
+ console.log(`Certification: ${output.jsonPath}`);
26539
+ console.log(`Summary: ${output.markdownPath}`);
26540
+ }
26541
+ return certification;
26542
+ };
26543
+ var certifyMobileRelease = async (args) => {
26544
+ const [requested] = args;
26545
+ if (!requested || requested.startsWith("--"))
26546
+ throw new TypeError("mobile certify requires a release directory or release.json path.");
26547
+ const projectRoot = process.cwd();
26548
+ const release = await readCertifiableMobileRelease(projectRoot, requested);
26549
+ const requirement = mobileCertificationRequirement(mobileCertificationValue(args, "--require"));
26550
+ const verifyPath = mobileCertificationValue(args, "--verify");
26551
+ const startedAt = performance.now();
26552
+ try {
26553
+ const options = {
26554
+ args,
26555
+ projectRoot,
26556
+ release,
26557
+ ...requirement ? { requirement } : {},
26558
+ startedAt
26559
+ };
26560
+ return verifyPath ? await verifyMobileCertification({ ...options, verifyPath }) : await createMobileCertification(options);
26561
+ } catch (error) {
26562
+ sendTelemetryEvent("mobile:release-certification", {
26563
+ durationMs: Math.round(performance.now() - startedAt),
26564
+ engine: release.metadata.engine,
26565
+ mode: verifyPath ? "verify" : "create",
26566
+ platform: release.metadata.platform,
26567
+ ...requirement ? { requirement } : {},
26568
+ success: false
26569
+ });
26570
+ throw error;
26571
+ }
26572
+ };
25954
26573
  var runMobile = async (args) => {
25955
26574
  const [command] = args;
25956
26575
  if (command === "pair") {
@@ -25989,6 +26608,10 @@ var runMobile = async (args) => {
25989
26608
  await inspectMobile(args.slice(1));
25990
26609
  return;
25991
26610
  }
26611
+ if (command === "certify") {
26612
+ await certifyMobileRelease(args.slice(1));
26613
+ return;
26614
+ }
25992
26615
  if (command === "test" && args[1] === "android") {
25993
26616
  await testAndroid(args.slice(2));
25994
26617
  return;
@@ -26053,7 +26676,7 @@ var runMobile = async (args) => {
26053
26676
  await publishIos(args.slice(2));
26054
26677
  return;
26055
26678
  }
26056
- throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [inspect [name] [--json] | clean [name] --yes | --json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] [--require-bundle] | associations [--outdir dir] [--verify] | ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--remote name] [--registered-device-artifact] [--outdir dir] [--web-outdir dir] [--unsigned] | update provision [--storage local|s3] [--registry module] [--force] [--yes] | update signing generate --private-key path [--certificate path] [--public-key path] [--key-id id] [--common-name name] [--validity-years n] | update build [server-entry] --classification bug-fix|content|security --key-id id --signing-key path --within-submitted-purpose [--outdir dir] [--web-outdir dir] | update publish <release-directory> [--rollout fraction] [--registry module] | update promote --release id --rollout fraction [--registry module] | update rollback [--release id] [--registry module] | update status [--registry module] [--json] | update advance [--rollout fraction] [--registry module] [--json] | update pause|resume|cancel|reconcile [--registry module] [--json] | update storage [--retain count] [--min-age-days days] [--registry module] [--json] | update gc [--retain count] [--min-age-days days] [--grace-days days] [--apply] [--registry module] [--json] | publish android [server-entry] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--remote name] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--release release-dir [--yes] | --route path [--wait-for-hmr] [--port n]] [--report [dir]] [--serial id] [--artifacts dir] [--json] [--config path] | test ios [--release release-dir [--device id [--testflight] --yes] | --wait-for-hmr] [--report [dir]] [--udid id] [--artifacts dir] [--json] [--config path]");
26679
+ throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [inspect [name] [--json] | clean [name] --yes | --json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] [--require-bundle] | certify <release-dir> [--evidence report-dir]... [--require installed|simulator|device|store] [--outdir dir] [--json] | certify <release-dir> --verify certification-dir [--require installed|simulator|device|store] [--json] | associations [--outdir dir] [--verify] | ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--remote name] [--registered-device-artifact] [--outdir dir] [--web-outdir dir] [--unsigned] | update provision [--storage local|s3] [--registry module] [--force] [--yes] | update signing generate --private-key path [--certificate path] [--public-key path] [--key-id id] [--common-name name] [--validity-years n] | update build [server-entry] --classification bug-fix|content|security --key-id id --signing-key path --within-submitted-purpose [--outdir dir] [--web-outdir dir] | update publish <release-directory> [--rollout fraction] [--registry module] | update promote --release id --rollout fraction [--registry module] | update rollback [--release id] [--registry module] | update status [--registry module] [--json] | update advance [--rollout fraction] [--registry module] [--json] | update pause|resume|cancel|reconcile [--registry module] [--json] | update storage [--retain count] [--min-age-days days] [--registry module] [--json] | update gc [--retain count] [--min-age-days days] [--grace-days days] [--apply] [--registry module] [--json] | publish android [server-entry] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--remote name] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--release release-dir [--yes] | --route path [--wait-for-hmr] [--port n]] [--report [dir]] [--serial id] [--artifacts dir] [--json] [--config path] | test ios [--release release-dir [--remote name] [--device id [--testflight] --yes] | --wait-for-hmr] [--report [dir]] [--udid id] [--artifacts dir] [--json] [--config path]");
26057
26680
  };
26058
26681
  export {
26059
26682
  runMobile