@absolutejs/absolute 0.20.0-beta.106 → 0.20.0-beta.108

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.
@@ -4415,9 +4415,9 @@ var require_sha256 = __commonJS(function(exports, module) {
4415
4415
  var forge = require_forge();
4416
4416
  require_md();
4417
4417
  require_util();
4418
- var sha2564 = module.exports = forge.sha256 = forge.sha256 || {};
4419
- forge.md.sha256 = forge.md.algorithms.sha256 = sha2564;
4420
- 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() {
4421
4421
  if (!_initialized) {
4422
4422
  _init();
4423
4423
  }
@@ -16779,15 +16779,15 @@ var require_main = __commonJS(function(exports) {
16779
16779
 
16780
16780
  // src/cli/scripts/mobile.ts
16781
16781
  import {
16782
- access as access14,
16783
- mkdir as mkdir15,
16784
- mkdtemp as mkdtemp5,
16785
- readFile as readFile19,
16786
- rm as rm8,
16787
- 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
16788
16788
  } from "fs/promises";
16789
16789
  import { createPublicKey as createPublicKey2 } from "crypto";
16790
- 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";
16791
16791
  import { createInterface } from "readline/promises";
16792
16792
 
16793
16793
  // src/mobile/nativeDeepLinks.ts
@@ -22170,9 +22170,413 @@ var createAbsoluteAndroidTestReport = (options) => {
22170
22170
  });
22171
22171
  };
22172
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
+
22173
22577
  // src/mobile/releasePublisher.ts
22174
- import { access as access8 } from "fs/promises";
22175
- 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";
22176
22580
  import { pathToFileURL as pathToFileURL2 } from "url";
22177
22581
  var prepareAbsoluteIosRelease = async (publisher, options) => {
22178
22582
  if (typeof publisher.prepareIosRelease !== "function") {
@@ -22195,24 +22599,24 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
22195
22599
  }
22196
22600
  return versionCode;
22197
22601
  };
22198
- var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
22199
- 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";
22200
22604
  var publisherModulePath = (projectRoot, requested) => {
22201
- const root = resolve8(projectRoot);
22202
- const path = resolve8(root, requested);
22203
- const projectRelative = relative7(root, path);
22204
- 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)) {
22205
22609
  throw new TypeError("mobile publish --registry must remain inside the project.");
22206
22610
  }
22207
22611
  return path;
22208
22612
  };
22209
22613
  var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
22210
22614
  const modulePath = publisherModulePath(projectRoot, requestedModulePath);
22211
- await access8(modulePath).catch(() => {
22615
+ await access9(modulePath).catch(() => {
22212
22616
  throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
22213
22617
  });
22214
22618
  const loaded = await import(pathToFileURL2(modulePath).href);
22215
- const publisher = isRecord6(loaded) ? loaded.default ?? loaded.registry : undefined;
22619
+ const publisher = isRecord7(loaded) ? loaded.default ?? loaded.registry : undefined;
22216
22620
  if (!isPublisher(publisher)) {
22217
22621
  throw new TypeError("Native release registry module must default-export a registry with publish(options).");
22218
22622
  }
@@ -22270,8 +22674,8 @@ var publishAbsoluteIosRelease = async (options) => {
22270
22674
  };
22271
22675
 
22272
22676
  // src/mobile/mobileInspect.ts
22273
- import { access as access9, readFile as readFile15 } from "fs/promises";
22274
- 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";
22275
22679
  var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1;
22276
22680
  var MOBILE_PACKAGE_NAMES = new Set([
22277
22681
  "@absolutejs/absolute",
@@ -22286,19 +22690,19 @@ var MOBILE_PACKAGE_NAMES = new Set([
22286
22690
  ]);
22287
22691
  var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
22288
22692
  var portablePath2 = (projectRoot, path) => {
22289
- const value = relative8(resolve9(projectRoot), resolve9(path)).replaceAll("\\", "/");
22693
+ const value = relative9(resolve10(projectRoot), resolve10(path)).replaceAll("\\", "/");
22290
22694
  return value || ".";
22291
22695
  };
22292
22696
  var pathExists4 = async (path) => {
22293
22697
  try {
22294
- await access9(path);
22698
+ await access10(path);
22295
22699
  return true;
22296
22700
  } catch {
22297
22701
  return false;
22298
22702
  }
22299
22703
  };
22300
22704
  var readObject2 = async (path) => {
22301
- const value = JSON.parse(await readFile15(path, "utf8"));
22705
+ const value = JSON.parse(await readFile16(path, "utf8"));
22302
22706
  if (!isObject2(value))
22303
22707
  throw new TypeError("JSON root must be an object.");
22304
22708
  return value;
@@ -22310,13 +22714,13 @@ var addPackageDeclarations = (declarations, value) => {
22310
22714
  declarations.set(name, declared);
22311
22715
  };
22312
22716
  var packageInspections = async (projectRoot, additionalNames) => {
22313
- const project = await readObject2(join13(projectRoot, "package.json"));
22717
+ const project = await readObject2(join14(projectRoot, "package.json"));
22314
22718
  const declarations = new Map;
22315
22719
  for (const field2 of ["dependencies", "devDependencies"])
22316
22720
  addPackageDeclarations(declarations, project[field2]);
22317
22721
  const names = [...new Set([...declarations.keys(), ...additionalNames])].filter((name) => MOBILE_PACKAGE_NAMES.has(name) || name.startsWith("@capacitor/") || additionalNames.includes(name)).sort();
22318
22722
  return Promise.all(names.map(async (name) => {
22319
- 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(() => {
22320
22724
  return;
22321
22725
  });
22322
22726
  const installed = installedManifest?.version;
@@ -22375,7 +22779,7 @@ var inspectAbsoluteMobileProject = async (config, projectRoot, options = {}) =>
22375
22779
  },
22376
22780
  format: ABSOLUTE_MOBILE_INSPECTION_FORMAT,
22377
22781
  nativeProjects: await Promise.all(config.platforms.map(async (platform) => {
22378
- const path = join13(config.nativeProjectDirectory, platform);
22782
+ const path = join14(config.nativeProjectDirectory, platform);
22379
22783
  return {
22380
22784
  initialized: await pathExists4(path),
22381
22785
  path: portablePath2(projectRoot, path),
@@ -22432,8 +22836,8 @@ var renderAbsoluteMobileProjectInspection = (report) => {
22432
22836
 
22433
22837
  // src/mobile/ciWorkflow.ts
22434
22838
  import { existsSync } from "fs";
22435
- import { access as access10, mkdir as mkdir11, readFile as readFile16, writeFile as writeFile12 } from "fs/promises";
22436
- 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";
22437
22841
  var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 1;
22438
22842
  var SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
22439
22843
  var CI_ENV_INDENTATION = 6;
@@ -22452,9 +22856,9 @@ var RESERVED_SECRET_NAMES = new Set([
22452
22856
  "APP_STORE_CONNECT_KEY_ID",
22453
22857
  "APP_STORE_CONNECT_PRIVATE_KEY_BASE64"
22454
22858
  ]);
22455
- var exists4 = async (path) => {
22859
+ var exists5 = async (path) => {
22456
22860
  try {
22457
- await access10(path);
22861
+ await access11(path);
22458
22862
  return true;
22459
22863
  } catch {
22460
22864
  return false;
@@ -22462,10 +22866,10 @@ var exists4 = async (path) => {
22462
22866
  };
22463
22867
  var yamlString = (value) => `'${value.replaceAll("'", "''")}'`;
22464
22868
  var projectPath2 = (projectRoot, value, field2, options = {}) => {
22465
- const root = resolve10(projectRoot);
22466
- const path = resolve10(root, value);
22467
- const portable = relative9(root, path).replaceAll("\\", "/");
22468
- 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 === "") {
22469
22873
  throw new TypeError(`${field2} must remain inside the project root.`);
22470
22874
  }
22471
22875
  if (/\r|\n/u.test(portable) || portable.startsWith("-"))
@@ -22475,11 +22879,11 @@ var projectPath2 = (projectRoot, value, field2, options = {}) => {
22475
22879
  return portable;
22476
22880
  };
22477
22881
  var workflowOutputPath = (projectRoot, value) => {
22478
- const root = resolve10(projectRoot);
22479
- const workflows = resolve10(root, ".github/workflows");
22480
- const path = resolve10(root, value ?? ".github/workflows/absolute-mobile.yml");
22481
- const portable = relative9(workflows, path);
22482
- 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") {
22483
22887
  throw new TypeError("mobile ci github --output must be a .yml or .yaml file inside .github/workflows.");
22484
22888
  }
22485
22889
  return path;
@@ -22908,12 +23312,12 @@ ${bundleAuditSteps}${platforms.includes("android") ? androidJob({ customSecrets,
22908
23312
  var writeAbsoluteMobileGithubWorkflow = async (options) => {
22909
23313
  const path = workflowOutputPath(options.projectRoot, options.outputPath);
22910
23314
  const generated = createAbsoluteMobileGithubWorkflow(options);
22911
- const previous = await exists4(path) ? await readFile16(path, "utf8") : undefined;
23315
+ const previous = await exists5(path) ? await readFile17(path, "utf8") : undefined;
22912
23316
  if (previous !== undefined && previous !== generated.workflow && !options.force)
22913
- 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.`);
22914
23318
  if (previous !== generated.workflow) {
22915
- await mkdir11(dirname10(path), { recursive: true });
22916
- await writeFile12(path, generated.workflow);
23319
+ await mkdir12(dirname10(path), { recursive: true });
23320
+ await writeFile13(path, generated.workflow);
22917
23321
  }
22918
23322
  return {
22919
23323
  changed: previous !== generated.workflow,
@@ -22926,46 +23330,46 @@ var writeAbsoluteMobileGithubWorkflow = async (options) => {
22926
23330
  };
22927
23331
 
22928
23332
  // src/mobile/updateSigning.ts
22929
- import { createHash as createHash5, sign, verify } from "crypto";
23333
+ import { createHash as createHash6, sign, verify } from "crypto";
22930
23334
  import {
22931
23335
  cp,
22932
- mkdir as mkdir12,
22933
- mkdtemp as mkdtemp4,
23336
+ mkdir as mkdir13,
23337
+ mkdtemp as mkdtemp5,
22934
23338
  readdir as readdir6,
22935
- readFile as readFile17,
22936
- rename as rename10,
22937
- rm as rm7,
23339
+ readFile as readFile18,
23340
+ rename as rename11,
23341
+ rm as rm8,
22938
23342
  stat as stat6,
22939
- writeFile as writeFile13
23343
+ writeFile as writeFile14
22940
23344
  } from "fs/promises";
22941
- 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";
22942
23346
  var UPDATE_MANIFEST_FILE = "update.json";
22943
23347
  var UPDATE_FILES_DIRECTORY = "files";
22944
- var sha2563 = (value) => createHash5("sha256").update(value).digest("hex");
23348
+ var sha2564 = (value) => createHash6("sha256").update(value).digest("hex");
22945
23349
  var listFiles = async (root, directory = root) => {
22946
23350
  const entries = await readdir6(directory, { withFileTypes: true });
22947
23351
  const paths = await Promise.all(entries.map(async (entry) => {
22948
- const path = join14(directory, entry.name);
23352
+ const path = join15(directory, entry.name);
22949
23353
  if (entry.isDirectory())
22950
23354
  return listFiles(root, path);
22951
23355
  if (!entry.isFile())
22952
23356
  throw new TypeError("Mobile updates cannot contain links or special files.");
22953
- return [relative10(root, path).replaceAll("\\", "/")];
23357
+ return [relative11(root, path).replaceAll("\\", "/")];
22954
23358
  }));
22955
23359
  return paths.flat().sort((left, right) => left.localeCompare(right));
22956
23360
  };
22957
23361
  var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
22958
- const source = join14(root, path);
23362
+ const source = join15(root, path);
22959
23363
  const [metadata, contents] = await Promise.all([
22960
23364
  stat6(source),
22961
- readFile17(source)
23365
+ readFile18(source)
22962
23366
  ]);
22963
- return { bytes: metadata.size, path, sha256: sha2563(contents) };
23367
+ return { bytes: metadata.size, path, sha256: sha2564(contents) };
22964
23368
  }));
22965
- var releaseIdFor = (value) => `amu_${sha2563(canonicalizeAbsoluteMobileUpdate(value))}`;
23369
+ var releaseIdFor = (value) => `amu_${sha2564(canonicalizeAbsoluteMobileUpdate(value))}`;
22966
23370
  var buildAbsoluteMobileUpdate = async (options) => {
22967
- const bundleDirectory = resolve11(options.bundleDirectory);
22968
- const outputRoot = resolve11(options.outputDirectory);
23371
+ const bundleDirectory = resolve12(options.bundleDirectory);
23372
+ const outputRoot = resolve12(options.outputDirectory);
22969
23373
  if (outputRoot === bundleDirectory || outputRoot.startsWith(`${bundleDirectory}/`))
22970
23374
  throw new TypeError("Mobile update output must be outside the embedded bundle.");
22971
23375
  const paths = await listFiles(bundleDirectory);
@@ -22993,30 +23397,30 @@ var buildAbsoluteMobileUpdate = async (options) => {
22993
23397
  value: signature.toString("base64")
22994
23398
  }
22995
23399
  });
22996
- await mkdir12(outputRoot, { recursive: true });
22997
- const outputDirectory = join14(outputRoot, manifest.releaseId);
22998
- 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-"));
22999
23403
  try {
23000
- await cp(bundleDirectory, join14(staging, UPDATE_FILES_DIRECTORY), {
23404
+ await cp(bundleDirectory, join15(staging, UPDATE_FILES_DIRECTORY), {
23001
23405
  force: true,
23002
23406
  recursive: true
23003
23407
  });
23004
- 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")}
23005
23409
  `);
23006
- await rename10(staging, outputDirectory);
23410
+ await rename11(staging, outputDirectory);
23007
23411
  } catch (error) {
23008
- await rm7(staging, { force: true, recursive: true });
23412
+ await rm8(staging, { force: true, recursive: true });
23009
23413
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
23010
23414
  throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
23011
23415
  throw error;
23012
23416
  }
23013
23417
  return {
23014
23418
  manifest,
23015
- manifestPath: join14(outputDirectory, UPDATE_MANIFEST_FILE),
23419
+ manifestPath: join15(outputDirectory, UPDATE_MANIFEST_FILE),
23016
23420
  outputDirectory
23017
23421
  };
23018
23422
  };
23019
- 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")));
23020
23424
  var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
23021
23425
  const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
23022
23426
  const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
@@ -23026,8 +23430,8 @@ var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
23026
23430
  };
23027
23431
 
23028
23432
  // src/mobile/expoUpdate.ts
23029
- import { access as access11, mkdir as mkdir13, readFile as readFile18, writeFile as writeFile14 } from "fs/promises";
23030
- 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";
23031
23435
  var ABSOLUTE_EXPO_UPDATE_DESCRIPTOR = "_absolute/expo-update.json";
23032
23436
  var ABSOLUTE_EXPO_UPDATE_FORMAT = 1;
23033
23437
  var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
@@ -23093,7 +23497,7 @@ var parseAbsoluteExpoUpdateDescriptor = (value) => {
23093
23497
  };
23094
23498
  };
23095
23499
  var finalizeAbsoluteExpoUpdateExport = async (options) => {
23096
- 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"));
23097
23501
  if (!object2(metadata) || !object2(metadata.fileMetadata))
23098
23502
  throw new TypeError("Expo export metadata.json is invalid.");
23099
23503
  const descriptor = parseAbsoluteExpoUpdateDescriptor({
@@ -23104,33 +23508,33 @@ var finalizeAbsoluteExpoUpdateExport = async (options) => {
23104
23508
  runtimeVersion: options.runtimeVersion
23105
23509
  });
23106
23510
  const referenced = Object.values(descriptor.platforms).flatMap((entry) => entry ? [entry.launchAsset, ...entry.assets] : []);
23107
- 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(() => {
23108
23512
  throw new TypeError(`Expo export metadata references missing asset ${path}.`);
23109
23513
  })));
23110
- const destination = join15(options.exportDirectory, ABSOLUTE_EXPO_UPDATE_DESCRIPTOR);
23111
- await mkdir13(dirname12(destination), { recursive: true });
23112
- 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")}
23113
23517
  `);
23114
23518
  return { descriptor, path: destination };
23115
23519
  };
23116
23520
 
23117
23521
  // src/mobile/expoCodeSigning.ts
23118
23522
  var import_code_signing_certificates = __toESM(require_main(), 1);
23119
- import { access as access12, mkdir as mkdir14, writeFile as writeFile15 } from "fs/promises";
23120
- import { dirname as dirname13, isAbsolute as isAbsolute6, relative as relative11, resolve as resolve12, sep as sep7 } from "path";
23121
- var exists5 = async (path) => access12(path).then(() => true).catch(() => false);
23122
- var inside3 = (root, path) => {
23123
- const location = relative11(root, path);
23124
- 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);
23125
23529
  };
23126
23530
  var generateAbsoluteExpoCodeSigning = async (options) => {
23127
- const root = resolve12(options.projectRoot);
23128
- const certificatePath = resolve12(root, options.certificatePath);
23129
- const privateKeyPath = resolve12(root, options.privateKeyPath);
23130
- const publicKeyPath = resolve12(root, options.publicKeyPath ?? resolve12(dirname13(privateKeyPath), "public-key.pem"));
23131
- 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))
23132
23536
  throw new TypeError("Expo code-signing certificate must be written inside the project so store builds can embed it.");
23133
- if (inside3(root, privateKeyPath) || inside3(root, publicKeyPath))
23537
+ if (inside4(root, privateKeyPath) || inside4(root, publicKeyPath))
23134
23538
  throw new TypeError("Expo code-signing private/public key output must remain outside the project and source control.");
23135
23539
  const commonName = options.commonName.trim();
23136
23540
  if (!commonName || commonName.length > 64)
@@ -23139,7 +23543,7 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
23139
23543
  if (!Number.isSafeInteger(validityYears) || validityYears < 1 || validityYears > 20)
23140
23544
  throw new TypeError("Expo code-signing validity must be an integer from 1 through 20 years.");
23141
23545
  const collisions = (await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map(async (path) => ({
23142
- exists: await exists5(path),
23546
+ exists: await exists6(path),
23143
23547
  path
23144
23548
  })))).filter((entry) => entry.exists);
23145
23549
  if (collisions.length > 0)
@@ -23157,11 +23561,11 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
23157
23561
  import_code_signing_certificates.validateSelfSignedCertificate(certificate, keyPair);
23158
23562
  const { privateKeyPEM, publicKeyPEM } = import_code_signing_certificates.convertKeyPairToPEM(keyPair);
23159
23563
  const certificatePem = import_code_signing_certificates.convertCertificateToCertificatePEM(certificate);
23160
- 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 })));
23161
23565
  await Promise.all([
23162
- writeFile15(certificatePath, certificatePem, { flag: "wx", mode: 420 }),
23163
- writeFile15(privateKeyPath, privateKeyPEM, { flag: "wx", mode: 384 }),
23164
- 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 })
23165
23569
  ]);
23166
23570
  return {
23167
23571
  certificatePath,
@@ -23172,8 +23576,8 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
23172
23576
  };
23173
23577
 
23174
23578
  // src/mobile/updatePublisher.ts
23175
- import { access as access13 } from "fs/promises";
23176
- 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";
23177
23581
  import { pathToFileURL as pathToFileURL3 } from "url";
23178
23582
  var lifecycleMethod = (publisher, name) => {
23179
23583
  const method = publisher[name];
@@ -23282,16 +23686,16 @@ var pruneAbsoluteMobileUpdates = async (options) => {
23282
23686
  var object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
23283
23687
  var isPublisher2 = (value) => object3(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
23284
23688
  var projectPath3 = (projectRoot, requested, label) => {
23285
- const root = resolve13(projectRoot);
23286
- const path = resolve13(root, requested);
23287
- const projectRelative = relative12(root, path);
23288
- 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))
23289
23693
  throw new TypeError(`${label} must remain inside the project.`);
23290
23694
  return path;
23291
23695
  };
23292
23696
  var loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
23293
23697
  const modulePath = projectPath3(projectRoot, requestedModulePath, "mobile update registry");
23294
- await access13(modulePath).catch(() => {
23698
+ await access14(modulePath).catch(() => {
23295
23699
  throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
23296
23700
  });
23297
23701
  const loaded = await import(pathToFileURL3(modulePath).href);
@@ -23346,10 +23750,10 @@ var rollbackAbsoluteMobileUpdate = async (options) => {
23346
23750
  var NOT_FOUND4 = -1;
23347
23751
  var ANDROID_EMULATOR_BOOT_POLL_MS = 500;
23348
23752
  var ANDROID_EMULATOR_BOOT_TIMEOUT_MS = 180000;
23349
- var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
23753
+ var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
23350
23754
  var expoPublicConfig = (config) => {
23351
23755
  const result = { ...config };
23352
- if (!isRecord7(result.updates))
23756
+ if (!isRecord8(result.updates))
23353
23757
  return result;
23354
23758
  const {
23355
23759
  codeSigningCertificate: _codeSigningCertificate,
@@ -23389,13 +23793,13 @@ var CAPACITOR_SYNC_PACKAGE_SPECS = [
23389
23793
  ];
23390
23794
  var packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@"));
23391
23795
  var directProjectPackages = async (projectRoot) => {
23392
- const manifest = JSON.parse(await readFile19(join16(projectRoot, "package.json"), "utf8"));
23393
- if (!isRecord7(manifest))
23796
+ const manifest = JSON.parse(await readFile20(join17(projectRoot, "package.json"), "utf8"));
23797
+ if (!isRecord8(manifest))
23394
23798
  throw new TypeError("Application package.json must contain an object.");
23395
23799
  const names = new Set;
23396
23800
  for (const field2 of ["dependencies", "devDependencies"]) {
23397
23801
  const dependencies = Reflect.get(manifest, field2);
23398
- if (isRecord7(dependencies))
23802
+ if (isRecord8(dependencies))
23399
23803
  for (const name of Object.keys(dependencies))
23400
23804
  names.add(name);
23401
23805
  }
@@ -23403,8 +23807,8 @@ var directProjectPackages = async (projectRoot) => {
23403
23807
  };
23404
23808
  var resolvedPackageVersion = async (projectRoot, packageName) => {
23405
23809
  try {
23406
- const manifest = JSON.parse(await readFile19(join16(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
23407
- 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;
23408
23812
  } catch {
23409
23813
  return;
23410
23814
  }
@@ -23467,9 +23871,9 @@ var runCapacitor = async (projectRoot, args) => {
23467
23871
  };
23468
23872
  var runCapacitorForPlatforms = (projectRoot, command, platforms) => platforms.reduce((pending, platform2) => pending.then(() => runCapacitor(projectRoot, [command, platform2])), Promise.resolve());
23469
23873
  var expoExecutable = async (project) => {
23470
- const executable = join16(project, "node_modules", ".bin", "expo");
23874
+ const executable = join17(project, "node_modules", ".bin", "expo");
23471
23875
  try {
23472
- await access14(executable);
23876
+ await access15(executable);
23473
23877
  return executable;
23474
23878
  } catch {
23475
23879
  throw new TypeError("Expo dependencies are not installed in the generated shell. Run `absolute mobile init --yes`.");
@@ -23501,14 +23905,14 @@ var runExpo = async (project, args, options = {}) => {
23501
23905
  };
23502
23906
  var ensureExpoPackages = async (project, args) => {
23503
23907
  try {
23504
- const manifest = JSON.parse(await readFile19(join16(project, "package.json"), "utf8"));
23908
+ const manifest = JSON.parse(await readFile20(join17(project, "package.json"), "utf8"));
23505
23909
  const dependencies = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "dependencies") : undefined;
23506
23910
  if (typeof dependencies !== "object" || dependencies === null || Array.isArray(dependencies))
23507
23911
  throw new TypeError("Generated Expo dependencies are invalid.");
23508
23912
  await Promise.all(Object.entries(dependencies).map(async ([name, expected]) => {
23509
23913
  if (typeof expected !== "string")
23510
23914
  throw new TypeError("Generated Expo dependency version is invalid.");
23511
- 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"));
23512
23916
  const actual = typeof installed === "object" && installed !== null ? Reflect.get(installed, "version") : undefined;
23513
23917
  if (typeof actual !== "string")
23514
23918
  throw new TypeError("Installed Expo dependency version is invalid.");
@@ -23706,7 +24110,7 @@ var sync = async (args) => {
23706
24110
  };
23707
24111
  var associations = async (args) => {
23708
24112
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
23709
- const outputDirectory = resolve14(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
24113
+ const outputDirectory = resolve15(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
23710
24114
  if (args.includes("--verify")) {
23711
24115
  const result2 = await verifyAbsoluteMobileAssociationFiles(mobile);
23712
24116
  console.log(`Verified ${result2.results.length} hosted association files`);
@@ -23758,7 +24162,7 @@ var generateGithubCi = async (args) => {
23758
24162
  const publicResult = {
23759
24163
  changed: result.changed,
23760
24164
  format: result.format,
23761
- path: relative13(projectRoot, result.path).replaceAll("\\", "/"),
24165
+ path: relative14(projectRoot, result.path).replaceAll("\\", "/"),
23762
24166
  platforms: result.platforms,
23763
24167
  publishing: result.publishing,
23764
24168
  requiredSecrets: result.requiredSecrets
@@ -23869,9 +24273,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
23869
24273
  temporaryDirectory: undefined
23870
24274
  };
23871
24275
  await ensureExpoPackages(mobile.nativeProjectDirectory, options.args);
23872
- const temporaryParent = join16(options.projectRoot, ".absolutejs", "mobile", "expo-update-exports");
23873
- await mkdir15(temporaryParent, { recursive: true });
23874
- 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-"));
23875
24279
  try {
23876
24280
  await runExpo(mobile.nativeProjectDirectory, [
23877
24281
  "export",
@@ -23883,9 +24287,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
23883
24287
  temporaryDirectory,
23884
24288
  "--clear"
23885
24289
  ], { production: true });
23886
- const generatedApp = JSON.parse(await readFile19(join16(mobile.nativeProjectDirectory, "app.json"), "utf8"));
23887
- const expoConfig = isRecord7(generatedApp) ? generatedApp.expo : undefined;
23888
- 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))
23889
24293
  throw new TypeError("Generated Expo app configuration is invalid.");
23890
24294
  const publicExpoConfig = expoPublicConfig(expoConfig);
23891
24295
  const computedRuntime = resolveAbsoluteMobileUpdateRuntime(mobile, options.projectRoot).fingerprint;
@@ -23901,7 +24305,7 @@ var prepareExpoMobileUpdateExport = async (options) => {
23901
24305
  temporaryDirectory
23902
24306
  };
23903
24307
  } catch (error) {
23904
- await rm8(temporaryDirectory, { force: true, recursive: true });
24308
+ await rm9(temporaryDirectory, { force: true, recursive: true });
23905
24309
  throw error;
23906
24310
  }
23907
24311
  };
@@ -23926,11 +24330,11 @@ var buildMobileUpdate = async (args) => {
23926
24330
  let expoExportDirectory;
23927
24331
  try {
23928
24332
  await start(mobileBuildServerEntry(args), valueAfter(args, "--web-outdir"), configPath, { prepareOnly: true });
23929
- const embedded = JSON.parse(await readFile19(join16(mobile.bundleDirectory, "absolute-mobile-manifest.json"), "utf8"));
23930
- 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;
23931
24335
  if (typeof runtimeFingerprint !== "string" || !/^[a-f0-9]{64}$/u.test(runtimeFingerprint))
23932
24336
  throw new TypeError("Prepared mobile bundle is missing its native runtime fingerprint.");
23933
- const privateKey = await readFile19(resolve14(projectRoot, signingKeyPath));
24337
+ const privateKey = await readFile20(resolve15(projectRoot, signingKeyPath));
23934
24338
  const configuredPublicKey = Buffer.from(mobile.updates.publicKeys[keyId], "base64");
23935
24339
  const derivedPublicKey = createPublicKey2(privateKey).export({
23936
24340
  format: "der",
@@ -23951,7 +24355,7 @@ var buildMobileUpdate = async (args) => {
23951
24355
  channel: mobile.updates.channel,
23952
24356
  classification,
23953
24357
  keyId,
23954
- outputDirectory: valueAfter(args, "--outdir") ?? join16(projectRoot, ".absolutejs", "mobile", "updates"),
24358
+ outputDirectory: valueAfter(args, "--outdir") ?? join17(projectRoot, ".absolutejs", "mobile", "updates"),
23955
24359
  privateKey,
23956
24360
  runtimeFingerprint
23957
24361
  });
@@ -23963,7 +24367,7 @@ var buildMobileUpdate = async (args) => {
23963
24367
  return result;
23964
24368
  } finally {
23965
24369
  if (expoExportDirectory)
23966
- await rm8(expoExportDirectory, { force: true, recursive: true });
24370
+ await rm9(expoExportDirectory, { force: true, recursive: true });
23967
24371
  sendTelemetryEvent("mobile:update-build", {
23968
24372
  classification: classification ?? "invalid",
23969
24373
  durationMs: Math.round(performance.now() - startedAt),
@@ -23993,7 +24397,7 @@ var generateExpoUpdateSigning = async (args) => {
23993
24397
  ...valueAfter(args, "--public-key") ? { publicKeyPath: valueAfter(args, "--public-key") } : {},
23994
24398
  ...validityYears === undefined ? {} : { validityYears }
23995
24399
  });
23996
- const certificatePath = relative13(projectRoot, result.certificatePath).replaceAll("\\", "/");
24400
+ const certificatePath = relative14(projectRoot, result.certificatePath).replaceAll("\\", "/");
23997
24401
  sendTelemetryEvent("mobile:update-code-signing-generated", {
23998
24402
  engine: "expo",
23999
24403
  validityYears: validityYears ?? 10
@@ -24055,7 +24459,7 @@ var provisionMobileUpdate = async (args) => {
24055
24459
  publicKeys: mobile.updates.publicKeys,
24056
24460
  storage: requestedStorage
24057
24461
  });
24058
- 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("\\", "/")}.`);
24059
24463
  if (requestedStorage === "local")
24060
24464
  console.log("Production release checks will require durable storage. Re-run with --storage s3 --force before deploying.");
24061
24465
  else
@@ -24532,7 +24936,7 @@ var buildAndroid = async (args, prepareVersionCode) => {
24532
24936
  const durationMs = Math.round(performance.now() - startedAt);
24533
24937
  console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} Android App Bundle in ${getDurationString(durationMs)}.`);
24534
24938
  console.log(`Artifact: ${release.artifactPath}`);
24535
- console.log(`Metadata: ${join16(release.releaseRoot, "release.json")}`);
24939
+ console.log(`Metadata: ${join17(release.releaseRoot, "release.json")}`);
24536
24940
  return release;
24537
24941
  } finally {
24538
24942
  sendTelemetryEvent("mobile:android-release-build", {
@@ -24692,7 +25096,7 @@ var buildIos = async (args, prepareBuildNumber) => {
24692
25096
  console.log(`Artifact: ${release.artifactPath}`);
24693
25097
  if (release.registeredArtifactPath)
24694
25098
  console.log(`Registered-device artifact: ${release.registeredArtifactPath}`);
24695
- console.log(`Metadata: ${join16(release.releaseRoot, "release.json")}`);
25099
+ console.log(`Metadata: ${join17(release.releaseRoot, "release.json")}`);
24696
25100
  return release;
24697
25101
  } finally {
24698
25102
  stopListeningForCancellation();
@@ -24804,7 +25208,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
24804
25208
  checks.push({
24805
25209
  id: "sync.storage-schema",
24806
25210
  label: `Offline schema ${schema.components.map((component) => `${component.id}@${component.version}`).join(", ")}`,
24807
- path: join16(projectRoot, "package.json"),
25211
+ path: join17(projectRoot, "package.json"),
24808
25212
  platform: "host",
24809
25213
  status: "pass"
24810
25214
  });
@@ -24812,7 +25216,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
24812
25216
  checks.push({
24813
25217
  id: "sync.storage-schema",
24814
25218
  label: "Offline schema metadata is invalid",
24815
- path: join16(projectRoot, "package.json"),
25219
+ path: join17(projectRoot, "package.json"),
24816
25220
  platform: "host",
24817
25221
  remediation: error instanceof Error ? error.message : String(error),
24818
25222
  status: "fail"
@@ -24900,7 +25304,7 @@ var requireAndroidTestPort = (args, projectRoot) => {
24900
25304
  }
24901
25305
  return { https: args.includes("--https"), port };
24902
25306
  }
24903
- 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);
24904
25308
  if (instances.length !== 1) {
24905
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>.");
24906
25310
  }
@@ -24994,8 +25398,8 @@ var selectOrStartAndroidReleaseEmulator = async (adb2, emulator, explicitSerial,
24994
25398
  return waitForBoot();
24995
25399
  };
24996
25400
  var safeArtifactRoot = (projectRoot, value) => {
24997
- const root = resolve14(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
24998
- 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)}/`)) {
24999
25403
  throw new TypeError("mobile test --artifacts must remain inside the project.");
25000
25404
  }
25001
25405
  return root;
@@ -25023,12 +25427,12 @@ var waitForRequestedAndroidHmr = async (args, session, checks, timeoutMs) => {
25023
25427
  });
25024
25428
  };
25025
25429
  var writeAndroidFailureArtifacts = async (options) => {
25026
- await mkdir15(options.artifactRoot, { recursive: true });
25027
- 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(() => {
25028
25432
  return;
25029
25433
  }) : undefined;
25030
- const diagnosticPath = join16(options.artifactRoot, "android-failure.json");
25031
- await writeFile16(diagnosticPath, `${JSON.stringify({
25434
+ const diagnosticPath = join17(options.artifactRoot, "android-failure.json");
25435
+ await writeFile17(diagnosticPath, `${JSON.stringify({
25032
25436
  diagnostics: options.session?.diagnostics ?? [],
25033
25437
  error: options.error instanceof Error ? options.error.message : String(options.error),
25034
25438
  platform: "android",
@@ -25049,7 +25453,7 @@ var captureAndroidReleaseScreenshot = async (adb2, serial, destination) => {
25049
25453
  ]);
25050
25454
  if (exitCode !== 0)
25051
25455
  throw new Error(`Android release screenshot failed: ${stderr.trim() || `status ${exitCode}`}`);
25052
- await writeFile16(destination, bytes);
25456
+ await writeFile17(destination, bytes);
25053
25457
  return destination;
25054
25458
  };
25055
25459
  var printAndroidReleaseAcceptance = (result, json) => {
@@ -25103,7 +25507,7 @@ var testAndroidRelease = async (args, mobile, projectRoot) => {
25103
25507
  release,
25104
25508
  serial
25105
25509
  });
25106
- 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;
25107
25511
  sendTelemetryEvent("mobile:android-release-conformance", {
25108
25512
  durationMs: result.durationMs,
25109
25513
  engine: result.engine,
@@ -25150,9 +25554,9 @@ var testAndroidRelease = async (args, mobile, projectRoot) => {
25150
25554
  platform: "android",
25151
25555
  success: false
25152
25556
  });
25153
- await mkdir15(artifactRoot, { recursive: true });
25154
- const diagnosticPath = join16(artifactRoot, "android-release-failure.json");
25155
- 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({
25156
25560
  error: sanitizeNativeReportText(error instanceof Error ? error.message : String(error)),
25157
25561
  platform: "android",
25158
25562
  provider: mobile.engine,
@@ -25246,7 +25650,7 @@ var testAndroid = async (args) => {
25246
25650
  console.log(JSON.stringify(report, null, 2));
25247
25651
  else
25248
25652
  printAndroidTestReport(report);
25249
- 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;
25250
25654
  await writeRequestedAndroidReport({
25251
25655
  adb: adb2,
25252
25656
  args,
@@ -25315,14 +25719,14 @@ var requireIosTestContext = (args, projectRoot) => {
25315
25719
  const port = Number(explicit);
25316
25720
  if (!Number.isInteger(port) || port < 1 || port > 65535)
25317
25721
  throw new TypeError("mobile test --port must be a valid TCP port.");
25318
- 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);
25319
25723
  return {
25320
25724
  https: instance2?.https ?? args.includes("--https"),
25321
25725
  instance: instance2,
25322
25726
  port
25323
25727
  };
25324
25728
  }
25325
- 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);
25326
25730
  if (instances.length !== 1)
25327
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>.");
25328
25732
  const [instance] = instances;
@@ -25340,7 +25744,7 @@ var waitForIosHmrClient = async (options) => {
25340
25744
  return;
25341
25745
  });
25342
25746
  const status = response?.ok ? await response.json().catch(() => null) : null;
25343
- const targets = isRecord7(status) && isRecord7(status.connectedTargets) ? status.connectedTargets : undefined;
25747
+ const targets = isRecord8(status) && isRecord8(status.connectedTargets) ? status.connectedTargets : undefined;
25344
25748
  if (targets && typeof targets["capacitor-ios"] === "number" && targets["capacitor-ios"] > 0)
25345
25749
  return;
25346
25750
  await Bun.sleep(100);
@@ -25466,8 +25870,8 @@ var requireCapturedCommand = (command, label) => {
25466
25870
  return result;
25467
25871
  };
25468
25872
  var writeIosFailureArtifacts = async (options) => {
25469
- await mkdir15(options.artifactRoot, { recursive: true });
25470
- const screenshot = join16(options.artifactRoot, "ios-failure.png");
25873
+ await mkdir16(options.artifactRoot, { recursive: true });
25874
+ const screenshot = join17(options.artifactRoot, "ios-failure.png");
25471
25875
  const screenshotResult = captureCommand4([
25472
25876
  options.xcrun,
25473
25877
  "simctl",
@@ -25476,8 +25880,8 @@ var writeIosFailureArtifacts = async (options) => {
25476
25880
  "screenshot",
25477
25881
  screenshot
25478
25882
  ]);
25479
- const diagnosticPath = join16(options.artifactRoot, "ios-failure.json");
25480
- await writeFile16(diagnosticPath, `${JSON.stringify({
25883
+ const diagnosticPath = join17(options.artifactRoot, "ios-failure.json");
25884
+ await writeFile17(diagnosticPath, `${JSON.stringify({
25481
25885
  appId: options.appId,
25482
25886
  error: options.error instanceof Error ? options.error.message : String(options.error),
25483
25887
  platform: "ios",
@@ -25504,8 +25908,8 @@ var nativeReportRoot = (args, projectRoot, platform2) => {
25504
25908
  var absolutejsVersionForReport = async () => {
25505
25909
  let absolutejsVersion = process.env.ABSOLUTE_VERSION ?? "unknown";
25506
25910
  const versions = await Promise.all([
25507
- resolve14(import.meta.dir, "..", "..", "package.json"),
25508
- resolve14(import.meta.dir, "..", "..", "..", "package.json")
25911
+ resolve15(import.meta.dir, "..", "..", "package.json"),
25912
+ resolve15(import.meta.dir, "..", "..", "..", "package.json")
25509
25913
  ].map((candidate) => readPackageVersionForIosReport(candidate).catch(() => "unknown")));
25510
25914
  for (const version of versions) {
25511
25915
  if (version === "unknown")
@@ -25882,9 +26286,9 @@ var testIosRelease = async (args, mobile, projectRoot) => {
25882
26286
  remote: remoteProfile !== undefined,
25883
26287
  success: false
25884
26288
  });
25885
- await mkdir15(artifactRoot, { recursive: true });
25886
- const diagnosticPath = join16(artifactRoot, "ios-release-failure.json");
25887
- 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({
25888
26292
  error: sanitizeIosReportText(error instanceof Error ? error.message : String(error)),
25889
26293
  platform: "ios",
25890
26294
  provider: mobile.engine,
@@ -25977,8 +26381,8 @@ var testIos = async (args) => {
25977
26381
  mobile.appId
25978
26382
  ], "iOS app launch");
25979
26383
  await waitForIosHmrClient({ https, port, timeoutMs });
25980
- await mkdir15(artifactRoot, { recursive: true });
25981
- const screenshot = join16(artifactRoot, "ios-simulator.png");
26384
+ await mkdir16(artifactRoot, { recursive: true });
26385
+ const screenshot = join17(artifactRoot, "ios-simulator.png");
25982
26386
  requireCapturedCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
25983
26387
  const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
25984
26388
  const report = {
@@ -26062,6 +26466,110 @@ var testIos = async (args) => {
26062
26466
  throw new Error(`${error instanceof Error ? error.message : String(error)} Failure diagnostics: ${diagnosticPath}${screenshot ? `; screenshot: ${screenshot}` : ""}`, { cause: error });
26063
26467
  }
26064
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
+ };
26065
26573
  var runMobile = async (args) => {
26066
26574
  const [command] = args;
26067
26575
  if (command === "pair") {
@@ -26100,6 +26608,10 @@ var runMobile = async (args) => {
26100
26608
  await inspectMobile(args.slice(1));
26101
26609
  return;
26102
26610
  }
26611
+ if (command === "certify") {
26612
+ await certifyMobileRelease(args.slice(1));
26613
+ return;
26614
+ }
26103
26615
  if (command === "test" && args[1] === "android") {
26104
26616
  await testAndroid(args.slice(2));
26105
26617
  return;
@@ -26164,7 +26676,7 @@ var runMobile = async (args) => {
26164
26676
  await publishIos(args.slice(2));
26165
26677
  return;
26166
26678
  }
26167
- 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 [--remote name] [--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]");
26168
26680
  };
26169
26681
  export {
26170
26682
  runMobile