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

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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,13 @@ This file is generated by `absolute-changelog` from the entries in
6
6
  `changelog/`. Edit an entry, not this file — and add new ones under
7
7
  `changelog/unreleased/`.
8
8
 
9
+ ## 0.20.0-beta.112 — 2026-09-17
10
+
11
+ ### Added
12
+
13
+ - **Mobile CI promotions correlate, watch, and audit the exact dispatched workflow run** (`ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT`, `ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT`, `AbsoluteMobileCiRunStatus`, `AbsoluteMobilePromotionAudit`, `AbsoluteMobilePromotionContext`, `AbsoluteMobilePromotionRun`, `discoverAbsoluteMobilePromotionRun`, `parseAbsoluteMobilePromotionRunList`)
14
+ The promotion CLI assigns each protected dispatch a unique non-secret identity, discovers only the GitHub Actions run carrying that identity, returns its run ID and URL, optionally waits for completion, and can chain an independent local audit without racing concurrent promotions; retained format-1 audit context remains readable.
15
+
9
16
  ## 0.20.0-beta.111 — 2026-09-17
10
17
 
11
18
  ### Added
package/changelog.json CHANGED
@@ -2,6 +2,27 @@
2
2
  "contract": 1,
3
3
  "name": "@absolutejs/absolute",
4
4
  "releases": [
5
+ {
6
+ "changes": [
7
+ {
8
+ "detail": "The promotion CLI assigns each protected dispatch a unique non-secret identity, discovers only the GitHub Actions run carrying that identity, returns its run ID and URL, optionally waits for completion, and can chain an independent local audit without racing concurrent promotions; retained format-1 audit context remains readable.",
9
+ "kind": "added",
10
+ "summary": "Mobile CI promotions correlate, watch, and audit the exact dispatched workflow run",
11
+ "symbols": [
12
+ "ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT",
13
+ "ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT",
14
+ "AbsoluteMobileCiRunStatus",
15
+ "AbsoluteMobilePromotionAudit",
16
+ "AbsoluteMobilePromotionContext",
17
+ "AbsoluteMobilePromotionRun",
18
+ "discoverAbsoluteMobilePromotionRun",
19
+ "parseAbsoluteMobilePromotionRunList"
20
+ ]
21
+ }
22
+ ],
23
+ "date": "2026-09-17",
24
+ "version": "0.20.0-beta.112"
25
+ },
5
26
  {
6
27
  "changes": [
7
28
  {
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  var __require = import.meta.require;
3
3
 
4
- // .angular-partial-tmp-gyyIkV/src/core/streamingSlotRegistrar.ts
4
+ // .angular-partial-tmp-Pjgrp8/src/core/streamingSlotRegistrar.ts
5
5
  var STREAMING_SLOT_REGISTRAR_KEY = Symbol.for("absolutejs.streamingSlotRegistrar");
6
6
  var STREAMING_SLOT_WARNING_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotWarningController");
7
7
  var STREAMING_SLOT_COLLECTION_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotCollectionController");
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  var __require = import.meta.require;
3
3
 
4
- // .angular-partial-tmp-gyyIkV/src/core/streamingSlotRegistrar.ts
4
+ // .angular-partial-tmp-Pjgrp8/src/core/streamingSlotRegistrar.ts
5
5
  var STREAMING_SLOT_REGISTRAR_KEY = Symbol.for("absolutejs.streamingSlotRegistrar");
6
6
  var STREAMING_SLOT_WARNING_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotWarningController");
7
7
  var STREAMING_SLOT_COLLECTION_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotCollectionController");
@@ -48,7 +48,7 @@ var warnMissingStreamingSlotCollector = (primitiveName) => {
48
48
  getWarningController()?.maybeWarn(primitiveName);
49
49
  };
50
50
 
51
- // .angular-partial-tmp-gyyIkV/src/core/streamingSlotRegistry.ts
51
+ // .angular-partial-tmp-Pjgrp8/src/core/streamingSlotRegistry.ts
52
52
  var STREAMING_SLOT_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotAsyncLocalStorage");
53
53
  var isObjectRecord2 = (value) => Boolean(value) && typeof value === "object";
54
54
  var isAsyncLocalStorage = (value) => isObjectRecord2(value) && ("getStore" in value) && typeof value.getStore === "function" && ("run" in value) && typeof value.run === "function";
package/dist/cli/index.js CHANGED
@@ -203,7 +203,7 @@ if (command === "dev") {
203
203
  sendTelemetryEvent("cli:command", {
204
204
  command: `mobile:${workspaceCommand ?? "unknown"}`
205
205
  });
206
- const { runMobile } = await import("./mobile-k7gpwfs8.js");
206
+ const { runMobile } = await import("./mobile-d5wbqynh.js");
207
207
  try {
208
208
  await runMobile(args);
209
209
  } catch (error) {
@@ -16786,7 +16786,7 @@ import {
16786
16786
  rm as rm10,
16787
16787
  writeFile as writeFile19
16788
16788
  } from "fs/promises";
16789
- import { createPublicKey as createPublicKey2 } from "crypto";
16789
+ import { createPublicKey as createPublicKey2, randomUUID } from "crypto";
16790
16790
  import { join as join18, relative as relative16, resolve as resolve17 } from "path";
16791
16791
  import { createInterface } from "readline/promises";
16792
16792
 
@@ -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 = 3;
22985
+ var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 4;
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([
@@ -23141,6 +23141,10 @@ var publishingInputs = (platforms, includePublishing) => {
23141
23141
  description: Source workflow run ID for promotion without rebuilding
23142
23142
  required: false
23143
23143
  type: string`,
23144
+ ` dispatch_id:
23145
+ description: Unique promotion correlation ID supplied by the AbsoluteJS CLI
23146
+ required: false
23147
+ type: string`,
23144
23148
  ` certification_base64:
23145
23149
  description: AbsoluteJS certification supplied by the promotion CLI
23146
23150
  required: false
@@ -23513,6 +23517,10 @@ var promotionValidationSteps = ` - name: Validate promotion handoff
23513
23517
  echo "Promotion requires a numeric source workflow run ID." >&2
23514
23518
  exit 1
23515
23519
  fi
23520
+ if [[ ! "$ABSOLUTE_PROMOTION_DISPATCH_ID" =~ ^amp_[a-f0-9]{32}$ ]]; then
23521
+ echo "Promotion requires a valid AbsoluteJS dispatch correlation ID." >&2
23522
+ exit 1
23523
+ fi
23516
23524
  if [[ -z "$ABSOLUTE_CERTIFICATION_BASE64" ]]; then
23517
23525
  echo "Promotion requires certification supplied by absolute mobile ci promote." >&2
23518
23526
  exit 1
@@ -23527,7 +23535,7 @@ var promotionSharedSteps = (platform) => ` - name: Restore exact ${platform
23527
23535
  - name: Record bounded promotion context
23528
23536
  shell: bash
23529
23537
  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");'
23538
+ bun -e 'const dispatchId = process.env.ABSOLUTE_PROMOTION_DISPATCH_ID; const sourceRunId = process.env.ABSOLUTE_SOURCE_RUN_ID; const promotionRunId = process.env.GITHUB_RUN_ID; if (!dispatchId?.match(/^amp_[a-f0-9]{32}$/) || !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({ dispatchId, format: 2, platform: ${JSON.stringify(platform)}, promotionRunId, sourceArtifact: ${JSON.stringify(`absolute-mobile-${platform}`)}, sourceRunId }, null, 2) + "\\n");'
23531
23539
  - name: Import and verify exact certification
23532
23540
  shell: bash
23533
23541
  run: |
@@ -23565,6 +23573,7 @@ var androidPromotionJob = (options) => {
23565
23573
  id-token: write
23566
23574
  env:
23567
23575
  ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}
23576
+ ABSOLUTE_PROMOTION_DISPATCH_ID: \${{ inputs.dispatch_id }}
23568
23577
  ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
23569
23578
  ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
23570
23579
  ABSOLUTE_PLAY_TRACK: \${{ inputs.play_track }}
@@ -23630,6 +23639,7 @@ var iosPromotionJob = (options) => {
23630
23639
  id-token: write
23631
23640
  env:
23632
23641
  ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}
23642
+ ABSOLUTE_PROMOTION_DISPATCH_ID: \${{ inputs.dispatch_id }}
23633
23643
  ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
23634
23644
  ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
23635
23645
  ABSOLUTE_TESTFLIGHT_GROUP: \${{ inputs.testflight_group }}
@@ -23720,6 +23730,7 @@ var createAbsoluteMobileGithubWorkflow = (options) => {
23720
23730
  });
23721
23731
  let workflow = `# Generated by AbsoluteJS. Regenerate with: absolute mobile ci github${includePublishing ? " --publish" : ""}
23722
23732
  name: AbsoluteJS Mobile
23733
+ run-name: \${{ inputs.operation == 'promote' && format('AbsoluteJS mobile promotion [{0}]', inputs.dispatch_id) || format('AbsoluteJS mobile {0}', github.event_name) }}
23723
23734
 
23724
23735
  on:
23725
23736
  pull_request:
@@ -23745,6 +23756,7 @@ jobs:
23745
23756
  env:
23746
23757
  ${environment}${includePublishing ? `
23747
23758
  ABSOLUTE_PROMOTION_PLATFORM: \${{ inputs.platform }}
23759
+ ABSOLUTE_PROMOTION_DISPATCH_ID: \${{ inputs.dispatch_id }}
23748
23760
  ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
23749
23761
  ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}` : ""}
23750
23762
  steps:
@@ -23807,12 +23819,15 @@ import {
23807
23819
  } from "fs/promises";
23808
23820
  import { dirname as dirname12, isAbsolute as isAbsolute8, join as join15, relative as relative12, resolve as resolve13, sep as sep9 } from "path";
23809
23821
  var ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT = 1;
23810
- var ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT = 1;
23822
+ var ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT = 2;
23811
23823
  var MAX_GITHUB_OUTPUT_BYTES = 1048576;
23812
23824
  var MAX_AUDIT_JSON_BYTES = 1048576;
23813
23825
  var MAX_SEARCH_FILES = 20000;
23814
23826
  var RUN_ID_PATTERN = /^[1-9][0-9]*$/u;
23815
23827
  var REPOSITORY_PATTERN2 = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u;
23828
+ var DISPATCH_ID_PATTERN = /^amp_[a-f0-9]{32}$/u;
23829
+ var PROMOTION_DISCOVERY_ATTEMPTS = 60;
23830
+ var PROMOTION_DISCOVERY_INTERVAL_MS = 1000;
23816
23831
  var isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
23817
23832
  var requiredString = (value, name) => {
23818
23833
  if (typeof value !== "string" || value.trim() === "")
@@ -23829,6 +23844,26 @@ var runId = (value, name) => {
23829
23844
  throw new TypeError(`${name} is invalid.`);
23830
23845
  return value;
23831
23846
  };
23847
+ var dispatchId = (value) => {
23848
+ if (!DISPATCH_ID_PATTERN.test(value))
23849
+ throw new TypeError("Mobile promotion dispatch ID is invalid.");
23850
+ return value;
23851
+ };
23852
+ var promotionRunTitle = (value) => `AbsoluteJS mobile promotion [${dispatchId(value)}]`;
23853
+ var promotionDiscoveryCommand = (workflow, repository) => [
23854
+ "gh",
23855
+ "run",
23856
+ "list",
23857
+ "--workflow",
23858
+ workflow,
23859
+ "--event",
23860
+ "workflow_dispatch",
23861
+ "--limit",
23862
+ "50",
23863
+ ...repositoryArgs(repository),
23864
+ "--json",
23865
+ "databaseId,displayTitle,event,url"
23866
+ ];
23832
23867
  var repositoryArgs = (repository) => {
23833
23868
  if (repository === undefined)
23834
23869
  return [];
@@ -23876,6 +23911,27 @@ var parseJob = (value) => {
23876
23911
  status: requiredString(value.status, "GitHub job status")
23877
23912
  };
23878
23913
  };
23914
+ var discoverAbsoluteMobilePromotionRun = async (options) => {
23915
+ const requestedDispatchId = dispatchId(options.dispatchId);
23916
+ const attempts = options.attempts ?? PROMOTION_DISCOVERY_ATTEMPTS;
23917
+ if (!Number.isSafeInteger(attempts) || attempts < 1 || attempts > 300)
23918
+ throw new TypeError("Mobile promotion discovery attempts are invalid.");
23919
+ const pollIntervalMs = options.pollIntervalMs ?? PROMOTION_DISCOVERY_INTERVAL_MS;
23920
+ if (!Number.isSafeInteger(pollIntervalMs) || pollIntervalMs < 0 || pollIntervalMs > 1e4)
23921
+ throw new TypeError("Mobile promotion discovery interval is invalid.");
23922
+ const sleep = options.sleep ?? Bun.sleep;
23923
+ const discover = async (attempt) => {
23924
+ const result = await requireCommand2(options.run ?? defaultRunner, promotionDiscoveryCommand(options.workflow, options.repository), { cwd: options.projectRoot }, "GitHub promotion run discovery");
23925
+ const discovered = parseAbsoluteMobilePromotionRunList(JSON.parse(result.stdout), requestedDispatchId);
23926
+ if (discovered)
23927
+ return discovered;
23928
+ if (attempt >= attempts)
23929
+ throw new TypeError(`GitHub did not expose promotion dispatch ${requestedDispatchId} within ${attempts} attempts.`);
23930
+ await sleep(pollIntervalMs);
23931
+ return discover(attempt + 1);
23932
+ };
23933
+ return discover(1);
23934
+ };
23879
23935
  var inspectAbsoluteMobileCiRun = async (options) => {
23880
23936
  const requestedRunId = runId(options.runId, "GitHub run ID");
23881
23937
  const result = await requireCommand2(options.run ?? defaultRunner, [
@@ -23885,7 +23941,7 @@ var inspectAbsoluteMobileCiRun = async (options) => {
23885
23941
  requestedRunId,
23886
23942
  ...repositoryArgs(options.repository),
23887
23943
  "--json",
23888
- "conclusion,createdAt,databaseId,headBranch,headSha,jobs,startedAt,status,updatedAt,url,workflowName"
23944
+ "conclusion,createdAt,databaseId,displayTitle,headBranch,headSha,jobs,startedAt,status,updatedAt,url,workflowName"
23889
23945
  ], { cwd: options.projectRoot }, "GitHub workflow inspection");
23890
23946
  return parseAbsoluteMobileCiRunStatus(JSON.parse(result.stdout), requestedRunId);
23891
23947
  };
@@ -23898,6 +23954,7 @@ var parseAbsoluteMobileCiRunStatus = (value, requestedRunId) => {
23898
23954
  return {
23899
23955
  conclusion: optionalString(value.conclusion, "GitHub run conclusion"),
23900
23956
  createdAt: requiredString(value.createdAt, "GitHub run createdAt"),
23957
+ displayTitle: requiredString(value.displayTitle, "GitHub run displayTitle"),
23901
23958
  headBranch: requiredString(value.headBranch, "GitHub run headBranch"),
23902
23959
  headSha: requiredString(value.headSha, "GitHub run headSha"),
23903
23960
  jobs: value.jobs.map(parseJob),
@@ -23910,20 +23967,44 @@ var parseAbsoluteMobileCiRunStatus = (value, requestedRunId) => {
23910
23967
  };
23911
23968
  };
23912
23969
  var parseAbsoluteMobilePromotionContext = (value) => {
23913
- if (!isRecord9(value) || value.format !== ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT || value.platform !== "android" && value.platform !== "ios")
23970
+ if (!isRecord9(value) || value.format !== 1 && value.format !== ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT || value.platform !== "android" && value.platform !== "ios")
23914
23971
  throw new TypeError("Mobile promotion context is invalid.");
23915
23972
  const { platform } = value;
23916
23973
  const sourceArtifact = requiredString(value.sourceArtifact, "promotion source artifact");
23917
23974
  if (sourceArtifact !== `absolute-mobile-${platform}`)
23918
23975
  throw new TypeError("Mobile promotion source artifact is invalid.");
23919
23976
  return {
23920
- format: ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT,
23977
+ dispatchId: value.format === 1 ? null : dispatchId(requiredString(value.dispatchId, "promotion dispatch ID")),
23978
+ format: value.format,
23921
23979
  platform,
23922
23980
  promotionRunId: runId(requiredString(value.promotionRunId, "promotion run ID"), "promotion run ID"),
23923
23981
  sourceArtifact,
23924
23982
  sourceRunId: runId(requiredString(value.sourceRunId, "source run ID"), "source run ID")
23925
23983
  };
23926
23984
  };
23985
+ var parseAbsoluteMobilePromotionRunList = (value, requestedDispatchId) => {
23986
+ if (!Array.isArray(value))
23987
+ throw new TypeError("GitHub workflow run list response is invalid.");
23988
+ const expectedTitle = promotionRunTitle(requestedDispatchId);
23989
+ const matches = value.filter((candidate) => isRecord9(candidate) && candidate.displayTitle === expectedTitle);
23990
+ if (matches.length === 0)
23991
+ return null;
23992
+ if (matches.length !== 1)
23993
+ throw new TypeError("GitHub returned multiple runs for one promotion dispatch identity.");
23994
+ const [match] = matches;
23995
+ if (!isRecord9(match) || match.event !== "workflow_dispatch")
23996
+ throw new TypeError("GitHub promotion run identity is invalid.");
23997
+ const databaseId = typeof match.databaseId === "number" || typeof match.databaseId === "string" ? String(match.databaseId) : "";
23998
+ const requestedRunId = runId(databaseId, "GitHub promotion run ID");
23999
+ const url = requiredString(match.url, "GitHub promotion run URL");
24000
+ if (!url.endsWith(`/actions/runs/${requestedRunId}`))
24001
+ throw new TypeError("GitHub promotion run URL does not match its ID.");
24002
+ return {
24003
+ dispatchId: requestedDispatchId,
24004
+ runId: requestedRunId,
24005
+ url
24006
+ };
24007
+ };
23927
24008
  var watchAbsoluteMobileCiRun = async (options) => {
23928
24009
  const requestedRunId = runId(options.runId, "GitHub run ID");
23929
24010
  await requireCommand2(options.run ?? defaultRunner, [
@@ -24042,6 +24123,7 @@ var renderAuditMarkdown = (audit) => `# AbsoluteJS mobile promotion audit
24042
24123
  - Artifact SHA-256: \`${audit.release.sha256}\`
24043
24124
  - Source workflow run: ${audit.github.sourceRunId}
24044
24125
  - Promotion workflow run: ${audit.github.promotionRunId}
24126
+ - Dispatch correlation: ${audit.github.dispatchId ?? "legacy workflow (not recorded)"}
24045
24127
  - Workflow: ${audit.github.workflowName}
24046
24128
  - GitHub identity: \`${audit.provenance.repository}/${audit.provenance.workflowPath}@${audit.provenance.ref}\`
24047
24129
  - Certification: \`${audit.certification.certificationId}\` (${audit.certification.strength})
@@ -24091,6 +24173,8 @@ var auditAbsoluteMobileCiPromotion = async (options) => {
24091
24173
  throw new TypeError("Promotion context run identity does not match.");
24092
24174
  if (context && context.platform !== platform)
24093
24175
  throw new TypeError("Promotion context platform does not match.");
24176
+ if (context?.dispatchId && status.displayTitle !== promotionRunTitle(context.dispatchId))
24177
+ throw new TypeError("Promotion dispatch identity does not match the GitHub run.");
24094
24178
  const sourceRunId = options.sourceRunId ? runId(options.sourceRunId, "GitHub source run ID") : context?.sourceRunId;
24095
24179
  if (!sourceRunId)
24096
24180
  throw new TypeError("Promotion artifact predates source-run context; pass --source-run-id.");
@@ -24159,6 +24243,7 @@ var auditAbsoluteMobileCiPromotion = async (options) => {
24159
24243
  format: ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT,
24160
24244
  github: {
24161
24245
  conclusion: status.conclusion,
24246
+ dispatchId: context?.dispatchId ?? null,
24162
24247
  promotionRunId,
24163
24248
  repository: verification.identity.repository,
24164
24249
  sourceRunId,
@@ -25061,12 +25146,38 @@ var createGithubCertificationVerification = async (args) => {
25061
25146
  console.log(relative16(projectRoot, path).replaceAll("\\", "/"));
25062
25147
  return verification;
25063
25148
  };
25149
+ var auditDispatchedGithubMobilePromotion = async (options) => {
25150
+ const startedAt = performance.now();
25151
+ try {
25152
+ const audited = await auditAbsoluteMobileCiPromotion({
25153
+ outputDirectory: valueAfter(options.args, "--outdir"),
25154
+ projectRoot: options.projectRoot,
25155
+ ...options.repository ? { repository: options.repository } : {},
25156
+ runId: options.runId
25157
+ });
25158
+ sendTelemetryEvent("mobile:ci-promotion-audit", {
25159
+ durationMs: Math.round(performance.now() - startedAt),
25160
+ platform: options.platform,
25161
+ provider: "github-actions",
25162
+ success: true
25163
+ });
25164
+ return audited;
25165
+ } catch (error) {
25166
+ sendTelemetryEvent("mobile:ci-promotion-audit", {
25167
+ durationMs: Math.round(performance.now() - startedAt),
25168
+ platform: options.platform,
25169
+ provider: "github-actions",
25170
+ success: false
25171
+ });
25172
+ throw error;
25173
+ }
25174
+ };
25064
25175
  var dispatchGithubMobilePromotion = async (args) => {
25065
25176
  const [platform2] = args;
25066
25177
  if (platform2 !== "android" && platform2 !== "ios")
25067
- throw new TypeError("Usage: absolute mobile ci promote <android|ios> --run-id id --certification directory-or-json [--channel name] [store options]");
25068
- const runId2 = valueAfter(args, "--run-id");
25069
- if (!runId2 || !/^[1-9][0-9]*$/u.test(runId2))
25178
+ throw new TypeError("Usage: absolute mobile ci promote <android|ios> --run-id id --certification directory-or-json [--channel name] [store options] [--watch] [--audit]");
25179
+ const sourceRunId = valueAfter(args, "--run-id");
25180
+ if (!sourceRunId || !/^[1-9][0-9]*$/u.test(sourceRunId))
25070
25181
  throw new TypeError("mobile ci promote requires a numeric --run-id.");
25071
25182
  const requestedCertification = valueAfter(args, "--certification");
25072
25183
  if (!requestedCertification || requestedCertification.startsWith("-"))
@@ -25085,6 +25196,8 @@ var dispatchGithubMobilePromotion = async (args) => {
25085
25196
  if (certificationBase64.length > 48000)
25086
25197
  throw new TypeError("Mobile certification is too large for protected GitHub dispatch.");
25087
25198
  const workflow = valueAfter(args, "--workflow") ?? ".github/workflows/absolute-mobile.yml";
25199
+ const dispatchId2 = `amp_${randomUUID().replaceAll("-", "")}`;
25200
+ const repository = valueAfter(args, "--repo");
25088
25201
  const command = [
25089
25202
  "workflow",
25090
25203
  "run",
@@ -25094,7 +25207,9 @@ var dispatchGithubMobilePromotion = async (args) => {
25094
25207
  "-f",
25095
25208
  `platform=${platform2}`,
25096
25209
  "-f",
25097
- `source_run_id=${runId2}`,
25210
+ `source_run_id=${sourceRunId}`,
25211
+ "-f",
25212
+ `dispatch_id=${dispatchId2}`,
25098
25213
  "-f",
25099
25214
  `certification_base64=${certificationBase64}`,
25100
25215
  "-f",
@@ -25109,21 +25224,71 @@ var dispatchGithubMobilePromotion = async (args) => {
25109
25224
  const ref = valueAfter(args, "--ref");
25110
25225
  if (ref)
25111
25226
  command.push("--ref", ref);
25227
+ if (repository)
25228
+ command.push("--repo", repository);
25112
25229
  const child = Bun.spawn(["gh", ...command], {
25113
25230
  cwd: projectRoot,
25114
- stderr: "inherit",
25231
+ stderr: "pipe",
25115
25232
  stdin: "inherit",
25116
- stdout: "inherit"
25233
+ stdout: "pipe"
25117
25234
  });
25118
- const exitCode = await child.exited;
25235
+ const [exitCode, stderr] = await Promise.all([
25236
+ child.exited,
25237
+ new Response(child.stderr).text(),
25238
+ new Response(child.stdout).text()
25239
+ ]);
25119
25240
  if (exitCode !== 0)
25120
- throw new TypeError(`GitHub promotion dispatch exited with status ${exitCode}.`);
25241
+ throw new TypeError(`GitHub promotion dispatch exited with status ${exitCode}: ${stderr.trim() || "unknown GitHub CLI error"}`);
25242
+ const promotion = await discoverAbsoluteMobilePromotionRun({
25243
+ dispatchId: dispatchId2,
25244
+ projectRoot,
25245
+ ...repository ? { repository } : {},
25246
+ workflow
25247
+ });
25121
25248
  sendTelemetryEvent("mobile:ci-promotion-dispatched", {
25249
+ discovery: "correlated",
25122
25250
  platform: platform2,
25123
25251
  provider: "github-actions",
25124
25252
  storeTarget: platform2 === "android" ? playTrack !== "registry-only" : testflightGroup !== ""
25125
25253
  });
25126
- console.log(`Dispatched protected ${platform2} promotion for ${certification.release.releaseId} from workflow run ${runId2}.`);
25254
+ if (!args.includes("--json")) {
25255
+ console.log(`Dispatched protected ${platform2} promotion ${promotion.runId} for ${certification.release.releaseId} from workflow run ${sourceRunId}.`);
25256
+ console.log(promotion.url);
25257
+ }
25258
+ const shouldWatch = args.includes("--watch") || args.includes("--audit");
25259
+ if (shouldWatch && !args.includes("--json"))
25260
+ console.log(`Watching GitHub run ${promotion.runId} until completion\u2026`);
25261
+ const status = shouldWatch ? await watchAbsoluteMobileCiRun({
25262
+ projectRoot,
25263
+ ...repository ? { repository } : {},
25264
+ runId: promotion.runId
25265
+ }) : null;
25266
+ const audited = args.includes("--audit") ? await auditDispatchedGithubMobilePromotion({
25267
+ args,
25268
+ platform: platform2,
25269
+ projectRoot,
25270
+ ...repository ? { repository } : {},
25271
+ runId: promotion.runId
25272
+ }) : null;
25273
+ const result = {
25274
+ auditDirectory: audited ? relative16(projectRoot, audited.directory).replaceAll("\\", "/") : null,
25275
+ dispatchId: dispatchId2,
25276
+ platform: platform2,
25277
+ releaseId: certification.release.releaseId,
25278
+ runId: promotion.runId,
25279
+ sourceRunId,
25280
+ status: status?.status ?? "dispatched",
25281
+ url: promotion.url
25282
+ };
25283
+ if (args.includes("--json"))
25284
+ console.log(JSON.stringify(result, null, 2));
25285
+ if (args.includes("--json"))
25286
+ return result;
25287
+ if (status)
25288
+ renderGithubMobileRunStatus(status);
25289
+ if (audited)
25290
+ renderGithubMobilePromotionAudit(audited, projectRoot);
25291
+ return result;
25127
25292
  };
25128
25293
  var githubMobileRunId = (args) => {
25129
25294
  const value = valueAfter(args, "--run-id");
@@ -27813,7 +27978,7 @@ var runMobile = async (args) => {
27813
27978
  await publishIos(args.slice(2));
27814
27979
  return;
27815
27980
  }
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]");
27981
+ 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] [--repo owner/name] [--watch] [--audit] [--outdir path] [--json] | 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]");
27817
27982
  };
27818
27983
  export {
27819
27984
  runMobile