@absolutejs/absolute 0.20.0-beta.110 → 0.20.0-beta.111

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.
@@ -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 access16,
16783
- mkdir as mkdir17,
16784
- mkdtemp as mkdtemp6,
16785
- readFile as readFile21,
16786
- rm as rm9,
16787
- writeFile as writeFile18
16782
+ access as access17,
16783
+ mkdir as mkdir18,
16784
+ mkdtemp as mkdtemp7,
16785
+ readFile as readFile22,
16786
+ rm as rm10,
16787
+ writeFile as writeFile19
16788
16788
  } from "fs/promises";
16789
16789
  import { createPublicKey as createPublicKey2 } from "crypto";
16790
- import { join as join17, relative as relative15, resolve as resolve16 } from "path";
16790
+ import { join as join18, relative as relative16, resolve as resolve17 } from "path";
16791
16791
  import { createInterface } from "readline/promises";
16792
16792
 
16793
16793
  // src/mobile/nativeDeepLinks.ts
@@ -22982,7 +22982,7 @@ var renderAbsoluteMobileProjectInspection = (report) => {
22982
22982
  import { existsSync } from "fs";
22983
22983
  import { access as access12, mkdir as mkdir13, readFile as readFile18, writeFile as writeFile14 } from "fs/promises";
22984
22984
  import { dirname as dirname11, extname as extname2, relative as relative11, resolve as resolve12, sep as sep8 } from "path";
22985
- var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 2;
22985
+ var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 3;
22986
22986
  var SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
22987
22987
  var CI_ENV_INDENTATION = 6;
22988
22988
  var RESERVED_SECRET_NAMES = new Set([
@@ -23524,6 +23524,10 @@ var promotionSharedSteps = (platform) => ` - name: Restore exact ${platform
23524
23524
  path: .absolutejs/mobile/releases/${platform}
23525
23525
  run-id: \${{ inputs.source_run_id }}
23526
23526
  github-token: \${{ github.token }}
23527
+ - name: Record bounded promotion context
23528
+ shell: bash
23529
+ run: |
23530
+ bun -e 'const sourceRunId = process.env.ABSOLUTE_SOURCE_RUN_ID; const promotionRunId = process.env.GITHUB_RUN_ID; if (!sourceRunId?.match(/^[1-9][0-9]*$/) || !promotionRunId?.match(/^[1-9][0-9]*$/)) throw new Error("Invalid promotion run identity"); await Bun.write(".absolutejs/mobile-ci/promotion-context.json", JSON.stringify({ format: 1, platform: ${JSON.stringify(platform)}, promotionRunId, sourceArtifact: ${JSON.stringify(`absolute-mobile-${platform}`)}, sourceRunId }, null, 2) + "\\n");'
23527
23531
  - name: Import and verify exact certification
23528
23532
  shell: bash
23529
23533
  run: |
@@ -23562,6 +23566,7 @@ var androidPromotionJob = (options) => {
23562
23566
  env:
23563
23567
  ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}
23564
23568
  ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
23569
+ ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
23565
23570
  ABSOLUTE_PLAY_TRACK: \${{ inputs.play_track }}
23566
23571
  ABSOLUTE_GOOGLE_CREDENTIALS_BASE64: \${{ secrets.ABSOLUTE_GOOGLE_CREDENTIALS_BASE64 }}
23567
23572
  GOOGLE_APPLICATION_CREDENTIALS: \${{ runner.temp }}/absolute-google-credentials.json${custom ? `
@@ -23601,6 +23606,7 @@ ${promotionSharedSteps("android")}
23601
23606
  name: absolute-mobile-android-promotion-\${{ github.run_id }}
23602
23607
  path: |
23603
23608
  .absolutejs/mobile-ci/imported-certification/
23609
+ .absolutejs/mobile-ci/promotion-context.json
23604
23610
  .absolutejs/mobile-ci/promotion-receipt.json
23605
23611
  if-no-files-found: error
23606
23612
  retention-days: 90
@@ -23625,6 +23631,7 @@ var iosPromotionJob = (options) => {
23625
23631
  env:
23626
23632
  ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}
23627
23633
  ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
23634
+ ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
23628
23635
  ABSOLUTE_TESTFLIGHT_GROUP: \${{ inputs.testflight_group }}
23629
23636
  ABSOLUTE_TESTFLIGHT_SUBMIT_REVIEW: \${{ inputs.submit_testflight_review }}
23630
23637
  APP_STORE_CONNECT_ISSUER_ID: \${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
@@ -23666,6 +23673,7 @@ ${promotionSharedSteps("ios")}
23666
23673
  name: absolute-mobile-ios-promotion-\${{ github.run_id }}
23667
23674
  path: |
23668
23675
  .absolutejs/mobile-ci/imported-certification/
23676
+ .absolutejs/mobile-ci/promotion-context.json
23669
23677
  .absolutejs/mobile-ci/promotion-receipt.json
23670
23678
  if-no-files-found: error
23671
23679
  retention-days: 90
@@ -23785,47 +23793,448 @@ var writeAbsoluteMobileGithubWorkflow = async (options) => {
23785
23793
  };
23786
23794
  };
23787
23795
 
23788
- // src/mobile/updateSigning.ts
23789
- import { createHash as createHash6, sign, verify } from "crypto";
23796
+ // src/mobile/ciPromotionAudit.ts
23790
23797
  import {
23791
- cp,
23798
+ access as access13,
23792
23799
  mkdir as mkdir14,
23793
23800
  mkdtemp as mkdtemp5,
23794
- readdir as readdir6,
23795
23801
  readFile as readFile19,
23802
+ readdir as readdir6,
23796
23803
  rename as rename11,
23797
23804
  rm as rm8,
23798
23805
  stat as stat7,
23799
23806
  writeFile as writeFile15
23800
23807
  } from "fs/promises";
23801
- import { dirname as dirname12, join as join15, relative as relative12, resolve as resolve13 } from "path";
23808
+ import { dirname as dirname12, isAbsolute as isAbsolute8, join as join15, relative as relative12, resolve as resolve13, sep as sep9 } from "path";
23809
+ var ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT = 1;
23810
+ var ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT = 1;
23811
+ var MAX_GITHUB_OUTPUT_BYTES = 1048576;
23812
+ var MAX_AUDIT_JSON_BYTES = 1048576;
23813
+ var MAX_SEARCH_FILES = 20000;
23814
+ var RUN_ID_PATTERN = /^[1-9][0-9]*$/u;
23815
+ var REPOSITORY_PATTERN2 = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u;
23816
+ var isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
23817
+ var requiredString = (value, name) => {
23818
+ if (typeof value !== "string" || value.trim() === "")
23819
+ throw new TypeError(`${name} is invalid.`);
23820
+ return value.trim();
23821
+ };
23822
+ var optionalString = (value, name) => {
23823
+ if (value === null || value === undefined)
23824
+ return null;
23825
+ return requiredString(value, name);
23826
+ };
23827
+ var runId = (value, name) => {
23828
+ if (!RUN_ID_PATTERN.test(value))
23829
+ throw new TypeError(`${name} is invalid.`);
23830
+ return value;
23831
+ };
23832
+ var repositoryArgs = (repository) => {
23833
+ if (repository === undefined)
23834
+ return [];
23835
+ if (!REPOSITORY_PATTERN2.test(repository))
23836
+ throw new TypeError("GitHub repository must be owner/name.");
23837
+ return ["--repo", repository];
23838
+ };
23839
+ var projectPath3 = (projectRoot, requested) => {
23840
+ const root = resolve13(projectRoot);
23841
+ const path = resolve13(root, requested);
23842
+ const portable = relative12(root, path);
23843
+ if (portable === ".." || portable.startsWith(`..${sep9}`) || isAbsolute8(portable))
23844
+ throw new TypeError("Mobile CI audit output must remain inside the project.");
23845
+ return path;
23846
+ };
23847
+ var defaultRunner = async (command, options) => {
23848
+ const child = Bun.spawn(command, {
23849
+ cwd: options.cwd,
23850
+ env: options.env,
23851
+ stderr: "pipe",
23852
+ stdin: "ignore",
23853
+ stdout: "pipe"
23854
+ });
23855
+ const [exitCode, stderr, stdout] = await Promise.all([
23856
+ child.exited,
23857
+ new Response(child.stderr).text(),
23858
+ new Response(child.stdout).text()
23859
+ ]);
23860
+ return { exitCode, stderr, stdout };
23861
+ };
23862
+ var requireCommand2 = async (run, command, options, label) => {
23863
+ const result = await run(command, options);
23864
+ if (result.exitCode !== 0)
23865
+ throw new TypeError(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.exitCode}`}`);
23866
+ if (result.stdout.length > MAX_GITHUB_OUTPUT_BYTES || result.stderr.length > MAX_GITHUB_OUTPUT_BYTES)
23867
+ throw new TypeError(`${label} output exceeded the 1 MiB limit.`);
23868
+ return result;
23869
+ };
23870
+ var parseJob = (value) => {
23871
+ if (!isRecord9(value))
23872
+ throw new TypeError("GitHub run job is invalid.");
23873
+ return {
23874
+ conclusion: optionalString(value.conclusion, "GitHub job conclusion"),
23875
+ name: requiredString(value.name, "GitHub job name"),
23876
+ status: requiredString(value.status, "GitHub job status")
23877
+ };
23878
+ };
23879
+ var inspectAbsoluteMobileCiRun = async (options) => {
23880
+ const requestedRunId = runId(options.runId, "GitHub run ID");
23881
+ const result = await requireCommand2(options.run ?? defaultRunner, [
23882
+ "gh",
23883
+ "run",
23884
+ "view",
23885
+ requestedRunId,
23886
+ ...repositoryArgs(options.repository),
23887
+ "--json",
23888
+ "conclusion,createdAt,databaseId,headBranch,headSha,jobs,startedAt,status,updatedAt,url,workflowName"
23889
+ ], { cwd: options.projectRoot }, "GitHub workflow inspection");
23890
+ return parseAbsoluteMobileCiRunStatus(JSON.parse(result.stdout), requestedRunId);
23891
+ };
23892
+ var parseAbsoluteMobileCiRunStatus = (value, requestedRunId) => {
23893
+ if (!isRecord9(value) || !Array.isArray(value.jobs))
23894
+ throw new TypeError("GitHub workflow run response is invalid.");
23895
+ const { databaseId } = value;
23896
+ if (typeof databaseId !== "number" && typeof databaseId !== "string" || String(databaseId) !== requestedRunId)
23897
+ throw new TypeError("GitHub workflow run identity does not match.");
23898
+ return {
23899
+ conclusion: optionalString(value.conclusion, "GitHub run conclusion"),
23900
+ createdAt: requiredString(value.createdAt, "GitHub run createdAt"),
23901
+ headBranch: requiredString(value.headBranch, "GitHub run headBranch"),
23902
+ headSha: requiredString(value.headSha, "GitHub run headSha"),
23903
+ jobs: value.jobs.map(parseJob),
23904
+ runId: requestedRunId,
23905
+ startedAt: optionalString(value.startedAt, "GitHub run startedAt"),
23906
+ status: requiredString(value.status, "GitHub run status"),
23907
+ updatedAt: requiredString(value.updatedAt, "GitHub run updatedAt"),
23908
+ url: requiredString(value.url, "GitHub run URL"),
23909
+ workflowName: requiredString(value.workflowName, "GitHub workflow name")
23910
+ };
23911
+ };
23912
+ var parseAbsoluteMobilePromotionContext = (value) => {
23913
+ if (!isRecord9(value) || value.format !== ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT || value.platform !== "android" && value.platform !== "ios")
23914
+ throw new TypeError("Mobile promotion context is invalid.");
23915
+ const { platform } = value;
23916
+ const sourceArtifact = requiredString(value.sourceArtifact, "promotion source artifact");
23917
+ if (sourceArtifact !== `absolute-mobile-${platform}`)
23918
+ throw new TypeError("Mobile promotion source artifact is invalid.");
23919
+ return {
23920
+ format: ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT,
23921
+ platform,
23922
+ promotionRunId: runId(requiredString(value.promotionRunId, "promotion run ID"), "promotion run ID"),
23923
+ sourceArtifact,
23924
+ sourceRunId: runId(requiredString(value.sourceRunId, "source run ID"), "source run ID")
23925
+ };
23926
+ };
23927
+ var watchAbsoluteMobileCiRun = async (options) => {
23928
+ const requestedRunId = runId(options.runId, "GitHub run ID");
23929
+ await requireCommand2(options.run ?? defaultRunner, [
23930
+ "gh",
23931
+ "run",
23932
+ "watch",
23933
+ requestedRunId,
23934
+ ...repositoryArgs(options.repository),
23935
+ "--exit-status"
23936
+ ], { cwd: options.projectRoot }, "GitHub workflow watch");
23937
+ return inspectAbsoluteMobileCiRun({ ...options, runId: requestedRunId });
23938
+ };
23939
+ var readBoundedJson = async (path, label) => {
23940
+ const metadata = await stat7(path).catch(() => null);
23941
+ if (!metadata?.isFile() || metadata.size > MAX_AUDIT_JSON_BYTES)
23942
+ throw new TypeError(`${label} is missing or exceeds the 1 MiB limit.`);
23943
+ const value = JSON.parse(await readFile19(path, "utf8"));
23944
+ return value;
23945
+ };
23946
+ var findFiles = async (root, name) => {
23947
+ const found = [];
23948
+ let visited = 0;
23949
+ const walk = async (directory) => {
23950
+ const entries = await readdir6(directory, { withFileTypes: true });
23951
+ await Promise.all(entries.map(async (entry) => {
23952
+ visited += 1;
23953
+ if (visited > MAX_SEARCH_FILES)
23954
+ throw new TypeError("Downloaded GitHub artifact contains too many files.");
23955
+ const path = join15(directory, entry.name);
23956
+ if (entry.isDirectory()) {
23957
+ await walk(path);
23958
+ return;
23959
+ }
23960
+ if (entry.isFile() && entry.name === name)
23961
+ found.push(path);
23962
+ }));
23963
+ };
23964
+ await walk(root);
23965
+ return found;
23966
+ };
23967
+ var inferPromotionPlatform = (status) => {
23968
+ const matched = status.jobs.flatMap((job) => {
23969
+ if (job.name === "Promote exact certified Android release")
23970
+ return ["android"];
23971
+ if (job.name === "Promote exact certified iOS release")
23972
+ return ["ios"];
23973
+ return [];
23974
+ });
23975
+ if (matched.length !== 1)
23976
+ throw new TypeError("GitHub run does not contain exactly one mobile promotion job.");
23977
+ const [platform] = matched;
23978
+ if (!platform)
23979
+ throw new TypeError("Mobile promotion platform is missing.");
23980
+ return platform;
23981
+ };
23982
+ var onlyPath = (paths, label) => {
23983
+ const [path] = paths;
23984
+ if (paths.length !== 1 || !path)
23985
+ throw new TypeError(`${label} must contain exactly one matching file.`);
23986
+ return path;
23987
+ };
23988
+ var canonicalJson = (value) => {
23989
+ if (Array.isArray(value))
23990
+ return `[${value.map((entry) => canonicalJson(entry)).join(",")}]`;
23991
+ if (isRecord9(value))
23992
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
23993
+ return JSON.stringify(value);
23994
+ };
23995
+ var publicationSummary = (value, platform, release, certification) => {
23996
+ if (!isRecord9(value) || !isRecord9(value.record) || !isRecord9(value.record.metadata))
23997
+ throw new TypeError("Mobile promotion receipt is invalid.");
23998
+ const { metadata } = value.record;
23999
+ if (metadata.appId !== release.metadata.appId || metadata.releaseId !== release.metadata.releaseId || metadata.sha256 !== release.metadata.sha256)
24000
+ throw new TypeError("Mobile promotion receipt release identity does not match.");
24001
+ if (!isRecord9(value.certification) || !isRecord9(value.certification.provenance))
24002
+ throw new TypeError("Mobile promotion receipt lacks trusted certification provenance.");
24003
+ if (value.certification.certificationId !== certification.certificationId || value.certification.releaseId !== release.metadata.releaseId || value.certification.requirement !== certification.requirement || value.certification.strength !== certification.strength || value.certification.provenance.subject !== release.metadata.releaseId)
24004
+ throw new TypeError("Mobile promotion certification receipt does not match.");
24005
+ requiredString(value.certification.provenance.issuer, "promotion provenance issuer");
24006
+ requiredString(value.certification.provenance.verificationId, "promotion provenance verification ID");
24007
+ const provenanceTime = requiredString(value.certification.provenance.verifiedAt, "promotion provenance verification time");
24008
+ if (Number.isNaN(Date.parse(provenanceTime)))
24009
+ throw new TypeError("Mobile promotion provenance time is invalid.");
24010
+ if (typeof value.reused !== "boolean")
24011
+ throw new TypeError("Mobile promotion reuse status is invalid.");
24012
+ const channel = isRecord9(value.channel) ? optionalString(value.channel.channel, "promotion channel") : null;
24013
+ if (isRecord9(value.channel) && value.channel.releaseId !== release.metadata.releaseId)
24014
+ throw new TypeError("Mobile promotion channel receipt does not match.");
24015
+ const providerReceipt = platform === "android" ? value.googlePlay : value.appStoreConnect;
24016
+ if (providerReceipt === undefined)
24017
+ return {
24018
+ channel,
24019
+ provider: "registry",
24020
+ reused: value.reused,
24021
+ stage: null
24022
+ };
24023
+ if (!isRecord9(providerReceipt) || !isRecord9(providerReceipt.receipt))
24024
+ throw new TypeError("Mobile store promotion receipt is invalid.");
24025
+ if (typeof providerReceipt.reused !== "boolean")
24026
+ throw new TypeError("Mobile store promotion reuse status is invalid.");
24027
+ const expectedProvider = platform === "android" ? "google-play" : "app-store-connect";
24028
+ if (providerReceipt.receipt.provider !== expectedProvider || providerReceipt.receipt.releaseId !== release.metadata.releaseId || providerReceipt.receipt.sha256 !== release.metadata.sha256)
24029
+ throw new TypeError("Mobile store promotion receipt does not match.");
24030
+ return {
24031
+ channel,
24032
+ provider: expectedProvider,
24033
+ reused: providerReceipt.reused,
24034
+ stage: requiredString(providerReceipt.receipt.stage, "Mobile store promotion stage")
24035
+ };
24036
+ };
24037
+ var renderAuditMarkdown = (audit) => `# AbsoluteJS mobile promotion audit
24038
+
24039
+ - Status: **VERIFIED**
24040
+ - Platform: ${audit.platform}
24041
+ - Release: \`${audit.release.releaseId}\`
24042
+ - Artifact SHA-256: \`${audit.release.sha256}\`
24043
+ - Source workflow run: ${audit.github.sourceRunId}
24044
+ - Promotion workflow run: ${audit.github.promotionRunId}
24045
+ - Workflow: ${audit.github.workflowName}
24046
+ - GitHub identity: \`${audit.provenance.repository}/${audit.provenance.workflowPath}@${audit.provenance.ref}\`
24047
+ - Certification: \`${audit.certification.certificationId}\` (${audit.certification.strength})
24048
+ - Publication: ${audit.publication.provider}${audit.publication.stage ? ` / ${audit.publication.stage}` : ""}
24049
+ - Verified at: ${audit.verifiedAt}
24050
+
24051
+ This report contains no credentials, device identifiers, local absolute paths, application data, or GitHub token.
24052
+ `;
24053
+ var auditAbsoluteMobileCiPromotion = async (options) => {
24054
+ const runner = options.run ?? defaultRunner;
24055
+ const promotionRunId = runId(options.runId, "GitHub promotion run ID");
24056
+ const status = await inspectAbsoluteMobileCiRun({
24057
+ projectRoot: options.projectRoot,
24058
+ ...options.repository ? { repository: options.repository } : {},
24059
+ run: runner,
24060
+ runId: promotionRunId
24061
+ });
24062
+ if (status.status !== "completed" || status.conclusion !== "success")
24063
+ throw new TypeError("Mobile promotion run has not completed successfully.");
24064
+ const platform = inferPromotionPlatform(status);
24065
+ const output = projectPath3(options.projectRoot, options.outputDirectory ?? `.absolutejs/mobile-ci/audits/${promotionRunId}`);
24066
+ if (await access13(output).then(() => true).catch(() => false))
24067
+ throw new TypeError(`Mobile CI audit output already exists: ${output}`);
24068
+ await mkdir14(dirname12(output), { recursive: true });
24069
+ const staging = await mkdtemp5(join15(dirname12(output), ".promotion-audit-"));
24070
+ try {
24071
+ const evidenceRoot = join15(staging, "promotion-evidence");
24072
+ await mkdir14(evidenceRoot);
24073
+ await requireCommand2(runner, [
24074
+ "gh",
24075
+ "run",
24076
+ "download",
24077
+ promotionRunId,
24078
+ ...repositoryArgs(options.repository),
24079
+ "--name",
24080
+ `absolute-mobile-${platform}-promotion-${promotionRunId}`,
24081
+ "--dir",
24082
+ evidenceRoot
24083
+ ], { cwd: options.projectRoot }, "GitHub promotion artifact download");
24084
+ const contextPaths = await findFiles(evidenceRoot, "promotion-context.json");
24085
+ let context;
24086
+ if (contextPaths.length === 1)
24087
+ context = parseAbsoluteMobilePromotionContext(await readBoundedJson(onlyPath(contextPaths, "Promotion context"), "Promotion context"));
24088
+ else if (contextPaths.length > 1)
24089
+ throw new TypeError("Promotion artifact contains duplicate contexts.");
24090
+ if (context && context.promotionRunId !== promotionRunId)
24091
+ throw new TypeError("Promotion context run identity does not match.");
24092
+ if (context && context.platform !== platform)
24093
+ throw new TypeError("Promotion context platform does not match.");
24094
+ const sourceRunId = options.sourceRunId ? runId(options.sourceRunId, "GitHub source run ID") : context?.sourceRunId;
24095
+ if (!sourceRunId)
24096
+ throw new TypeError("Promotion artifact predates source-run context; pass --source-run-id.");
24097
+ if (context && options.sourceRunId && context.sourceRunId !== sourceRunId)
24098
+ throw new TypeError("Requested source run does not match promotion context.");
24099
+ const sourceArtifact = context?.sourceArtifact ?? `absolute-mobile-${platform}`;
24100
+ const sourceRoot = join15(staging, "source-release");
24101
+ await mkdir14(sourceRoot);
24102
+ await requireCommand2(runner, [
24103
+ "gh",
24104
+ "run",
24105
+ "download",
24106
+ sourceRunId,
24107
+ ...repositoryArgs(options.repository),
24108
+ "--name",
24109
+ sourceArtifact,
24110
+ "--dir",
24111
+ sourceRoot
24112
+ ], { cwd: options.projectRoot }, "GitHub source release download");
24113
+ const releaseMetadata = await findFiles(sourceRoot, "release.json");
24114
+ if (releaseMetadata.length !== 1)
24115
+ throw new TypeError("Source artifact must contain exactly one immutable mobile release.");
24116
+ const releaseMetadataPath = onlyPath(releaseMetadata, "Source artifact");
24117
+ const release = platform === "android" ? await readAbsoluteAndroidRelease(options.projectRoot, releaseMetadataPath) : await readAbsoluteIosRelease(options.projectRoot, releaseMetadataPath);
24118
+ const certificationPaths = await findFiles(evidenceRoot, "certification.json");
24119
+ const verificationPaths = await findFiles(evidenceRoot, "verification.json");
24120
+ const receiptPaths = await findFiles(evidenceRoot, "promotion-receipt.json");
24121
+ if (certificationPaths.length !== 1 || verificationPaths.length !== 1 || receiptPaths.length !== 1)
24122
+ throw new TypeError("Promotion audit artifact is incomplete or ambiguous.");
24123
+ const certificationPath = onlyPath(certificationPaths, "Promotion certification");
24124
+ const verificationPath = onlyPath(verificationPaths, "Promotion verification");
24125
+ const receiptPath = onlyPath(receiptPaths, "Promotion receipt");
24126
+ const loadedCertification = await readAbsoluteMobileReleaseCertification(options.projectRoot, certificationPath);
24127
+ const certification = verifyAbsoluteMobileReleaseCertification(loadedCertification.certification, release);
24128
+ const verification = await readAbsoluteMobileCertificationVerification(options.projectRoot, verificationPath);
24129
+ if (verification.identity.sha !== status.headSha)
24130
+ throw new TypeError("Sigstore workflow commit does not match the promotion run.");
24131
+ if (options.repository && verification.identity.repository !== options.repository)
24132
+ throw new TypeError("Sigstore repository does not match the requested repository.");
24133
+ const bundlePath = `${certificationPath}.sigstore.json`;
24134
+ const bundle = await readAbsoluteSigstoreBundle(options.projectRoot, bundlePath);
24135
+ if (canonicalJson(bundle) !== canonicalJson(verification.bundle))
24136
+ throw new TypeError("Sigstore bundle does not match verification envelope.");
24137
+ await requireCommand2(runner, [
24138
+ "bunx",
24139
+ "@absolutejs/attest@0.2.0",
24140
+ "verify-blobs",
24141
+ certificationPath
24142
+ ], {
24143
+ cwd: options.projectRoot,
24144
+ env: {
24145
+ ...process.env,
24146
+ GITHUB_REF: verification.identity.ref,
24147
+ GITHUB_REPOSITORY: verification.identity.repository,
24148
+ GITHUB_SHA: verification.identity.sha,
24149
+ GITHUB_WORKFLOW_REF: `${verification.identity.repository}/${verification.identity.workflowPath}@${verification.identity.ref}`
24150
+ }
24151
+ }, "Sigstore certification verification");
24152
+ const publication = publicationSummary(await readBoundedJson(receiptPath, "Mobile promotion receipt"), platform, release, certification);
24153
+ const audit = {
24154
+ certification: {
24155
+ certificationId: certification.certificationId,
24156
+ requirement: certification.requirement,
24157
+ strength: certification.strength
24158
+ },
24159
+ format: ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT,
24160
+ github: {
24161
+ conclusion: status.conclusion,
24162
+ promotionRunId,
24163
+ repository: verification.identity.repository,
24164
+ sourceRunId,
24165
+ url: status.url,
24166
+ workflowName: status.workflowName
24167
+ },
24168
+ platform,
24169
+ provenance: verification.identity,
24170
+ publication,
24171
+ release: {
24172
+ appId: release.metadata.appId,
24173
+ bytes: release.metadata.bytes,
24174
+ engine: release.metadata.engine,
24175
+ releaseId: release.metadata.releaseId,
24176
+ runtime: release.metadata.runtime,
24177
+ sha256: release.metadata.sha256
24178
+ },
24179
+ status: "verified",
24180
+ verifiedAt: new Date().toISOString()
24181
+ };
24182
+ await Promise.all([
24183
+ writeFile15(join15(staging, "audit.json"), `${JSON.stringify(audit, null, 2)}
24184
+ `, { mode: 384 }),
24185
+ writeFile15(join15(staging, "audit.md"), renderAuditMarkdown(audit), {
24186
+ mode: 384
24187
+ })
24188
+ ]);
24189
+ await rename11(staging, output);
24190
+ return { audit, directory: output };
24191
+ } catch (error) {
24192
+ await rm8(staging, { force: true, recursive: true });
24193
+ throw error;
24194
+ }
24195
+ };
24196
+
24197
+ // src/mobile/updateSigning.ts
24198
+ import { createHash as createHash6, sign, verify } from "crypto";
24199
+ import {
24200
+ cp,
24201
+ mkdir as mkdir15,
24202
+ mkdtemp as mkdtemp6,
24203
+ readdir as readdir7,
24204
+ readFile as readFile20,
24205
+ rename as rename12,
24206
+ rm as rm9,
24207
+ stat as stat8,
24208
+ writeFile as writeFile16
24209
+ } from "fs/promises";
24210
+ import { dirname as dirname13, join as join16, relative as relative13, resolve as resolve14 } from "path";
23802
24211
  var UPDATE_MANIFEST_FILE = "update.json";
23803
24212
  var UPDATE_FILES_DIRECTORY = "files";
23804
24213
  var sha2564 = (value) => createHash6("sha256").update(value).digest("hex");
23805
24214
  var listFiles = async (root, directory = root) => {
23806
- const entries = await readdir6(directory, { withFileTypes: true });
24215
+ const entries = await readdir7(directory, { withFileTypes: true });
23807
24216
  const paths = await Promise.all(entries.map(async (entry) => {
23808
- const path = join15(directory, entry.name);
24217
+ const path = join16(directory, entry.name);
23809
24218
  if (entry.isDirectory())
23810
24219
  return listFiles(root, path);
23811
24220
  if (!entry.isFile())
23812
24221
  throw new TypeError("Mobile updates cannot contain links or special files.");
23813
- return [relative12(root, path).replaceAll("\\", "/")];
24222
+ return [relative13(root, path).replaceAll("\\", "/")];
23814
24223
  }));
23815
24224
  return paths.flat().sort((left, right) => left.localeCompare(right));
23816
24225
  };
23817
24226
  var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
23818
- const source = join15(root, path);
24227
+ const source = join16(root, path);
23819
24228
  const [metadata, contents] = await Promise.all([
23820
- stat7(source),
23821
- readFile19(source)
24229
+ stat8(source),
24230
+ readFile20(source)
23822
24231
  ]);
23823
24232
  return { bytes: metadata.size, path, sha256: sha2564(contents) };
23824
24233
  }));
23825
24234
  var releaseIdFor = (value) => `amu_${sha2564(canonicalizeAbsoluteMobileUpdate(value))}`;
23826
24235
  var buildAbsoluteMobileUpdate = async (options) => {
23827
- const bundleDirectory = resolve13(options.bundleDirectory);
23828
- const outputRoot = resolve13(options.outputDirectory);
24236
+ const bundleDirectory = resolve14(options.bundleDirectory);
24237
+ const outputRoot = resolve14(options.outputDirectory);
23829
24238
  if (outputRoot === bundleDirectory || outputRoot.startsWith(`${bundleDirectory}/`))
23830
24239
  throw new TypeError("Mobile update output must be outside the embedded bundle.");
23831
24240
  const paths = await listFiles(bundleDirectory);
@@ -23853,30 +24262,30 @@ var buildAbsoluteMobileUpdate = async (options) => {
23853
24262
  value: signature.toString("base64")
23854
24263
  }
23855
24264
  });
23856
- await mkdir14(outputRoot, { recursive: true });
23857
- const outputDirectory = join15(outputRoot, manifest.releaseId);
23858
- const staging = await mkdtemp5(join15(outputRoot, ".stage-"));
24265
+ await mkdir15(outputRoot, { recursive: true });
24266
+ const outputDirectory = join16(outputRoot, manifest.releaseId);
24267
+ const staging = await mkdtemp6(join16(outputRoot, ".stage-"));
23859
24268
  try {
23860
- await cp(bundleDirectory, join15(staging, UPDATE_FILES_DIRECTORY), {
24269
+ await cp(bundleDirectory, join16(staging, UPDATE_FILES_DIRECTORY), {
23861
24270
  force: true,
23862
24271
  recursive: true
23863
24272
  });
23864
- await writeFile15(join15(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
24273
+ await writeFile16(join16(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
23865
24274
  `);
23866
- await rename11(staging, outputDirectory);
24275
+ await rename12(staging, outputDirectory);
23867
24276
  } catch (error) {
23868
- await rm8(staging, { force: true, recursive: true });
24277
+ await rm9(staging, { force: true, recursive: true });
23869
24278
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
23870
24279
  throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
23871
24280
  throw error;
23872
24281
  }
23873
24282
  return {
23874
24283
  manifest,
23875
- manifestPath: join15(outputDirectory, UPDATE_MANIFEST_FILE),
24284
+ manifestPath: join16(outputDirectory, UPDATE_MANIFEST_FILE),
23876
24285
  outputDirectory
23877
24286
  };
23878
24287
  };
23879
- var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile19(join15(resolve13(directory), UPDATE_MANIFEST_FILE), "utf8")));
24288
+ var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile20(join16(resolve14(directory), UPDATE_MANIFEST_FILE), "utf8")));
23880
24289
  var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
23881
24290
  const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
23882
24291
  const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
@@ -23886,8 +24295,8 @@ var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
23886
24295
  };
23887
24296
 
23888
24297
  // src/mobile/expoUpdate.ts
23889
- import { access as access13, mkdir as mkdir15, readFile as readFile20, writeFile as writeFile16 } from "fs/promises";
23890
- import { dirname as dirname13, extname as extname3, join as join16 } from "path";
24298
+ import { access as access14, mkdir as mkdir16, readFile as readFile21, writeFile as writeFile17 } from "fs/promises";
24299
+ import { dirname as dirname14, extname as extname3, join as join17 } from "path";
23891
24300
  var ABSOLUTE_EXPO_UPDATE_DESCRIPTOR = "_absolute/expo-update.json";
23892
24301
  var ABSOLUTE_EXPO_UPDATE_FORMAT = 1;
23893
24302
  var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
@@ -23953,7 +24362,7 @@ var parseAbsoluteExpoUpdateDescriptor = (value) => {
23953
24362
  };
23954
24363
  };
23955
24364
  var finalizeAbsoluteExpoUpdateExport = async (options) => {
23956
- const metadata = JSON.parse(await readFile20(join16(options.exportDirectory, "metadata.json"), "utf8"));
24365
+ const metadata = JSON.parse(await readFile21(join17(options.exportDirectory, "metadata.json"), "utf8"));
23957
24366
  if (!object2(metadata) || !object2(metadata.fileMetadata))
23958
24367
  throw new TypeError("Expo export metadata.json is invalid.");
23959
24368
  const descriptor = parseAbsoluteExpoUpdateDescriptor({
@@ -23964,30 +24373,30 @@ var finalizeAbsoluteExpoUpdateExport = async (options) => {
23964
24373
  runtimeVersion: options.runtimeVersion
23965
24374
  });
23966
24375
  const referenced = Object.values(descriptor.platforms).flatMap((entry) => entry ? [entry.launchAsset, ...entry.assets] : []);
23967
- await Promise.all(referenced.map(({ path }) => access13(join16(options.exportDirectory, path)).catch(() => {
24376
+ await Promise.all(referenced.map(({ path }) => access14(join17(options.exportDirectory, path)).catch(() => {
23968
24377
  throw new TypeError(`Expo export metadata references missing asset ${path}.`);
23969
24378
  })));
23970
- const destination = join16(options.exportDirectory, ABSOLUTE_EXPO_UPDATE_DESCRIPTOR);
23971
- await mkdir15(dirname13(destination), { recursive: true });
23972
- await writeFile16(destination, `${JSON.stringify(descriptor, null, "\t")}
24379
+ const destination = join17(options.exportDirectory, ABSOLUTE_EXPO_UPDATE_DESCRIPTOR);
24380
+ await mkdir16(dirname14(destination), { recursive: true });
24381
+ await writeFile17(destination, `${JSON.stringify(descriptor, null, "\t")}
23973
24382
  `);
23974
24383
  return { descriptor, path: destination };
23975
24384
  };
23976
24385
 
23977
24386
  // src/mobile/expoCodeSigning.ts
23978
24387
  var import_code_signing_certificates = __toESM(require_main(), 1);
23979
- import { access as access14, mkdir as mkdir16, writeFile as writeFile17 } from "fs/promises";
23980
- import { dirname as dirname14, isAbsolute as isAbsolute8, relative as relative13, resolve as resolve14, sep as sep9 } from "path";
23981
- var exists6 = async (path) => access14(path).then(() => true).catch(() => false);
24388
+ import { access as access15, mkdir as mkdir17, writeFile as writeFile18 } from "fs/promises";
24389
+ import { dirname as dirname15, isAbsolute as isAbsolute9, relative as relative14, resolve as resolve15, sep as sep10 } from "path";
24390
+ var exists6 = async (path) => access15(path).then(() => true).catch(() => false);
23982
24391
  var inside4 = (root, path) => {
23983
- const location = relative13(root, path);
23984
- return location === "" || location !== ".." && !location.startsWith(`..${sep9}`) && !isAbsolute8(location);
24392
+ const location = relative14(root, path);
24393
+ return location === "" || location !== ".." && !location.startsWith(`..${sep10}`) && !isAbsolute9(location);
23985
24394
  };
23986
24395
  var generateAbsoluteExpoCodeSigning = async (options) => {
23987
- const root = resolve14(options.projectRoot);
23988
- const certificatePath = resolve14(root, options.certificatePath);
23989
- const privateKeyPath = resolve14(root, options.privateKeyPath);
23990
- const publicKeyPath = resolve14(root, options.publicKeyPath ?? resolve14(dirname14(privateKeyPath), "public-key.pem"));
24396
+ const root = resolve15(options.projectRoot);
24397
+ const certificatePath = resolve15(root, options.certificatePath);
24398
+ const privateKeyPath = resolve15(root, options.privateKeyPath);
24399
+ const publicKeyPath = resolve15(root, options.publicKeyPath ?? resolve15(dirname15(privateKeyPath), "public-key.pem"));
23991
24400
  if (!inside4(root, certificatePath))
23992
24401
  throw new TypeError("Expo code-signing certificate must be written inside the project so store builds can embed it.");
23993
24402
  if (inside4(root, privateKeyPath) || inside4(root, publicKeyPath))
@@ -24017,11 +24426,11 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
24017
24426
  import_code_signing_certificates.validateSelfSignedCertificate(certificate, keyPair);
24018
24427
  const { privateKeyPEM, publicKeyPEM } = import_code_signing_certificates.convertKeyPairToPEM(keyPair);
24019
24428
  const certificatePem = import_code_signing_certificates.convertCertificateToCertificatePEM(certificate);
24020
- await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) => mkdir16(dirname14(path), { recursive: true })));
24429
+ await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) => mkdir17(dirname15(path), { recursive: true })));
24021
24430
  await Promise.all([
24022
- writeFile17(certificatePath, certificatePem, { flag: "wx", mode: 420 }),
24023
- writeFile17(privateKeyPath, privateKeyPEM, { flag: "wx", mode: 384 }),
24024
- writeFile17(publicKeyPath, publicKeyPEM, { flag: "wx", mode: 420 })
24431
+ writeFile18(certificatePath, certificatePem, { flag: "wx", mode: 420 }),
24432
+ writeFile18(privateKeyPath, privateKeyPEM, { flag: "wx", mode: 384 }),
24433
+ writeFile18(publicKeyPath, publicKeyPEM, { flag: "wx", mode: 420 })
24025
24434
  ]);
24026
24435
  return {
24027
24436
  certificatePath,
@@ -24032,8 +24441,8 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
24032
24441
  };
24033
24442
 
24034
24443
  // src/mobile/updatePublisher.ts
24035
- import { access as access15 } from "fs/promises";
24036
- import { isAbsolute as isAbsolute9, relative as relative14, resolve as resolve15, sep as sep10 } from "path";
24444
+ import { access as access16 } from "fs/promises";
24445
+ import { isAbsolute as isAbsolute10, relative as relative15, resolve as resolve16, sep as sep11 } from "path";
24037
24446
  import { pathToFileURL as pathToFileURL3 } from "url";
24038
24447
  var lifecycleMethod = (publisher, name) => {
24039
24448
  const method = publisher[name];
@@ -24141,17 +24550,17 @@ var pruneAbsoluteMobileUpdates = async (options) => {
24141
24550
  };
24142
24551
  var object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
24143
24552
  var isPublisher2 = (value) => object3(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
24144
- var projectPath3 = (projectRoot, requested, label) => {
24145
- const root = resolve15(projectRoot);
24146
- const path = resolve15(root, requested);
24147
- const projectRelative = relative14(root, path);
24148
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep10}`) || isAbsolute9(projectRelative))
24553
+ var projectPath4 = (projectRoot, requested, label) => {
24554
+ const root = resolve16(projectRoot);
24555
+ const path = resolve16(root, requested);
24556
+ const projectRelative = relative15(root, path);
24557
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep11}`) || isAbsolute10(projectRelative))
24149
24558
  throw new TypeError(`${label} must remain inside the project.`);
24150
24559
  return path;
24151
24560
  };
24152
24561
  var loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
24153
- const modulePath = projectPath3(projectRoot, requestedModulePath, "mobile update registry");
24154
- await access15(modulePath).catch(() => {
24562
+ const modulePath = projectPath4(projectRoot, requestedModulePath, "mobile update registry");
24563
+ await access16(modulePath).catch(() => {
24155
24564
  throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
24156
24565
  });
24157
24566
  const loaded = await import(pathToFileURL3(modulePath).href);
@@ -24173,7 +24582,7 @@ var promoteAbsoluteMobileUpdate = async (options) => {
24173
24582
  return result;
24174
24583
  };
24175
24584
  var publishAbsoluteMobileUpdate = async (options) => {
24176
- const releaseDirectory = projectPath3(options.projectRoot, options.releaseDirectory, "mobile update release directory");
24585
+ const releaseDirectory = projectPath4(options.projectRoot, options.releaseDirectory, "mobile update release directory");
24177
24586
  const manifest = await readAbsoluteMobileUpdate(releaseDirectory);
24178
24587
  const result = await options.publisher.publishUpdate({
24179
24588
  manifest,
@@ -24206,10 +24615,10 @@ var rollbackAbsoluteMobileUpdate = async (options) => {
24206
24615
  var NOT_FOUND4 = -1;
24207
24616
  var ANDROID_EMULATOR_BOOT_POLL_MS = 500;
24208
24617
  var ANDROID_EMULATOR_BOOT_TIMEOUT_MS = 180000;
24209
- var isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
24618
+ var isRecord10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
24210
24619
  var expoPublicConfig = (config) => {
24211
24620
  const result = { ...config };
24212
- if (!isRecord9(result.updates))
24621
+ if (!isRecord10(result.updates))
24213
24622
  return result;
24214
24623
  const {
24215
24624
  codeSigningCertificate: _codeSigningCertificate,
@@ -24249,13 +24658,13 @@ var CAPACITOR_SYNC_PACKAGE_SPECS = [
24249
24658
  ];
24250
24659
  var packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@"));
24251
24660
  var directProjectPackages = async (projectRoot) => {
24252
- const manifest = JSON.parse(await readFile21(join17(projectRoot, "package.json"), "utf8"));
24253
- if (!isRecord9(manifest))
24661
+ const manifest = JSON.parse(await readFile22(join18(projectRoot, "package.json"), "utf8"));
24662
+ if (!isRecord10(manifest))
24254
24663
  throw new TypeError("Application package.json must contain an object.");
24255
24664
  const names = new Set;
24256
24665
  for (const field2 of ["dependencies", "devDependencies"]) {
24257
24666
  const dependencies = Reflect.get(manifest, field2);
24258
- if (isRecord9(dependencies))
24667
+ if (isRecord10(dependencies))
24259
24668
  for (const name of Object.keys(dependencies))
24260
24669
  names.add(name);
24261
24670
  }
@@ -24263,8 +24672,8 @@ var directProjectPackages = async (projectRoot) => {
24263
24672
  };
24264
24673
  var resolvedPackageVersion = async (projectRoot, packageName) => {
24265
24674
  try {
24266
- const manifest = JSON.parse(await readFile21(join17(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
24267
- return isRecord9(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
24675
+ const manifest = JSON.parse(await readFile22(join18(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
24676
+ return isRecord10(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
24268
24677
  } catch {
24269
24678
  return;
24270
24679
  }
@@ -24327,9 +24736,9 @@ var runCapacitor = async (projectRoot, args) => {
24327
24736
  };
24328
24737
  var runCapacitorForPlatforms = (projectRoot, command, platforms) => platforms.reduce((pending, platform2) => pending.then(() => runCapacitor(projectRoot, [command, platform2])), Promise.resolve());
24329
24738
  var expoExecutable = async (project) => {
24330
- const executable = join17(project, "node_modules", ".bin", "expo");
24739
+ const executable = join18(project, "node_modules", ".bin", "expo");
24331
24740
  try {
24332
- await access16(executable);
24741
+ await access17(executable);
24333
24742
  return executable;
24334
24743
  } catch {
24335
24744
  throw new TypeError("Expo dependencies are not installed in the generated shell. Run `absolute mobile init --yes`.");
@@ -24361,14 +24770,14 @@ var runExpo = async (project, args, options = {}) => {
24361
24770
  };
24362
24771
  var ensureExpoPackages = async (project, args) => {
24363
24772
  try {
24364
- const manifest = JSON.parse(await readFile21(join17(project, "package.json"), "utf8"));
24773
+ const manifest = JSON.parse(await readFile22(join18(project, "package.json"), "utf8"));
24365
24774
  const dependencies = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "dependencies") : undefined;
24366
24775
  if (typeof dependencies !== "object" || dependencies === null || Array.isArray(dependencies))
24367
24776
  throw new TypeError("Generated Expo dependencies are invalid.");
24368
24777
  await Promise.all(Object.entries(dependencies).map(async ([name, expected]) => {
24369
24778
  if (typeof expected !== "string")
24370
24779
  throw new TypeError("Generated Expo dependency version is invalid.");
24371
- const installed = JSON.parse(await readFile21(join17(project, "node_modules", name, "package.json"), "utf8"));
24780
+ const installed = JSON.parse(await readFile22(join18(project, "node_modules", name, "package.json"), "utf8"));
24372
24781
  const actual = typeof installed === "object" && installed !== null ? Reflect.get(installed, "version") : undefined;
24373
24782
  if (typeof actual !== "string")
24374
24783
  throw new TypeError("Installed Expo dependency version is invalid.");
@@ -24566,7 +24975,7 @@ var sync = async (args) => {
24566
24975
  };
24567
24976
  var associations = async (args) => {
24568
24977
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
24569
- const outputDirectory = resolve16(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
24978
+ const outputDirectory = resolve17(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
24570
24979
  if (args.includes("--verify")) {
24571
24980
  const result2 = await verifyAbsoluteMobileAssociationFiles(mobile);
24572
24981
  console.log(`Verified ${result2.results.length} hosted association files`);
@@ -24618,7 +25027,7 @@ var generateGithubCi = async (args) => {
24618
25027
  const publicResult = {
24619
25028
  changed: result.changed,
24620
25029
  format: result.format,
24621
- path: relative15(projectRoot, result.path).replaceAll("\\", "/"),
25030
+ path: relative16(projectRoot, result.path).replaceAll("\\", "/"),
24622
25031
  platforms: result.platforms,
24623
25032
  publishing: result.publishing,
24624
25033
  requiredSecrets: result.requiredSecrets
@@ -24649,15 +25058,15 @@ var createGithubCertificationVerification = async (args) => {
24649
25058
  GITHUB_WORKFLOW_REF: process.env.GITHUB_WORKFLOW_REF
24650
25059
  });
24651
25060
  const path = await writeAbsoluteMobileCertificationVerification(projectRoot, outputPath, verification);
24652
- console.log(relative15(projectRoot, path).replaceAll("\\", "/"));
25061
+ console.log(relative16(projectRoot, path).replaceAll("\\", "/"));
24653
25062
  return verification;
24654
25063
  };
24655
25064
  var dispatchGithubMobilePromotion = async (args) => {
24656
25065
  const [platform2] = args;
24657
25066
  if (platform2 !== "android" && platform2 !== "ios")
24658
25067
  throw new TypeError("Usage: absolute mobile ci promote <android|ios> --run-id id --certification directory-or-json [--channel name] [store options]");
24659
- const runId = valueAfter(args, "--run-id");
24660
- if (!runId || !/^[1-9][0-9]*$/u.test(runId))
25068
+ const runId2 = valueAfter(args, "--run-id");
25069
+ if (!runId2 || !/^[1-9][0-9]*$/u.test(runId2))
24661
25070
  throw new TypeError("mobile ci promote requires a numeric --run-id.");
24662
25071
  const requestedCertification = valueAfter(args, "--certification");
24663
25072
  if (!requestedCertification || requestedCertification.startsWith("-"))
@@ -24685,7 +25094,7 @@ var dispatchGithubMobilePromotion = async (args) => {
24685
25094
  "-f",
24686
25095
  `platform=${platform2}`,
24687
25096
  "-f",
24688
- `source_run_id=${runId}`,
25097
+ `source_run_id=${runId2}`,
24689
25098
  "-f",
24690
25099
  `certification_base64=${certificationBase64}`,
24691
25100
  "-f",
@@ -24714,7 +25123,85 @@ var dispatchGithubMobilePromotion = async (args) => {
24714
25123
  provider: "github-actions",
24715
25124
  storeTarget: platform2 === "android" ? playTrack !== "registry-only" : testflightGroup !== ""
24716
25125
  });
24717
- console.log(`Dispatched protected ${platform2} promotion for ${certification.release.releaseId} from workflow run ${runId}.`);
25126
+ console.log(`Dispatched protected ${platform2} promotion for ${certification.release.releaseId} from workflow run ${runId2}.`);
25127
+ };
25128
+ var githubMobileRunId = (args) => {
25129
+ const value = valueAfter(args, "--run-id");
25130
+ if (!value || !/^[1-9][0-9]*$/u.test(value))
25131
+ throw new TypeError("mobile ci command requires a numeric --run-id.");
25132
+ return value;
25133
+ };
25134
+ var renderGithubMobileRunStatus = (status) => {
25135
+ console.log(`GitHub run ${status.runId}: ${status.status}${status.conclusion ? ` / ${status.conclusion}` : ""}`);
25136
+ console.log(`Workflow: ${status.workflowName}`);
25137
+ console.log(`Commit: ${status.headSha} (${status.headBranch})`);
25138
+ status.jobs.forEach((job) => console.log(` ${job.name}: ${job.status}${job.conclusion ? ` / ${job.conclusion}` : ""}`));
25139
+ console.log(status.url);
25140
+ };
25141
+ var inspectGithubMobileRun = async (args) => {
25142
+ const projectRoot = process.cwd();
25143
+ const requestedRunId = githubMobileRunId(args);
25144
+ const repository = valueAfter(args, "--repo");
25145
+ if (args.includes("--watch") && !args.includes("--json"))
25146
+ console.log(`Watching GitHub run ${requestedRunId} until completion\u2026`);
25147
+ const status = args.includes("--watch") ? await watchAbsoluteMobileCiRun({
25148
+ projectRoot,
25149
+ ...repository ? { repository } : {},
25150
+ runId: requestedRunId
25151
+ }) : await inspectAbsoluteMobileCiRun({
25152
+ projectRoot,
25153
+ ...repository ? { repository } : {},
25154
+ runId: requestedRunId
25155
+ });
25156
+ sendTelemetryEvent("mobile:ci-status", {
25157
+ conclusion: status.conclusion ?? "pending",
25158
+ provider: "github-actions",
25159
+ status: status.status,
25160
+ watched: args.includes("--watch")
25161
+ });
25162
+ if (args.includes("--json"))
25163
+ console.log(JSON.stringify(status, null, 2));
25164
+ else
25165
+ renderGithubMobileRunStatus(status);
25166
+ return status;
25167
+ };
25168
+ var renderGithubMobilePromotionAudit = (result, projectRoot) => {
25169
+ console.log(`\u2713 Verified ${result.audit.platform} promotion ${result.audit.github.promotionRunId} for ${result.audit.release.releaseId}.`);
25170
+ console.log(`Audit: ${relative16(projectRoot, result.directory)}`);
25171
+ };
25172
+ var auditGithubMobilePromotion = async (args) => {
25173
+ const projectRoot = process.cwd();
25174
+ const requestedRunId = githubMobileRunId(args);
25175
+ const repository = valueAfter(args, "--repo");
25176
+ const sourceRunId = valueAfter(args, "--source-run-id");
25177
+ const startedAt = performance.now();
25178
+ try {
25179
+ const result = await auditAbsoluteMobileCiPromotion({
25180
+ outputDirectory: valueAfter(args, "--outdir"),
25181
+ projectRoot,
25182
+ ...repository ? { repository } : {},
25183
+ runId: requestedRunId,
25184
+ ...sourceRunId ? { sourceRunId } : {}
25185
+ });
25186
+ sendTelemetryEvent("mobile:ci-promotion-audit", {
25187
+ durationMs: Math.round(performance.now() - startedAt),
25188
+ platform: result.audit.platform,
25189
+ provider: "github-actions",
25190
+ success: true
25191
+ });
25192
+ if (args.includes("--json"))
25193
+ console.log(JSON.stringify(result.audit, null, 2));
25194
+ else
25195
+ renderGithubMobilePromotionAudit(result, projectRoot);
25196
+ return result;
25197
+ } catch (error) {
25198
+ sendTelemetryEvent("mobile:ci-promotion-audit", {
25199
+ durationMs: Math.round(performance.now() - startedAt),
25200
+ provider: "github-actions",
25201
+ success: false
25202
+ });
25203
+ throw error;
25204
+ }
24718
25205
  };
24719
25206
  var doctorMark = (status) => {
24720
25207
  if (status === "pass")
@@ -24812,9 +25299,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
24812
25299
  temporaryDirectory: undefined
24813
25300
  };
24814
25301
  await ensureExpoPackages(mobile.nativeProjectDirectory, options.args);
24815
- const temporaryParent = join17(options.projectRoot, ".absolutejs", "mobile", "expo-update-exports");
24816
- await mkdir17(temporaryParent, { recursive: true });
24817
- const temporaryDirectory = await mkdtemp6(join17(temporaryParent, ".stage-"));
25302
+ const temporaryParent = join18(options.projectRoot, ".absolutejs", "mobile", "expo-update-exports");
25303
+ await mkdir18(temporaryParent, { recursive: true });
25304
+ const temporaryDirectory = await mkdtemp7(join18(temporaryParent, ".stage-"));
24818
25305
  try {
24819
25306
  await runExpo(mobile.nativeProjectDirectory, [
24820
25307
  "export",
@@ -24826,9 +25313,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
24826
25313
  temporaryDirectory,
24827
25314
  "--clear"
24828
25315
  ], { production: true });
24829
- const generatedApp = JSON.parse(await readFile21(join17(mobile.nativeProjectDirectory, "app.json"), "utf8"));
24830
- const expoConfig = isRecord9(generatedApp) ? generatedApp.expo : undefined;
24831
- if (!isRecord9(expoConfig))
25316
+ const generatedApp = JSON.parse(await readFile22(join18(mobile.nativeProjectDirectory, "app.json"), "utf8"));
25317
+ const expoConfig = isRecord10(generatedApp) ? generatedApp.expo : undefined;
25318
+ if (!isRecord10(expoConfig))
24832
25319
  throw new TypeError("Generated Expo app configuration is invalid.");
24833
25320
  const publicExpoConfig = expoPublicConfig(expoConfig);
24834
25321
  const computedRuntime = resolveAbsoluteMobileUpdateRuntime(mobile, options.projectRoot).fingerprint;
@@ -24844,7 +25331,7 @@ var prepareExpoMobileUpdateExport = async (options) => {
24844
25331
  temporaryDirectory
24845
25332
  };
24846
25333
  } catch (error) {
24847
- await rm9(temporaryDirectory, { force: true, recursive: true });
25334
+ await rm10(temporaryDirectory, { force: true, recursive: true });
24848
25335
  throw error;
24849
25336
  }
24850
25337
  };
@@ -24869,11 +25356,11 @@ var buildMobileUpdate = async (args) => {
24869
25356
  let expoExportDirectory;
24870
25357
  try {
24871
25358
  await start(mobileBuildServerEntry(args), valueAfter(args, "--web-outdir"), configPath, { prepareOnly: true });
24872
- const embedded = JSON.parse(await readFile21(join17(mobile.bundleDirectory, "absolute-mobile-manifest.json"), "utf8"));
24873
- const runtimeFingerprint = isRecord9(embedded) ? embedded.nativeRuntime : undefined;
25359
+ const embedded = JSON.parse(await readFile22(join18(mobile.bundleDirectory, "absolute-mobile-manifest.json"), "utf8"));
25360
+ const runtimeFingerprint = isRecord10(embedded) ? embedded.nativeRuntime : undefined;
24874
25361
  if (typeof runtimeFingerprint !== "string" || !/^[a-f0-9]{64}$/u.test(runtimeFingerprint))
24875
25362
  throw new TypeError("Prepared mobile bundle is missing its native runtime fingerprint.");
24876
- const privateKey = await readFile21(resolve16(projectRoot, signingKeyPath));
25363
+ const privateKey = await readFile22(resolve17(projectRoot, signingKeyPath));
24877
25364
  const configuredPublicKey = Buffer.from(mobile.updates.publicKeys[keyId], "base64");
24878
25365
  const derivedPublicKey = createPublicKey2(privateKey).export({
24879
25366
  format: "der",
@@ -24894,7 +25381,7 @@ var buildMobileUpdate = async (args) => {
24894
25381
  channel: mobile.updates.channel,
24895
25382
  classification,
24896
25383
  keyId,
24897
- outputDirectory: valueAfter(args, "--outdir") ?? join17(projectRoot, ".absolutejs", "mobile", "updates"),
25384
+ outputDirectory: valueAfter(args, "--outdir") ?? join18(projectRoot, ".absolutejs", "mobile", "updates"),
24898
25385
  privateKey,
24899
25386
  runtimeFingerprint
24900
25387
  });
@@ -24906,7 +25393,7 @@ var buildMobileUpdate = async (args) => {
24906
25393
  return result;
24907
25394
  } finally {
24908
25395
  if (expoExportDirectory)
24909
- await rm9(expoExportDirectory, { force: true, recursive: true });
25396
+ await rm10(expoExportDirectory, { force: true, recursive: true });
24910
25397
  sendTelemetryEvent("mobile:update-build", {
24911
25398
  classification: classification ?? "invalid",
24912
25399
  durationMs: Math.round(performance.now() - startedAt),
@@ -24936,7 +25423,7 @@ var generateExpoUpdateSigning = async (args) => {
24936
25423
  ...valueAfter(args, "--public-key") ? { publicKeyPath: valueAfter(args, "--public-key") } : {},
24937
25424
  ...validityYears === undefined ? {} : { validityYears }
24938
25425
  });
24939
- const certificatePath = relative15(projectRoot, result.certificatePath).replaceAll("\\", "/");
25426
+ const certificatePath = relative16(projectRoot, result.certificatePath).replaceAll("\\", "/");
24940
25427
  sendTelemetryEvent("mobile:update-code-signing-generated", {
24941
25428
  engine: "expo",
24942
25429
  validityYears: validityYears ?? 10
@@ -24998,7 +25485,7 @@ var provisionMobileUpdate = async (args) => {
24998
25485
  publicKeys: mobile.updates.publicKeys,
24999
25486
  storage: requestedStorage
25000
25487
  });
25001
- console.log(`Provisioned ${requestedStorage === "local" ? "local development" : "durable S3-compatible"} mobile update storage in ${relative15(projectRoot, path).replaceAll("\\", "/")}.`);
25488
+ console.log(`Provisioned ${requestedStorage === "local" ? "local development" : "durable S3-compatible"} mobile update storage in ${relative16(projectRoot, path).replaceAll("\\", "/")}.`);
25002
25489
  if (requestedStorage === "local")
25003
25490
  console.log("Production release checks will require durable storage. Re-run with --storage s3 --force before deploying.");
25004
25491
  else
@@ -25475,7 +25962,7 @@ var buildAndroid = async (args, prepareVersionCode) => {
25475
25962
  const durationMs = Math.round(performance.now() - startedAt);
25476
25963
  console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} Android App Bundle in ${getDurationString(durationMs)}.`);
25477
25964
  console.log(`Artifact: ${release.artifactPath}`);
25478
- console.log(`Metadata: ${join17(release.releaseRoot, "release.json")}`);
25965
+ console.log(`Metadata: ${join18(release.releaseRoot, "release.json")}`);
25479
25966
  return release;
25480
25967
  } finally {
25481
25968
  sendTelemetryEvent("mobile:android-release-build", {
@@ -25698,7 +26185,7 @@ var buildIos = async (args, prepareBuildNumber) => {
25698
26185
  console.log(`Artifact: ${release.artifactPath}`);
25699
26186
  if (release.registeredArtifactPath)
25700
26187
  console.log(`Registered-device artifact: ${release.registeredArtifactPath}`);
25701
- console.log(`Metadata: ${join17(release.releaseRoot, "release.json")}`);
26188
+ console.log(`Metadata: ${join18(release.releaseRoot, "release.json")}`);
25702
26189
  return release;
25703
26190
  } finally {
25704
26191
  stopListeningForCancellation();
@@ -25849,7 +26336,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
25849
26336
  checks.push({
25850
26337
  id: "sync.storage-schema",
25851
26338
  label: `Offline schema ${schema.components.map((component) => `${component.id}@${component.version}`).join(", ")}`,
25852
- path: join17(projectRoot, "package.json"),
26339
+ path: join18(projectRoot, "package.json"),
25853
26340
  platform: "host",
25854
26341
  status: "pass"
25855
26342
  });
@@ -25857,7 +26344,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
25857
26344
  checks.push({
25858
26345
  id: "sync.storage-schema",
25859
26346
  label: "Offline schema metadata is invalid",
25860
- path: join17(projectRoot, "package.json"),
26347
+ path: join18(projectRoot, "package.json"),
25861
26348
  platform: "host",
25862
26349
  remediation: error instanceof Error ? error.message : String(error),
25863
26350
  status: "fail"
@@ -25945,7 +26432,7 @@ var requireAndroidTestPort = (args, projectRoot) => {
25945
26432
  }
25946
26433
  return { https: args.includes("--https"), port };
25947
26434
  }
25948
- const instances = listLiveInstances().filter((instance2) => resolve16(instance2.cwd) === resolve16(projectRoot) && instance2.source === "dev" && instance2.port !== null);
26435
+ const instances = listLiveInstances().filter((instance2) => resolve17(instance2.cwd) === resolve17(projectRoot) && instance2.source === "dev" && instance2.port !== null);
25949
26436
  if (instances.length !== 1) {
25950
26437
  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>.");
25951
26438
  }
@@ -26039,8 +26526,8 @@ var selectOrStartAndroidReleaseEmulator = async (adb2, emulator, explicitSerial,
26039
26526
  return waitForBoot();
26040
26527
  };
26041
26528
  var safeArtifactRoot = (projectRoot, value) => {
26042
- const root = resolve16(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
26043
- if (root !== projectRoot && !root.startsWith(`${resolve16(projectRoot)}/`)) {
26529
+ const root = resolve17(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
26530
+ if (root !== projectRoot && !root.startsWith(`${resolve17(projectRoot)}/`)) {
26044
26531
  throw new TypeError("mobile test --artifacts must remain inside the project.");
26045
26532
  }
26046
26533
  return root;
@@ -26068,12 +26555,12 @@ var waitForRequestedAndroidHmr = async (args, session, checks, timeoutMs) => {
26068
26555
  });
26069
26556
  };
26070
26557
  var writeAndroidFailureArtifacts = async (options) => {
26071
- await mkdir17(options.artifactRoot, { recursive: true });
26072
- const screenshot = options.session ? await options.session.screenshot(join17(options.artifactRoot, "android-failure.png")).catch(() => {
26558
+ await mkdir18(options.artifactRoot, { recursive: true });
26559
+ const screenshot = options.session ? await options.session.screenshot(join18(options.artifactRoot, "android-failure.png")).catch(() => {
26073
26560
  return;
26074
26561
  }) : undefined;
26075
- const diagnosticPath = join17(options.artifactRoot, "android-failure.json");
26076
- await writeFile18(diagnosticPath, `${JSON.stringify({
26562
+ const diagnosticPath = join18(options.artifactRoot, "android-failure.json");
26563
+ await writeFile19(diagnosticPath, `${JSON.stringify({
26077
26564
  diagnostics: options.session?.diagnostics ?? [],
26078
26565
  error: options.error instanceof Error ? options.error.message : String(options.error),
26079
26566
  platform: "android",
@@ -26094,7 +26581,7 @@ var captureAndroidReleaseScreenshot = async (adb2, serial, destination) => {
26094
26581
  ]);
26095
26582
  if (exitCode !== 0)
26096
26583
  throw new Error(`Android release screenshot failed: ${stderr.trim() || `status ${exitCode}`}`);
26097
- await writeFile18(destination, bytes);
26584
+ await writeFile19(destination, bytes);
26098
26585
  return destination;
26099
26586
  };
26100
26587
  var printAndroidReleaseAcceptance = (result, json) => {
@@ -26148,7 +26635,7 @@ var testAndroidRelease = async (args, mobile, projectRoot) => {
26148
26635
  release,
26149
26636
  serial
26150
26637
  });
26151
- const screenshot = reportRoot ? await captureAndroidReleaseScreenshot(adb2, serial, join17(reportRoot, "android-release.png")) : undefined;
26638
+ const screenshot = reportRoot ? await captureAndroidReleaseScreenshot(adb2, serial, join18(reportRoot, "android-release.png")) : undefined;
26152
26639
  sendTelemetryEvent("mobile:android-release-conformance", {
26153
26640
  durationMs: result.durationMs,
26154
26641
  engine: result.engine,
@@ -26195,9 +26682,9 @@ var testAndroidRelease = async (args, mobile, projectRoot) => {
26195
26682
  platform: "android",
26196
26683
  success: false
26197
26684
  });
26198
- await mkdir17(artifactRoot, { recursive: true });
26199
- const diagnosticPath = join17(artifactRoot, "android-release-failure.json");
26200
- await writeFile18(diagnosticPath, `${JSON.stringify({
26685
+ await mkdir18(artifactRoot, { recursive: true });
26686
+ const diagnosticPath = join18(artifactRoot, "android-release-failure.json");
26687
+ await writeFile19(diagnosticPath, `${JSON.stringify({
26201
26688
  error: sanitizeNativeReportText(error instanceof Error ? error.message : String(error)),
26202
26689
  platform: "android",
26203
26690
  provider: mobile.engine,
@@ -26291,7 +26778,7 @@ var testAndroid = async (args) => {
26291
26778
  console.log(JSON.stringify(report, null, 2));
26292
26779
  else
26293
26780
  printAndroidTestReport(report);
26294
- const screenshot = reportRoot ? await session.screenshot(join17(artifactRoot, "android-emulator.png")) : undefined;
26781
+ const screenshot = reportRoot ? await session.screenshot(join18(artifactRoot, "android-emulator.png")) : undefined;
26295
26782
  await writeRequestedAndroidReport({
26296
26783
  adb: adb2,
26297
26784
  args,
@@ -26360,14 +26847,14 @@ var requireIosTestContext = (args, projectRoot) => {
26360
26847
  const port = Number(explicit);
26361
26848
  if (!Number.isInteger(port) || port < 1 || port > 65535)
26362
26849
  throw new TypeError("mobile test --port must be a valid TCP port.");
26363
- const instance2 = listLiveInstances().find((candidate) => resolve16(candidate.cwd) === resolve16(projectRoot) && candidate.source === "dev" && candidate.port === port);
26850
+ const instance2 = listLiveInstances().find((candidate) => resolve17(candidate.cwd) === resolve17(projectRoot) && candidate.source === "dev" && candidate.port === port);
26364
26851
  return {
26365
26852
  https: instance2?.https ?? args.includes("--https"),
26366
26853
  instance: instance2,
26367
26854
  port
26368
26855
  };
26369
26856
  }
26370
- const instances = listLiveInstances().filter((instance2) => resolve16(instance2.cwd) === resolve16(projectRoot) && instance2.source === "dev" && instance2.port !== null);
26857
+ const instances = listLiveInstances().filter((instance2) => resolve17(instance2.cwd) === resolve17(projectRoot) && instance2.source === "dev" && instance2.port !== null);
26371
26858
  if (instances.length !== 1)
26372
26859
  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>.");
26373
26860
  const [instance] = instances;
@@ -26385,7 +26872,7 @@ var waitForIosHmrClient = async (options) => {
26385
26872
  return;
26386
26873
  });
26387
26874
  const status = response?.ok ? await response.json().catch(() => null) : null;
26388
- const targets = isRecord9(status) && isRecord9(status.connectedTargets) ? status.connectedTargets : undefined;
26875
+ const targets = isRecord10(status) && isRecord10(status.connectedTargets) ? status.connectedTargets : undefined;
26389
26876
  if (targets && typeof targets["capacitor-ios"] === "number" && targets["capacitor-ios"] > 0)
26390
26877
  return;
26391
26878
  await Bun.sleep(100);
@@ -26511,8 +26998,8 @@ var requireCapturedCommand = (command, label) => {
26511
26998
  return result;
26512
26999
  };
26513
27000
  var writeIosFailureArtifacts = async (options) => {
26514
- await mkdir17(options.artifactRoot, { recursive: true });
26515
- const screenshot = join17(options.artifactRoot, "ios-failure.png");
27001
+ await mkdir18(options.artifactRoot, { recursive: true });
27002
+ const screenshot = join18(options.artifactRoot, "ios-failure.png");
26516
27003
  const screenshotResult = captureCommand4([
26517
27004
  options.xcrun,
26518
27005
  "simctl",
@@ -26521,8 +27008,8 @@ var writeIosFailureArtifacts = async (options) => {
26521
27008
  "screenshot",
26522
27009
  screenshot
26523
27010
  ]);
26524
- const diagnosticPath = join17(options.artifactRoot, "ios-failure.json");
26525
- await writeFile18(diagnosticPath, `${JSON.stringify({
27011
+ const diagnosticPath = join18(options.artifactRoot, "ios-failure.json");
27012
+ await writeFile19(diagnosticPath, `${JSON.stringify({
26526
27013
  appId: options.appId,
26527
27014
  error: options.error instanceof Error ? options.error.message : String(options.error),
26528
27015
  platform: "ios",
@@ -26549,8 +27036,8 @@ var nativeReportRoot = (args, projectRoot, platform2) => {
26549
27036
  var absolutejsVersionForReport = async () => {
26550
27037
  let absolutejsVersion = process.env.ABSOLUTE_VERSION ?? "unknown";
26551
27038
  const versions = await Promise.all([
26552
- resolve16(import.meta.dir, "..", "..", "package.json"),
26553
- resolve16(import.meta.dir, "..", "..", "..", "package.json")
27039
+ resolve17(import.meta.dir, "..", "..", "package.json"),
27040
+ resolve17(import.meta.dir, "..", "..", "..", "package.json")
26554
27041
  ].map((candidate) => readPackageVersionForIosReport(candidate).catch(() => "unknown")));
26555
27042
  for (const version of versions) {
26556
27043
  if (version === "unknown")
@@ -26927,9 +27414,9 @@ var testIosRelease = async (args, mobile, projectRoot) => {
26927
27414
  remote: remoteProfile !== undefined,
26928
27415
  success: false
26929
27416
  });
26930
- await mkdir17(artifactRoot, { recursive: true });
26931
- const diagnosticPath = join17(artifactRoot, "ios-release-failure.json");
26932
- await writeFile18(diagnosticPath, `${JSON.stringify({
27417
+ await mkdir18(artifactRoot, { recursive: true });
27418
+ const diagnosticPath = join18(artifactRoot, "ios-release-failure.json");
27419
+ await writeFile19(diagnosticPath, `${JSON.stringify({
26933
27420
  error: sanitizeIosReportText(error instanceof Error ? error.message : String(error)),
26934
27421
  platform: "ios",
26935
27422
  provider: mobile.engine,
@@ -27022,8 +27509,8 @@ var testIos = async (args) => {
27022
27509
  mobile.appId
27023
27510
  ], "iOS app launch");
27024
27511
  await waitForIosHmrClient({ https, port, timeoutMs });
27025
- await mkdir17(artifactRoot, { recursive: true });
27026
- const screenshot = join17(artifactRoot, "ios-simulator.png");
27512
+ await mkdir18(artifactRoot, { recursive: true });
27513
+ const screenshot = join18(artifactRoot, "ios-simulator.png");
27027
27514
  requireCapturedCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
27028
27515
  const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
27029
27516
  const report = {
@@ -27240,6 +27727,10 @@ var runMobile = async (args) => {
27240
27727
  if (command === "ci") {
27241
27728
  if (args[1] === "promote")
27242
27729
  await dispatchGithubMobilePromotion(args.slice(2));
27730
+ else if (args[1] === "status")
27731
+ await inspectGithubMobileRun(args.slice(2));
27732
+ else if (args[1] === "audit")
27733
+ await auditGithubMobilePromotion(args.slice(2));
27243
27734
  else if (args[1] === "verification")
27244
27735
  await createGithubCertificationVerification(args.slice(2));
27245
27736
  else
@@ -27322,7 +27813,7 @@ var runMobile = async (args) => {
27322
27813
  await publishIos(args.slice(2));
27323
27814
  return;
27324
27815
  }
27325
- 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] | ci promote <android|ios> --run-id id --certification path [--channel name] [--play-track track|--testflight-group group] [--ref branch] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--remote name] [--registered-device-artifact] [--outdir dir] [--web-outdir dir] [--unsigned] | update provision [--storage local|s3] [--registry module] [--force] [--yes] | update signing generate --private-key path [--certificate path] [--public-key path] [--key-id id] [--common-name name] [--validity-years n] | update build [server-entry] --classification bug-fix|content|security --key-id id --signing-key path --within-submitted-purpose [--outdir dir] [--web-outdir dir] | update publish <release-directory> [--rollout fraction] [--registry module] | update promote --release id --rollout fraction [--registry module] | update rollback [--release id] [--registry module] | update status [--registry module] [--json] | update advance [--rollout fraction] [--registry module] [--json] | update pause|resume|cancel|reconcile [--registry module] [--json] | update storage [--retain count] [--min-age-days days] [--registry module] [--json] | update gc [--retain count] [--min-age-days days] [--grace-days days] [--apply] [--registry module] [--json] | publish android [server-entry] [--release release-dir] [--certification certification-dir] [--certification-attestation verification-json] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--release release-dir] [--certification certification-dir] [--certification-attestation verification-json] [--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]");
27816
+ 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] | ci promote <android|ios> --run-id id --certification path [--channel name] [--play-track track|--testflight-group group] [--ref branch] | ci status --run-id id [--watch] [--repo owner/name] [--json] | ci audit --run-id id [--source-run-id id] [--repo owner/name] [--outdir path] [--json] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--remote name] [--registered-device-artifact] [--outdir dir] [--web-outdir dir] [--unsigned] | update provision [--storage local|s3] [--registry module] [--force] [--yes] | update signing generate --private-key path [--certificate path] [--public-key path] [--key-id id] [--common-name name] [--validity-years n] | update build [server-entry] --classification bug-fix|content|security --key-id id --signing-key path --within-submitted-purpose [--outdir dir] [--web-outdir dir] | update publish <release-directory> [--rollout fraction] [--registry module] | update promote --release id --rollout fraction [--registry module] | update rollback [--release id] [--registry module] | update status [--registry module] [--json] | update advance [--rollout fraction] [--registry module] [--json] | update pause|resume|cancel|reconcile [--registry module] [--json] | update storage [--retain count] [--min-age-days days] [--registry module] [--json] | update gc [--retain count] [--min-age-days days] [--grace-days days] [--apply] [--registry module] [--json] | publish android [server-entry] [--release release-dir] [--certification certification-dir] [--certification-attestation verification-json] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--release release-dir] [--certification certification-dir] [--certification-attestation verification-json] [--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]");
27326
27817
  };
27327
27818
  export {
27328
27819
  runMobile